feat(tui): Linked Resources on project status and the harness hub - #2215
Conversation
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Changes requested
Nice piece of work overall — the grouping logic is clearly documented, TreeView gets a small, principled extension, and the dispatch/test coverage for TTY vs. non-TTY is thorough. One substantive issue about the region-forwarding mechanism, plus a couple of smaller things.
Region drops on forward navigation from a detail screen
src/components/Root.tsx (AppRoutes, ~lines 146–153) resolves the effective region from location.state.region for every route, and src/handlers/project/status/screen.tsx:1786 seeds that state when opening a detail page. Because state is per-history-entry, that works for the initial status → detail hop and for esc back — but any further forward navigation from the detail screen re-enters AppRoutes with a location.state that doesn't carry region, so the destination silently falls back to the launch (ambient) region:
src/handlers/runtime/get/screen.tsx:66— the "endpoints", "versions", and "detail (json)" actions callnavigate(action.to(runtimeId), { state: … })withoutregion. Only theinvokeaction sets state at all, and even then it's{ returnOnEscape: true }, not{ region }.src/handlers/memory/get/screen.tsx:71andsrc/handlers/harness/get/screen.tsx:78— actiononSelecthandlers callnavigate(action.to(id))with no state at all.src/handlers/gateway/target/get/…— same shape.
So the concrete case is: on a project deployed to us-west-2 with ambient region us-east-1, status → runtime detail fetches in us-west-2 (correct, and covered by a test), but then picking "endpoints" / "versions" / "invoke" / "json" from that page fetches in us-east-1 and the user sees "not found" or gets pointed at the wrong resource. Same for memory→segments/…, harness→endpoints/versions/etc., gateway→targets.
A few options for the author:
- Have every detail screen that owns an action list forward the current
regionfrom itslocation.stateonto itsnavigate({ state })calls. Simple and localized, but easy to forget for future screens. - Persist the region higher up (e.g. a small "session region" ref/context set by the status screen when it forwards, cleared when the user leaves via the project menu), so downstream screens don't have to plumb state.
- Encode the region in the URL for the detail routes that need it and read it from
useParams(route-first approach — no reliance on history state at all).
Whichever direction is chosen, please add a screen test that drills at least one level past the detail page (e.g. status → runtime detail → endpoints list) and asserts the region on the resulting core-client call.
Minor
src/handlers/project/status/screen.tsx:1734— the description string passed toProjectGate("the project's linked resources on target default") hardcodesdefault, but theTARGET_NAME = "default"constant is right above; consider interpolating it so the two can't drift. TheLayoutrender below already usesstatus.data.target.namefor the same string, so the gate/loading path is the only inconsistency. Not blocking.src/handlers/project/status/screen.tsx:1685— every memory inspec.memoriesis claimed by every runtime agent, butclaim()still callsclaimed.addon each pass. That's fine functionally (Set dedupes), but the intent — "a memory is claimed once it appears under any runtime" — is worth a one-liner comment for the next reader; the neighboring block-comment already explains the why of the grouping rule but not this detail.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## refactor #2215 +/- ##
============================================
+ Coverage 97.05% 97.06% +0.01%
============================================
Files 555 560 +5
Lines 38386 38979 +593
============================================
+ Hits 37254 37834 +580
- Misses 1132 1145 +13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Claude Security Review: no high-confidence findings. (run) |
|
Claude Security Review: no high-confidence findings. (run) |
03d6b09 to
f2ce185
Compare
|
Claude Security Review: no high-confidence findings. (run) |
A bare `agentcore project status` in a terminal now opens an interactive Linked Resources view: each agent the project declares (spec.runtimes and spec.harnesses) is a top-level group holding its deployed Runtime/Harness and linked Memories, with everything unattributable kept visible under a shared "project" group. Enter on a deployed Runtime, Harness, Memory, Gateway, or Gateway Target forwards to its existing detail page; local-only rows are dimmed and non-selectable. Any user-supplied flag, --json, or a non-TTY invocation keeps the headless JSON report unchanged. `status` joins the project router's supported TUI commands so the menu lists it as interactive. The harness hub ends with the same idea. Creating a harness provisions an AgentCore Runtime underneath it and, in almost every case, a managed Memory; a harness can also be wired to a Gateway (with an OAuth2 provider for outbound auth), a Browser, a Code Interpreter, and to the API key providers behind its model and git-backed skills. GetHarness reports all of them as ARNs, and most already have a detail screen, but the hub showed none of it. It now closes with a `linked resources` divider and a tree in the status screen's row style (padded type column, name, muted annotation): Runtime, Memory (`managed` or `attached`), Gateways with their OAuth2 provider nested beneath, Browsers and Code Interpreters (`aws default` when the ARN is unset or AWS-owned), then the credential providers. Enter forwards to the resource's own hub; Browser and Code Interpreter rows have no screen and print a hint instead. Inline-function and remote-MCP tools are not AgentCore resources and are left out. Detail screens fetch in the region pinned on their context, which is the ambient one — not necessarily where a linked resource lives. Links carry `?region=` on the detail route (the project target's region from status, the region parsed from the resource's own ARN from the harness hub), resolved by a useCoreOpts hook the detail screens share: a truthy region in the query string wins, otherwise the context's region applies as before. History-back restores the previous screen, so escape works at every level and the override is visible in the route itself. serviceIdFromArn lives in a shared, tested src/core/arn.ts alongside the helpers the credential provider ARNs need (the trailing name, the api-key/oauth2 kind, the region). The tree is an optional, generic slot on ResourceDetailScreen so the Runtime, Memory and Gateway hubs can adopt it later. The action list and the tree behave as one continuous list: down from the last action moves focus into the tree, up from the tree's first row hands it back (an onUpFromFirst hook on the vendored TreeView, which also gains the focusMarker ❯ style and per-node annotations), enter acts on whichever zone is focused, and only the focused zone shows the marker. The type-column padding is shared by both trees and accounts for the guide characters a nested row is pushed right by. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMPY8yuo2z5Vhrmyte1Lk2 Claude-Session: https://claude.ai/code/session_016KnHdxcPYTnQiDY7Y1PY6s
Type labels are lowercase service names now ("runtime", "memory",
"gateway", "gateway-target", "api key", …) instead of title-cased display
names, the status screen shows each resource's declared type as-is, and
the harness Runtime row names the runtime by its id — the value every
`runtime` command takes — rather than the derived agentRuntimeName.
The tree sits flush with the action list: TreeView drops the space it
padded between the branch marker and the label when icons are off, the
LinkedResourcesTree loses its left padding, and the type column takes a
minimum width so the harness tree's names line up with the action
descriptions above them. The status screen's heading is a plain
"resources" line without the blank line beneath it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016KnHdxcPYTnQiDY7Y1PY6s
|
Claude Security Review: no high-confidence findings. (run) |
After rebasing onto refactor, which routes terminal glyphs through the glyphs table for conhost and spells the quit hint ctrl+c, the tree's focus marker still hardcoded ❯ and the status screen still said ctl+c. Both follow the shared conventions now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KnHdxcPYTnQiDY7Y1PY6s
501f815 to
aa88158
Compare
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
The harness reviewer called out a really good finding which looks valid to me. The project target region isn't preserved on nav, so status would display "resource not found" for a resource that does exist in the user's target region.
One other minor finding. Changes look great otherwise!
| }; | ||
| }; | ||
|
|
||
| const agentGroups: StatusNode[] = [ |
There was a problem hiding this comment.
We should differentiate IDs based on resource type since harnesses and runtimes can have the same name. ID as agent:${name} for both would cause a collision in this scenario. Maybe agent-runtime:${name} and agent-harness:${name} ?
There was a problem hiding this comment.
On the region stuff, the way it actually works is that we add a region query string to the location ?region=us-west-2 and resolve the region from that if it exists. So they case the harness reviewer is warning about is actually handled.
On this second point, I think this is innocuous. Even if a harness and a runtime have the same name, it wouldn't cause an issue here.
What
Two Linked Resources trees, built on the vendored
TreeView, that turn the ARNs the service reports into navigable rows.agentcore project statuswas headless-only. A bare invocation in a terminal now opens an interactive tree; any user-supplied flag,--json, or a non-TTY invocation keeps producing exactly the current headless JSON report.spec.runtimes(code agents) andspec.harnesses(managed harness agents) is a top-level group holding the agent's deployed Runtime/Harness and its linked Memories. Resources not attributable to an agent (gateways, credentials, config bundles, …) stay visible under a sharedprojectgroup, with the resolver's nested children (gateway → targets, policy engine → policies, payment manager → connectors) carried through.local-only, and non-selectable; deployed types without a detail screen explain themselves instead of navigating.statusjoins the project router'ssupportedTuiCommands, so theagentcore projectmenu lists it above the "command line only" divider.The harness hub (
agentcore harness get→ a harness) now ends with alinked resourcesdivider and the same kind of tree, listing whatGetHarnessreports the harness is wired to:runtime— the AgentCore Runtime the service provisioned under the harness, named by its id (the value everyruntimecommand takes)memory—managed(managedMemoryConfiguration.arn) orattached(agentCoreMemoryConfiguration.arn); omitted when memory is absent ordisabledgatewayrows foragentCoreGatewaytools, with the outbound-authoauth2 providernested beneath (expanded by default)browser/code interpreterrows for those tools, annotatedaws defaultwhen the ARN is unset or owned by theawsaccountapi keyrows for the model'sapiKeyArn(annotated with the model id) and for git skills'auth.credentialArn(annotated with the skill path or repository)Enter opens the resource's existing hub; Browser and Code Interpreter have none, so they stay selectable and print
<type> <name> has no detail view.under the tree. Inline-function and remote-MCP tools are not AgentCore resources and never appear.Why
Creating a harness always provisions a Runtime and almost always a managed Memory, and a harness can reference a Gateway, Browser, Code Interpreter and credential providers — all reported as ARNs that already have detail screens. Until now neither the project nor the harness hub showed any of it; the user had to read the JSON and find each resource by hand.
Design decisions
buildStatusNodes): every declared memory groups under every runtime agent, because the CDK L3 injects aMEMORY_<NAME>_IDenv var for each declared memory into every runtime. A harness's memory ref lives in its ownharness.json, not in the project spec the report is built from, so harness groups list the harness itself and unclaimed memories fall back to the shared group.?region=on the detail route (the project target's region from status; the region parsed from the resource's own ARN from the harness hub, falling back to the region the harness was fetched in), resolved by a shareduseCoreOptshook: a truthy region in the query string wins, otherwise the context's region applies as before. The two identity get screens switch fromcoreOptsFromCtxtouseCoreOptsso the link is honoured there too. History-back restores the previous screen, so escape works at every level and the override is visible in the route itself.serviceIdFromArnlives insrc/core/arn.ts(tested) withresourceNameFromArn,regionFromArnandcredentialProviderTypeFromArn— credential provider ARNs (…:token-vault/<vault>/apikeycredentialprovider/<name>) need the trailing name, and the api-key/oauth2 distinction comes from the ARN alone.ResourceDetailScreengains an optionallinkedResourcesprop rendered throughLinkedResourcesTree(divider + tree + hint), so the Runtime, Memory and Gateway hubs can adopt it later; only the harness hub is wired up here. The node builders (buildStatusNodes,buildHarnessLinkNodes) are pure exported functions.onUpFromFirstprop onTreeView), enter acts on the focused zone, escape still goes back, and the❯marker only ever shows in one zone. Footer hints follow the zone (selectvsopen,←→ collapse/expandonly when the tree has nested rows).TreeViewgainsfocusMarker(the❯selection style the rest of the TUI uses instead of inverse video) and per-node muted annotations, with icons off. Type labels are lowercase service names; the shared type-column padding accounts for the guide characters a nested row is pushed right by, and takes a minimum width so the harness tree's names line up with the action descriptions above them.Checks
Rebased onto
refactorat 3fcf406 (conflicts resolved by hand); a follow-up commit routes the tree's focus marker through theglyphstable and spells the quit hintctrl+c, matching the conhost and help-text changes that landed there.bun test src— 3033 pass. The 11 failures are all insrc/io/exec.test.ts, which spawnsnode, not installed in the verification environment (same 11 fail on the base commit).bun run typecheck,bun run lint:check,bun run format:check— clean.src/handlers/project/status/(screen and handler dispatch),get.screen.test.tsx(rendering, disabled-memory case, focus hand-off, single marker, Runtime/Memory/Gateway/OAuth2/API Key navigation with the ARN's region, Browser hint, footer hints, node-builder unit tests) andsrc/core/arn.test.ts.Live verification
Project status (live E2E). Scaffolded a project with runtime
myFirstAgent+myFirstAgentMemory+ an added harness, deployed to us-west-2 with ambient region us-east-1 (the mismatch case), and drove the real TUI in a PTY: the tree grouped the memory under the agent, enter landed on live Runtime (READY), Memory (ACTIVE), and Harness (READY) detail pages fetched in us-west-2, and escape walked back cleanly. Headless--jsonverified unchanged; stack torn down afterwards (DELETE_COMPLETE, noStatusE2Eresources remain).Harness hub — inventory (live).
harness list --jsonin every region plusharness get --id … --jsonon each result: 19 harnesses — 13 in us-east-1,MyPDXHarness-rhkXkAE1ISin us-west-2,harness_demo-Rrs0v4OyAOin us-east-2, andMyFirstHarness-Ya6Rf9E905/vpc-xV0snhWxb9/vpc_test_2-owfavpaf0xin ap-southeast-2. Across them the linked resources are:KnicksHarness-MFZ7GXmTYQ,inlineCliDemo-2poyWgSG0m,myCliHarness-up15WydSrZFunTimes-T2MFxkuezh,KnicksHarness-MFZ7GXmTYQ,asdf-diSdqKDM2s,myCliHarness-up15WydSrZ,myFunDemoHarness-uwjQ1a7oM7,testAgain-2B1451OLO2,wizardDemo-igTsHbt17Maws.browser.v1ARNharness_stnea-VVc5FvKgtr,harness_ummrg-NC9CEPEP0A,harness_demo-Rrs0v4OyAO(us-east-2)harness_demo-Rrs0v4OyAOHarness hub — TUI walkthrough. The temporary session credentials expired partway through and the instance role has no AgentCore permissions, so this walkthrough ran in a real pseudo-terminal (
bun run start harness get --endpoint-url …) against a local stub that replays the live-capturedGetHarnessresponses verbatim, synthesises minimalGetAgentRuntime/GetMemory/GetGateway/ credential-provider responses for the link targets, and logs the SigV4 signing region of every request. Asynthetic-linkedharness (theKnicksHarnesscapture plus a gateway with OAuth outbound auth, a code interpreter, an OpenAI API key and a remote MCP tool) covered the row types no deployed harness has. Verified:KnicksHarness-MFZ7GXmTYQ: divider,runtime harness_KnicksHarness-QDSgiYAteM,memory … managed,browser default aws default; down fromupdatefocuses the runtime row, further down moves within the tree, up twice returns toupdate, exactly one❯at every step; enter on runtime opensagentcore → runtime → get → harness_KnicksHarness-QDSgiYAteM(stub sawGET /runtimes/… region=us-east-1, the ARN's region), escape returns to the hub with the tree; enter on memory opens the Memory hub (region=us-east-1); enter on browser prints the hint and does not navigate.harness_stnea-VVc5FvKgtrandharness_demo-Rrs0v4OyAO(opened with--region us-east-2):browser aws.browser.v1 aws default; the remote MCP tool does not appear.MyPDXHarness-rhkXkAE1ISopened with--region us-west-2: runtime row present; enter opens the runtime hub with the stub signing forus-west-2; escape returns.synthetic-linked: all seven rows with the type column aligned andoauth2 provider github-oauth outbound authnested under the gateway;←/→collapse and expand it; enter on gateway, oauth2 provider and api key open the right hubs (tools-Gw1234567,github-oauth,openai-key) with every fetch signed forus-west-2from the ARN while the harness sat inus-east-1; enter on code interpreter prints the hint. Re-rendered after the row-style pass and again after the rebase to confirm the labels align with the action descriptions and the Memory link still signs for the ARN's region.Not verified live. Forward navigation from the harness hub to the real Runtime and Memory services (only the route and signing region were observed through the stub), and every gateway / oauth2 provider / api key / code interpreter / attached-memory row (synthetic only). With fresh credentials:
bun run start harness get, pickKnicksHarness-MFZ7GXmTYQ, then the same steps as above.🤖 Generated with Claude Code
https://claude.ai/code/session_01AMPY8yuo2z5Vhrmyte1Lk2
https://claude.ai/code/session_016KnHdxcPYTnQiDY7Y1PY6s