feat(dev): wire the Agent Inspector into project dev - #2086
Open
tejaskash wants to merge 10 commits into
Open
Conversation
tejaskash
had a problem deploying
to
e2e-testing
August 24, 2026 17:49 — with
GitHub Actions
Failure
tejaskash
had a problem deploying
to
e2e-testing
August 24, 2026 17:57 — with
GitHub Actions
Failure
tejaskash
force-pushed
the
feat/inspector-wiring
branch
from
August 25, 2026 17:35
2dfad97 to
7786d33
Compare
tejaskash
had a problem deploying
to
e2e-testing
August 25, 2026 17:36 — with
GitHub Actions
Failure
tejaskash
force-pushed
the
feat/inspector-wiring
branch
from
August 25, 2026 17:45
7786d33 to
35a3427
Compare
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
tejaskash
force-pushed
the
feat/inspector-wiring
branch
from
August 25, 2026 19:01
35a3427 to
2d789c8
Compare
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
force-pushed
the
feat/inspector-wiring
branch
from
August 25, 2026 19:39
2d789c8 to
cf7416c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Third and final PR of the Agent Inspector stack (C1 #2082 → C2 #2085 → C3). 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 devand adds the two IO leaves it needs, so the Inspector is reachable from the CLI for the first time.Changes
project devis UI-by-default. New--uiflag (default true, so--no-uiis 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 viastartHttpServer(createInspectorHandler({...})), hands the collector'sTraceStoreto it asInspectorTraces, watchesagentcore.jsonto reload the supervised runtime set live, and opens the browser when interactive and not--json.--no-uikeeps 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.storerenamed totracesso the collector hands its store to the Inspector without the Inspector knowing the collector exists.DevTraceCollectorgains the matchingtracesaccessor.Why the two IO primitives belong in
src/ioThe 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 ofsrc/coreand the handlers. They live besidecheckPort,startHttpServer, and the other IO leaves.Lifecycle
The Inspector server rides the one
AbortControllershared 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'sonChangeswallows parse errors deliberately: a half-saved config parses on the next change event.Tests
project devUI-mode suite: starts the Inspector and prints the URL, opens the browser only on a TTY and never in--json,/api/statuslists every runtime with none started (lazy),agentcore.jsonedits reload the set,--agentnarrows it, a taken--ui-portfails fast,--portwith several runtimes is rejected.watchFilesuite: debounces a burst of edits to one callback and stops on abort, a missing file fails quietly.tracesrename.bun testfull suite green, typecheck / lint:check / format:check clean.Verification altitudes
--no-uigives the plain stream,--jsonnever opens a browser.Stacked on #2085. Merge C1 → C2 → C3 in order.