Skip to content

feat(dev): wire the Agent Inspector into project dev - #2086

Open
tejaskash wants to merge 10 commits into
feat/inspector-agent-proxiesfrom
feat/inspector-wiring
Open

feat(dev): wire the Agent Inspector into project dev#2086
tejaskash wants to merge 10 commits into
feat/inspector-agent-proxiesfrom
feat/inspector-wiring

Conversation

@tejaskash

Copy link
Copy Markdown
Contributor

What

Third and final PR of the Agent Inspector stack (C1 #2082 → C2 #2085C3). C1 built the Inspector HTTP layer and C2 added the agent-proxy routes, both tested in isolation behind fake dependencies. This PR wires that layer into agentcore project dev and adds the two IO leaves it needs, so the Inspector is reachable from the CLI for the first time.

Changes

  • project dev is UI-by-default. New --ui flag (default true, so --no-ui is auto-generated) and --ui-port. In UI mode the handler resolves a UI port (explicit port must be free, else scans from 8081), starts the Inspector server via startHttpServer(createInspectorHandler({...})), hands the collector's TraceStore to it as InspectorTraces, watches agentcore.json to reload the supervised runtime set live, and opens the browser when interactive and not --json. --no-ui keeps the existing plain single-runtime log stream.
  • src/io/openBrowser.ts — best-effort detached browser launch (open / cmd start / xdg-open), never blocks or fails the command.
  • src/io/watchFile.ts — single-file watcher, debounced, closes on abort, fails quietly on a missing file or unsupported platform.
  • OtelCollector.store renamed to traces so the collector hands its store to the Inspector without the Inspector knowing the collector exists. DevTraceCollector gains the matching traces accessor.

Why the two IO primitives belong in src/io

The handler is a real second caller of both, and both are raw platform mechanics (child-process spawn, fs.watch) that the boundary rule keeps out of src/core and the handlers. They live beside checkPort, startHttpServer, and the other IO leaves.

Lifecycle

The Inspector server rides the one AbortController shared with the collector, supervisor, and watcher, so Ctrl-C tears everything down through one cancellation domain. The collector closes only after runners return, so an agent's final spans still reach it. The config watcher's onChange swallows parse errors deliberately: a half-saved config parses on the next change event.

Tests

  • project dev UI-mode suite: starts the Inspector and prints the URL, opens the browser only on a TTY and never in --json, /api/status lists every runtime with none started (lazy), agentcore.json edits reload the set, --agent narrows it, a taken --ui-port fails fast, --port with several runtimes is rejected.
  • watchFile suite: debounces a burst of edits to one callback and stops on abort, a missing file fails quietly.
  • All existing trace-collection and interruption tests kept, updated for the traces rename.
  • bun test full suite green, typecheck / lint:check / format:check clean.

Verification altitudes

  • Per layer: the suites above.
  • End to end: pending on this branch after review — build the CLI, real project with two runtimes, confirm the browser opens the Inspector, invocations stream, MCP/A2A/resources answer, config reload is live, --no-ui gives the plain stream, --json never opens a browser.

Stacked on #2085. Merge C1 → C2 → C3 in order.

@github-actions github-actions Bot added the size/l PR size: L label Aug 24, 2026
@github-actions github-actions Bot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress size/l PR size: L labels Aug 24, 2026
@github-actions github-actions Bot added the size/l PR size: L label Aug 24, 2026
@tejaskash
tejaskash force-pushed the feat/inspector-wiring branch from 2dfad97 to 7786d33 Compare August 25, 2026 17:35
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Aug 25, 2026
@tejaskash tejaskash changed the title feat(dev): wire the Agent Inspector into project dev (C3) feat(dev): wire the Agent Inspector into project dev Aug 25, 2026
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Aug 25, 2026
@tejaskash
tejaskash force-pushed the feat/inspector-wiring branch from 7786d33 to 35a3427 Compare August 25, 2026 17:45
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.48148% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.10%. Comparing base (d6abf97) to head (cf7416c).

Files with missing lines Patch % Lines
src/io/openBrowser.ts 11.76% 15 Missing ⚠️
src/handlers/project/dev/index.ts 89.70% 7 Missing ⚠️
src/handlers/project/index.ts 72.72% 3 Missing ⚠️
Additional details and impacted files
@@                       Coverage Diff                        @@
##           feat/inspector-agent-proxies    #2086      +/-   ##
================================================================
- Coverage                         97.19%   97.10%   -0.09%     
================================================================
  Files                               433      435       +2     
  Lines                             26489    26606     +117     
================================================================
+ Hits                              25747    25837      +90     
- Misses                              742      769      +27     

☔ 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.

@tejaskash
tejaskash force-pushed the feat/inspector-wiring branch from 35a3427 to 2d789c8 Compare August 25, 2026 19:01
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Aug 25, 2026
project dev without --agent now runs all of the project's runtimes at once:
a DevSupervisor owns per-agent lifecycle (sequential port resolution — a
concurrent race would put two agents on one port), merges every runner's
output into one agent-attributed stream ([name] prefixes; an agent field in
NDJSON), and keeps the session alive when one agent crashes. Selecting a
single runtime (--agent, or a one-runtime project) keeps the direct path
where a crash still fails the command.
…setup

A child that stays alive without ever binding its port previously blocked
every later runtime (starts are sequential) until interrupted; readiness now
gives up after 120s and fails that start. Setup failures before the pump
exists (port resolution, environment) now remove their parent-abort listener
like every other exit path, so Inspector retries of a failing agent cannot
accumulate listeners.
The #1980 rebase carried a single-runtime host check (runtime.build) into
the multi-agent dev handler, where the variable is the runtimes array. Bind
all interfaces when any selected runtime runs in a container.
…batch

events() spliced the queue, yielded each event, then installed its wake
callback and blocked. A push landing during the yields ran while wake was
undefined, so its wake was a no-op, and the newly installed waiter never
noticed the queued event until the next push or shutdown. Re-check the queue
inside the wait so a queued event resolves immediately.
Port the reference WebUIServer as a pure request to response handler the
dev command composes with io/startHttpServer. This lands the HTTP contract
and SPA delivery only; agent-proxy routes (invocations, MCP, A2A, resources)
and the CLI wiring follow in later PRs.

- security: loopback-only Host check (incl. IPv6 [::1]), server-side origin
  allowlist, X-Agentcore-Local on POSTs, CORS preflight, CSP on served HTML
- routes: GET /api/status, POST /api/start, GET /api/traces[/:id], static SPA
  with index.html fallback, graceful JSON 404 for everything else
- exact-match trace routing with decodeURIComponent :id extraction
- InspectorAssets reads the staged SPA through AssetSource with an
  AGENT_INSPECTOR_PATH override and node_modules fallback; raw filesystem and
  package resolution live in src/io/packagedAssets, keeping node:fs/node:module
  out of core/dev
- build stages @aws/agent-inspector/dist-assets into the asset tree before
  bundle and compile
- serve static assets over a zero-copy Buffer view of the cached bytes
- hoist the constant CORS headers to module scope; the per-request origin
  pick reduces to origin || primary now that the guard runs first
- drop the single-use InspectorAssetReader alias; inline read's signature
- hoist the asset TextEncoder to module scope
Add the Inspector routes that talk to a running agent or read the
project spec, extending the C1 route table:

- POST /invocations proxies HTTP, A2A, and AGUI agents, normalizing each
  into the SPA's data:<json> SSE contract. MCP agents get a clear error
  directing them to /api/mcp rather than being mis-proxied as HTTP.
- POST /api/mcp forwards a JSON-RPC body to the agent's /mcp endpoint and
  buffers the reply under a 10MB cap.
- GET /api/a2a/agent-card fetches the running agent's A2A card.
- GET /api/resources flattens the project spec into the resource graph.

Every upstream fetch carries the client's abort signal, and io/httpServer
streams async-iterable bodies with backpressure so a disconnect tears the
upstream request down on Node. A single session id threads through the
request header, agent body, and echoed x-session-id.

SSE parsing follows the framing rules (optional leading space, multi-line
data fields, blank-line event boundary) instead of a hardcoded slice.
- collapse invokeHttpAgent/invokeAguiAgent into a shared forwardInvocation
- single-return parseAgentEvent normalizing empty payloads to null
- hoist the SSE TextEncoder to module scope
- readCapped iterates over the shared iterateBody helper
- drop the a2aId counter; A2A message ids use randomUUID
- add the AGUI missing-prompt test
Make the Inspector reachable from the CLI. project dev now runs UI-by-default:
resolve a UI port, start the Inspector HTTP server, watch agentcore.json to
reload the supervised runtime set live, and open the browser when interactive
and not --json. --no-ui keeps the plain single-runtime log stream.

Add the two IO leaves the handler needs: openBrowser (best-effort detached
launch) and watchFile (debounced single-file watch, closes on abort). Expose
the collector's TraceStore to the Inspector by renaming OtelCollector.store to
traces so the store is handed over without the Inspector knowing the collector.

The Inspector server rides the one AbortController with the collector,
supervisor, and watcher, so Ctrl-C tears everything down through one
cancellation domain; the collector closes only after runners return so final
spans persist.
- Extract findFreePort in core/dev/port.ts; resolveDevPort delegates to it and
  the dev handler's UI port resolution reuses it, deleting the duplicated
  resolveUiPort helper and its UI_PORT_ATTEMPTS copy of MAX_PORT_ATTEMPTS.
- Drop the dead resolvePort ternary: the --port guard already rejects an
  explicit port with more than one runtime, so flags.port applies directly.
- Rewrite the config-watch closure as a linear async function.
- Add projectSpecPath/PROJECT_SPEC_RELATIVE_PATH in core/project/fsUtils.ts and
  route the manager and the watch target through it, so the watched file and
  the read file resolve from one source.
@tejaskash
tejaskash force-pushed the feat/inspector-wiring branch from 2d789c8 to cf7416c Compare August 25, 2026 19:39
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants