feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(dev): agent-proxy routes for the Agent Inspector - #2085

Merged
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies
Aug 26, 2026
Merged

feat(dev): agent-proxy routes for the Agent Inspector#2085
tejaskash merged 5 commits into
refactorfrom
feat/inspector-agent-proxies

Conversation

@tejaskash

@tejaskashtejaskash commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Agent Inspector routes that talk to a running agent or read the project spec. This layer is still a pure request-to-response handler and is not reachable from the CLI yet. The project dev wiring lands in #2086.

Routes

  • POST /invocations — protocol-aware proxy. HTTP, A2A, and AGUI agents are each normalized into the SPA's data: <json> SSE contract. MCP returns a clear error pointing at /api/mcp rather than being proxied as HTTP.
  • POST /api/mcp — forwards a JSON-RPC body to the agent's /mcp endpoint, buffering the reply under a 10MB cap.
  • GET /api/a2a/agent-card — fetches the running agent's A2A card.
  • GET /api/resources — flattens project.spec into the resource graph the SPA renders.

Tests

Every route is unit tested behind fake deps: invocation routing and SSE normalization (including A2A dedup and the non-streaming fallback), the MCP forward and its size cap, the agent-card paths, the resource graph, and httpServer's streaming and abort handling.

bun test, typecheck, lint:check, format:check all green.

@github-actionsgithub-actionsBot added the size/xl PR size: XL label Aug 24, 2026
@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 24, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 24, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from fcff328 to 013ffe2CompareAugust 25, 2026 17:35
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routes (C2)feat(dev): Agent Inspector agent-proxy routesAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 013ffe2 to 5e98a35CompareAugust 25, 2026 17:45
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.09707% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.39%. Comparing base (097e1f0) to head (279c0da).
⚠️ Report is 2 commits behind head on refactor.

Files with missing linesPatch %Lines
src/core/dev/inspector/proxies.ts94.28%4 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## refactor #2085 +/- ##
============================================
+ Coverage 97.38% 97.39% +0.01% 
============================================
Files 440 449 +9 Lines 26626 27474 +848 ============================================
+ Hits 25929 26759 +830 - Misses 697 715 +18 

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

@tejaskashtejaskash changed the title feat(dev): Agent Inspector agent-proxy routesfeat(dev): agent-proxy routes for the Agent InspectorAug 25, 2026
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5e98a35 to 5cfa15fCompareAugust 25, 2026 19:01
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 25, 2026
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from 5cfa15f to d6abf97CompareAugust 25, 2026 19:39
Base automatically changed from feat/inspector-http-layer to refactorAugust 26, 2026 15:05
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch 2 times, most recently from 6f5b3a3 to 0a6bec4CompareAugust 26, 2026 15:09
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
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
Delete JSDoc and inline notes that narrate what the code already shows.
Keep only one-line notes for non-obvious reasoning (security guards,
cross-runtime disconnect behavior, wire-contract field names, SSE framing).
…atch
- Inline the single-caller invokeHttpAgent wrapper into handleInvocations.
- Make invokeAguiAgent async so its guard returns apiError directly.
- Return kind from extractSseEventText instead of re-deriving it in a
separate isStatusUpdateEvent pass.
- Collapse the A2A part/artifact accumulator loops into filter/map/join.
@tejaskash
tejaskashforce-pushed the feat/inspector-agent-proxies branch from d66062a to 0c0f90fCompareAugust 26, 2026 18:26
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026
- Populate the resources fixture with one of every resource type so each
wire-shaping map callback is exercised (was 55% covered).
- Add invocation unhappy paths: upstream 502s, parseAgentEvent null frames,
A2A artifact/task extraction, non-streaming and non-JSON fallbacks.
- Cover httpServer backpressure drain and the post-headers stream error.
@github-actionsgithub-actionsBot added size/xl PR size: XL and removed size/xl PR size: XL labels Aug 26, 2026

@HweinstockHweinstock 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.

LGTM! some small suggestions/questions but no blockers.

return json(200, status);
}

/** POST /api/start — start an agent on demand; concurrent starts share one attempt. */

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.

q: were these removed on purpose?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes, on purpose. Those route JSDocs just restated the method/path already visible in the handler, so the comment-cleanup pass dropped them per the no-restating-the-code guideline. The security and wire-contract comments stayed.

body,
signal,
});
} catch (error) {

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.

would there be a benefit to wire the logger here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

There's no logger in this layer today (the dev handler surfaces status via renderStatus and lets errors propagate). The upstream failure already reaches the user as the 502 body in the Inspector, so I left it. Happy to add structured logging if we introduce a logger dep for the dev command more broadly.

// Handles bedrock {text}, {error}, ConverseStream contentBlockDelta, bare JSON string, and non-JSON tokens.
export function parseAgentEvent(data: string): string | { error: string } | null {
try {
const parsed: unknown = JSON.parse(data);

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.

would it simplify this code to use a zod schema that we parse with?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I looked at it — zod does not buy much here. parseAgentEvent takes loosely-typed passthrough tokens from arbitrary agent runtimes, handles several shapes ({text}, {error}, ConverseStream delta, bare string) AND a non-JSON fallback that returns the raw token. A zod union would still need the try/catch + raw fallback + empty-to-null glue, so the imperative form stays clearer. Left as is.

}

// When streamedFromStatus is set, artifact-update text is skipped because status-update already streamed it.
function extractSseEventText(

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.

should this function signature / name mention that its A2A specific?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — renamed to extractA2aEventText in #2086 (0791591). It only handles A2A artifact/status/task kinds, so the generic SSE name was misleading.

expect(await response.text()).toBe(`data: ${JSON.stringify({ error: "boom" })}\n\n`);
});

test("passes a non-SSE response body through untouched", async () => {

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.

i like how readable these tests are.

Comment threadsrc/io/httpServer.ts
): Promise<void> {
for await (const chunk of body) {
if (signal.aborted) break;
if (!response.write(chunk)) await drain(response, signal);

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.

nice and simple!

@tejaskash
tejaskash merged commit 9ab30d3 into refactorAug 26, 2026
16 checks passed
@tejaskash
tejaskash deleted the feat/inspector-agent-proxies branch August 26, 2026 21:02
tejaskash added a commit that referenced this pull request Aug 26, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
@agentcore-devx-automationagentcore-devx-automationBot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

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

@agentcore-devx-automationagentcore-devx-automationBot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
tejaskash added a commit that referenced this pull request Aug 27, 2026
* feat(dev): wire the Agent Inspector into project dev (C3)
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.
* refactor(dev): apply /simplify cleanup to the Inspector wiring
- 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.
* feat(dev): --no-ui requires an explicit --agent
Without the UI there is no lazy per-agent start, so a multi-runtime
project must name which one streams to the terminal.
* refactor(inspector): name the A2A event extractor for its protocol
Rename extractSseEventText to extractA2aEventText; it only handles A2A
artifact/status/task event kinds, so the generic SSE name misled.
Addresses review feedback on #2085.
* feat(dev): replace --ui/--no-ui with a --mode enum
browser (default, Agent Inspector), headless (one agent in the terminal),
and tui as a reserved value for the planned terminal UI. Clearer than a
boolean as more modes arrive. Addresses review feedback on #2086.
* refactor(dev): inject the project manager instead of a reload closure
The dev handler took a bespoke reloadRuntimes closure; inject the project
manager (narrowed to resolve) like the sibling handlers do, and re-resolve
on config change. Addresses review feedback on #2086.
* fix(dev): hold live-agent edits until restart and await pumps on shutdown
setRuntimes no longer overwrites a running or starting agent's definition,
so the Inspector never proxies it with metadata that no longer matches the
child; the edit is applied on the agent's next start. events() now waits for
every live child's pump before ending, so an agent's final spans reach the
collector before shutdown closes it. Addresses review feedback on #2086.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xlPR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tejaskash@codecov-commenter@Hweinstock