Skip to content

feat(tui): Linked Resources on project status and the harness hub - #2215

Merged
AlexanderRichey merged 3 commits into
refactorfrom
project-status-tui
Sep 4, 2026
Merged

feat(tui): Linked Resources on project status and the harness hub#2215
AlexanderRichey merged 3 commits into
refactorfrom
project-status-tui

Conversation

@AlexanderRichey

@AlexanderRichey AlexanderRichey commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What

Two Linked Resources trees, built on the vendored TreeView, that turn the ARNs the service reports into navigable rows.

agentcore project status was 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.

  • Grouped by agent: each entry in spec.runtimes (code agents) and spec.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 shared project group, with the resolver's nested children (gateway → targets, policy engine → policies, payment manager → connectors) carried through.
  • Selection forwards to detail pages: enter on a deployed Runtime, Harness, Memory, Gateway, or Gateway Target opens its existing detail screen. Local-only rows are dimmed, annotated local-only, and non-selectable; deployed types without a detail screen explain themselves instead of navigating.
  • status joins the project router's supportedTuiCommands, so the agentcore project menu lists it above the "command line only" divider.

The harness hub (agentcore harness get → a harness) now ends with a linked resources divider and the same kind of tree, listing what GetHarness reports the harness is wired to:

  • runtime — the AgentCore Runtime the service provisioned under the harness, named by its id (the value every runtime command takes)
  • memorymanaged (managedMemoryConfiguration.arn) or attached (agentCoreMemoryConfiguration.arn); omitted when memory is absent or disabled
  • gateway rows for agentCoreGateway tools, with the outbound-auth oauth2 provider nested beneath (expanded by default)
  • browser / code interpreter rows for those tools, annotated aws default when the ARN is unset or owned by the aws account
  • api key rows for the model's apiKeyArn (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

  • Memory ↔ agent rule (commented on buildStatusNodes): every declared memory groups under every runtime agent, because the CDK L3 injects a MEMORY_<NAME>_ID env var for each declared memory into every runtime. A harness's memory ref lives in its own harness.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. Detail screens read the AWS region off the launch context — the ambient region, 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, falling back to the region the harness was fetched in), resolved by a shared useCoreOpts hook: a truthy region in the query string wins, otherwise the context's region applies as before. The two identity get screens switch from coreOptsFromCtx to useCoreOpts so 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.
  • Shared ARN helpers. serviceIdFromArn lives in src/core/arn.ts (tested) with resourceNameFromArn, regionFromArn and credentialProviderTypeFromArn — credential provider ARNs (…:token-vault/<vault>/apikeycredentialprovider/<name>) need the trailing name, and the api-key/oauth2 distinction comes from the ARN alone.
  • Generic slot. ResourceDetailScreen gains an optional linkedResources prop rendered through LinkedResourcesTree (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.
  • Focus hand-off. On the harness hub 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 returns to the last action (new onUpFromFirst prop on TreeView), enter acts on the focused zone, escape still goes back, and the marker only ever shows in one zone. Footer hints follow the zone (select vs open, ←→ collapse/expand only when the tree has nested rows).
  • Row style. TreeView gains focusMarker (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 refactor at 3fcf406 (conflicts resolved by hand); a follow-up commit routes the tree's focus marker through the glyphs table and spells the quit hint ctrl+c, matching the conhost and help-text changes that landed there.

  • bun test src — 3033 pass. The 11 failures are all in src/io/exec.test.ts, which spawns node, 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.
  • Tests: 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) and src/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 --json verified unchanged; stack torn down afterwards (DELETE_COMPLETE, no StatusE2E resources remain).

Harness hub — inventory (live). harness list --json in every region plus harness get --id … --json on each result: 19 harnesses — 13 in us-east-1, MyPDXHarness-rhkXkAE1IS in us-west-2, harness_demo-Rrs0v4OyAO in us-east-2, and MyFirstHarness-Ya6Rf9E905 / vpc-xV0snhWxb9 / vpc_test_2-owfavpaf0x in ap-southeast-2. Across them the linked resources are:

Row type Present on
runtime all 19
memory (managed) KnicksHarness-MFZ7GXmTYQ, inlineCliDemo-2poyWgSG0m, myCliHarness-up15WydSrZ
browser, ARN unset FunTimes-T2MFxkuezh, KnicksHarness-MFZ7GXmTYQ, asdf-diSdqKDM2s, myCliHarness-up15WydSrZ, myFunDemoHarness-uwjQ1a7oM7, testAgain-2B1451OLO2, wizardDemo-igTsHbt17M
browser, aws.browser.v1 ARN harness_stnea-VVc5FvKgtr, harness_ummrg-NC9CEPEP0A, harness_demo-Rrs0v4OyAO (us-east-2)
remote MCP tool (must not appear) harness_demo-Rrs0v4OyAO
memory (attached), gateway, oauth2 provider, code interpreter, api key none — no deployed harness carries them, and none was created

Harness 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-captured GetHarness responses verbatim, synthesises minimal GetAgentRuntime / GetMemory / GetGateway / credential-provider responses for the link targets, and logs the SigV4 signing region of every request. A synthetic-linked harness (the KnicksHarness capture 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 from update focuses the runtime row, further down moves within the tree, up twice returns to update, exactly one at every step; enter on runtime opens agentcore → runtime → get → harness_KnicksHarness-QDSgiYAteM (stub saw GET /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-VVc5FvKgtr and harness_demo-Rrs0v4OyAO (opened with --region us-east-2): browser aws.browser.v1 aws default; the remote MCP tool does not appear.
  • MyPDXHarness-rhkXkAE1IS opened with --region us-west-2: runtime row present; enter opens the runtime hub with the stub signing for us-west-2; escape returns.
  • synthetic-linked: all seven rows with the type column aligned and oauth2 provider github-oauth outbound auth nested 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 for us-west-2 from the ARN while the harness sat in us-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, pick KnicksHarness-MFZ7GXmTYQ, then the same steps as above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AMPY8yuo2z5Vhrmyte1Lk2
https://claude.ai/code/session_016KnHdxcPYTnQiDY7Y1PY6s

@github-actions github-actions Bot added the size/xl PR size: XL label Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Sep 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026

@agentcore-devx-automation agentcore-devx-automation Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 call navigate(action.to(runtimeId), { state: … }) without region. Only the invoke action sets state at all, and even then it's { returnOnEscape: true }, not { region }.
  • src/handlers/memory/get/screen.tsx:71 and src/handlers/harness/get/screen.tsx:78 — action onSelect handlers call navigate(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:

  1. Have every detail screen that owns an action list forward the current region from its location.state onto its navigate({ state }) calls. Simple and localized, but easy to forget for future screens.
  2. 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.
  3. 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 to ProjectGate ("the project's linked resources on target default") hardcodes default, but the TARGET_NAME = "default" constant is right above; consider interpolating it so the two can't drift. The Layout render below already uses status.data.target.name for the same string, so the gate/loading path is the only inconsistency. Not blocking.
  • src/handlers/project/status/screen.tsx:1685 — every memory in spec.memories is claimed by every runtime agent, but claim() still calls claimed.add on 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.

@agentcore-devx-automation agentcore-devx-automation Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Sep 4, 2026
@codecov-commenter

codecov-commenter commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.06%. Comparing base (3fcf406) to head (aa88158).
⚠️ Report is 1 commits behind head on refactor.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added size/l PR size: L and removed size/xl PR size: XL size/l PR size: L labels Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/l PR size: L size/xl PR size: XL labels Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@AlexanderRichey AlexanderRichey changed the title feat(tui): open a Linked Resources screen on bare project status feat(tui): Linked Resources on project status and the harness hub Sep 4, 2026
@AlexanderRichey
AlexanderRichey marked this pull request as ready for review September 4, 2026 18:31
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
AlexanderRichey and others added 2 commits September 4, 2026 18:33
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
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
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
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 4, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 4, 2026

@nborges-aws nborges-aws left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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} ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@AlexanderRichey
AlexanderRichey merged commit 63e24a7 into refactor Sep 4, 2026
22 of 24 checks passed
@AlexanderRichey
AlexanderRichey deleted the project-status-tui branch September 4, 2026 19:42

@jariy17 jariy17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Good.

@AlexanderRichey
AlexanderRichey restored the project-status-tui branch September 4, 2026 20:54
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 4, 2026
@AlexanderRichey
AlexanderRichey deleted the project-status-tui branch September 4, 2026 20:55
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants