feat(dev): PR 2b — unified live trace: streamed route invocations, cancel, correlated Trace page (#600) - #666
Conversation
…Workbench trace (#600 PR 2 foundation)
…600 PR 2, lane T3) - McpSessionServiceOptions.trace?: TracePublisher; one createMcpSessionTraceSink per session, composed with the dev-log sink (composeMcpSessionTraceSinks, per-sink isolation) - mcp.request / mcp.response paired by JSON-RPC id with durationMs, mcp.notification, mcp.progress, mcp.logging, mcp.stderr (summary through safeDevWireText), mcp.session.started / mcp.session.closed; correlation mcpSessionId, mcpRequestId, routeId (tool:/prompt:), epochId, host, and _meta-lifted requestId / conversationId / sessionId / correlationId; href via applicationNodePath + ?session= - McpSessionFrameTraceEntry lifts id, method, meta (McpSessionTraceMeta) as optional fields - tools/call session route accepts correlationId and stamps _meta['agent-bundle/correlationId'] - tests: mcp-session-trace-publisher (unit), mcp-session-routes, mcp-session-service (fixture)
…race (#600 PR 2, lane T5) - trace-client.ts: TraceClient (replay/stream), ForegroundTraceClient, strict decoders for TraceEntry / TraceReplayGap / replay body (AB8249), and openTraceFeed with replay -> NDJSON stream -> back-off reconnect and a restart-from-zero path for a refused non-zero cursor. - trace-model.ts: merge by sequence (bounded 4096), union-find grouping over conversationId > sessionId > invocationId | executionId | runId | mcpSessionId:mcpRequestId > correlationId, nesting, span/status/headline, filters, facets, ?correlation= and /trace/<id> selection, formatting. - trace-page.tsx + trace-page.css: timeline with grouped rows, sticky filter bar, "N new" pill with scroll anchoring, empty state, detail drawer with correlation links and Open route. PR 1 stopgap over /api/routes/invocations removed. - workbench-location.ts: /trace/<entryId> and ?correlation=<id>. - project-client.ts: 'route.invocation' joins projectEventTypes; activity events no longer trigger a status refetch. - main.tsx: construct ForegroundTraceClient and pass it to TracePage. - docs/diagnostics.md: register AB8249. - Tests: trace-model, trace-client (decoders, stream, feed reconnect), trace-page (static render), workbench-location, project-client.
Generated hook wrappers of the dev plugin post a slim receipt — the kernel's EventTraceEvents, EventTraceExecution identity, resolved lineage keys, and the host/session/request ids the native payload names, never the payload — to the authenticated dev server's POST /api/trace/receipts, discovered through the dev install marker (.agent-bundle-dev.json -> <project>/.agent-bundle/hook-receipts.json) or AGENT_BUNDLE_DEV_TRACE_URL/AGENT_BUNDLE_DEV_TRACE_TOKEN for dev-server-spawned simulations. HookReceiptRoutes lowers each receipt to hook.received / hook.completed / hook.failed (+ session.started / session.ended) on the TraceHub. Diagnostics AB8247-AB8249. Wiring for T1 in LANE-NOTES.md.
# Conflicts: # packages/agent-bundle/src/dev/foreground-server.ts # packages/agent-bundle/src/dev/workbench-server.ts
…ches. The PR 1 .trace-table step is gone; the audiobook-curator e2e now covers populated groups, ?correlation= scoping, /trace/trc_n deep links, Open route, and a live POST without reload. Leaving Trace is a designed stream abort.
🦋 Changeset detectedLatest commit: ba29609 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
# Conflicts: # packages/agent-bundle/src/adapters/hook-contract.ts # packages/workbench/tests/support/workbench-acceptance.ts
commit: |
|
Round 1 self-review (Claude Fable 5.1 Thinking High) posted 11 findings against |
…ntime source in docs, shared loopback helper, abort check before invocation.started, remove has(), cancel reap test, tracer changeset note
…t records fall back to the log source
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Part of #600 — PR 2b: unified live trace. Follows PR 2a (#643,
3afcdc9d87). Host sessions (PR 3) are gated on the owner's answer on #600.What changes
One timeline for a development plugin, fed by what already runs — no second trace framework.
packages/agent-bundle/src/dev/trace/**):TraceHubis the bounded in-memory log the dev server publishesTraceEntryrecords into;GET /api/trace(replay,aftercursor) andGET /api/trace/stream(NDJSON live) read it. Publishers: route invocations (invocation.startedat admission,completed/failed/cancelledat the end, the kernelEventTraceEvents forwarded live from the child over the existing IPC), MCP session frames (mcp.request/response/notification, fromMcpSessionTraceLog), hook receipts from generated hook wrappers running underagent-bundle dev(POST /api/trace/receipts: loopback-only, bearer token, noOrigin, 16 KiB cap —AB8247–AB8249), dev-log warnings/errors with request-scoped correlation, project diagnostics. The round-1 reviewer'sruntimesource (MCP App runtime controller) was deleted again — nothing in the spec needed it. Absolute paths are redacted before an entry leaves the server. DiagnosticsAB8240–AB8243.AgentRenderEvent(shell/progress/replace/error/complete) over IPC as it happens;RouteInvocationServicekeeps the newest 256 render events per running invocation (one{ type: 'truncated' }marker; the Workbench reducer keeps the same bound) andsubscribe(id)replays then streamsrender/trace/final;cancel(id)aborts through the sameAbortSignalPR 2a's queue uses and ends the recordstatus: 'cancelled'with nooutcome(AB8256if the run completed first). A request the service rejects before admission (AB8232/AB8237–AB8239, disconnected client) leaves no stream record behind. HTTP:POST /api/routes/invocationswithstream: true→202+ running record,GET /api/routes/invocations/<id>/stream(SSE, ends afterfinal),POST /api/routes/invocations/<id>/cancel(202;409AB8256once final). Non-streamPOST(with or withoutstream: false) is unchanged; every PR 2a fixture still passes.route.invocationproject events now also fire at start with a running record, so the payload widens toRouteInvocationSummary | RunningRouteInvocation— the changeset isminorand tells consumers to narrow onstatus.stream: true, render the Suspense shell → replacements → complete as they arrive, show a Cancel button while running, and settle on the unchanged final envelope; the Trace page (packages/workbench/src/trace/**) groups entries by correlation, deep-links each entry (href) to the route workspace (?invocation=<id>) or MCP session, and survives reload. B2 deleted the Trace filter bar, facets, "N new" pill and scroll bookkeeping — nothing in the spec or an existing test used them.adapters/hook-contract.ts): the standalone/shared/preflight wrappers open anEventTracerand, only when the dev-server receipt env vars are present — or a dev-installed bundle's.agent-bundle/hook-receipts.jsonrecord names a dev server whosepidis still alive — post one receipt at exit (750 ms bound on hook exit latency, documented); merged with feat(events): pass preflight data to routes #664'spreflightdata threading (gate.datainexecutionInput,preflightin the rendered payload). Outsideagent-bundle devthe receipt is a no-op with no network.website/docs/{en,zh}/guide/development/workbench.mdx,reference/dev-server-http.mdx,reference/runtime-environment.mdx,guide/authoring/hooks.mdx,docs/diagnostics.md. One changeset (.changeset/wb600-live-trace.md).Lanes: B0 rebase onto PR 2a, B1 live transport, B2 acceptance + deletions, B3
origin/mainmerge (#664 semantic merge inhook-contract.ts) + deslop, B4 round-1 review fixes. Lane notes are summarised here; the branch carries none.Acceptance (the spec)
runningwith entries before completionaudiobook-curator.acceptance.e2e.test.ts(live progress block: running badge, Cancel visible, busy non-empty shell document,invocation.startedrow beforesucceeded)cancelled, the child is gone, Trace shows it, nosucceeded/outcomecancelledbadge, no outcome,invocation.cancelledrow,GET /api/routes/invocations/<id>→status: 'cancelled', nooutcome);route-invocation-service.test.tscancel cases;route-invocation-dev-server.test.ts/cancel→ 202 thenfinalcancelledroute-invocation-service.test.ts"retains only the newest 256 render events and one truncation marker" (300 events → newest 256, first retained sequence 44, exactly one{ type: 'truncated' }, final document intact);packages/workbench/tests/invocation-model.test.tsasserts the browser reducer's 256 boundRouteInvocationenvelope PR 2a producesaudiobook-curator.acceptance.e2e.test.ts(final-envelope block: status, outcome, first provider name/status, first timing phase/duration compared between the API envelope and the workspace tabs)expectHeading(#632) throughout?invocation=<id>survives reload with status/outcome/correlation; Trace detail survives reload; "Open route" returns to the same snapshot); every heading throughexpectHeadingtools/callfrom the MCP session workspace and a hook receipt for the same session on one timeline under one correlation idtools/call search_audiblefrom the MCP session workspace + authenticated hook receipt carrying that session id in one Trace group);trace-model.test.tscross-source grouping;trace-dev-server.test.ts,hook-receipt-pipe.test.tsGates (local merge gate, AGENTS.md #662/#671)
Branch contains
origin/mainat26293c2354(#672). Run on686fa7a612(the whole pool), then re-run on headba296091dffor the two files the last commit touched:The PR's own CI run on the old head
aebe0429d0finished red onVerify (integration-2, Node 24):route-register-typegen.test.ts"Type 'false' does not satisfy the constraint 'true'" — the #664preflightwidening ofAgentEventRouteProps, fixed onmainby #670 (72a035a3fe), which that head predated. The branch contains #670 and the test passes in the whole-pool run above.Deslop
Deslop: GPT-5.6 Sol (lane B3), 13 edits, plus Claude Fable 5.1 (integrator, round 2–3), 5 edits:
RouteInvocationService.has()andtraceJoinKeysun-exported/deleted as test-only,dev-lock.tsrewired tocore/loopback-origin.tsinstead of its byte-identical copy, two deadruntime.eventmirror entries and the stale runtimecorrelationIdprose removed. B3 — one changeset instead of two;route-invocation-routes.tsreusesdev/http.tsnoQuery/badRequestinstead of local copies; lane/PR-history and restating comments removed fromhook-receipt-endpoint.ts,hook-receipts.ts,events/trace-receipt.ts,trace-entry.ts,trace-client.ts,trace-model.ts,trace-page.tsx;dev-server-http.mdxen+zh gained the stream/cancel endpoints; speculative host-session prose removed fromworkbench.mdxen+zh. Lane B2 deleted the Trace filter bar, facets, "N new" pill and scroll bookkeeping plus their CSS/tests and an unused test id.Self-review
Three rounds, Claude Fable 5.1 Thinking High each, read-only against the branch diff vs
origin/main.Round 1 (
aebe0429d0) — 11 findings, fixed by lane B4 (fe06da0891) unless noted:invoke()errors → record deleted before rethrow (route-invocation-service.ts); test inroute-invocation-service.test.ts.stream: falserejected as malformed → plain POST parsesrequestBody(route-invocation-routes.ts); test inroute-invocation-dev-server.test.ts.cancel()racing completion returnedsucceededwith 202 →AB8256unless the awaited record iscancelled.pid; wrapper ignores a record whose pid is dead; 750 ms bound documented en/zh.truncated.droppedstale for live subscribers → wire marker is{ type: 'truncated' }only.invocation-model.ts); test.mcpSessionIdvalue and the fixture supplies the id — dismissed: the join key is what the MCP session publisher and the hook receipt both carry; a host-launched Claude session id will need its own key in PR 3, which is exactly the "PTYs reuse this correlation" clause of the spec.RouteInvocationEventPayload.invocationwidened → changesetminor, docs tell consumers to narrow onstatus.hook-receipts.ts,events/project.ts,trace-model.ts,RouteInvocationRouteService.has,now/timeoutMs/tokenoptions).runtimetrace source not needed by the spec → deleted with its controller/protocol/MCP App changes;loganddiagnostickept.requestIdlisted for invocation correlation → removed from the docs row.Round 2 (
632d18020d) — 7 findings, fixed by the integrator (686fa7a612):runIdcopied intoTraceCorrelationby the dev-log lowering while the type/decoder had none → Trace page would failAB8243once an eval run logged. Fixed by deletingrunIdfrom the lowering (not by widening the type):traceRequestContextKeys,traceCorrelationFor,traceHrefFor, docs en/zh.runtimesource → removed en/zh.core/loopback-origin.tswas a copy ofdev-lock.ts's helper, never rewired →dev-lock.tsimports it, copy deleted.createEventTracerwithoutobservernow reads the process-local observer live (was permanently disabled) — public behavior not in the changeset → sentence added;guide/authoring/hooks.mdxen/zh updated.runningtrace entry when a plain-POST client disconnected during#prepared()→admissionSignal.throwIfAborted()moved above theinvocation.startedpublish.RouteInvocationService.has()test-only → deleted.reaps the render child and its descendants when the invocation is cancelledadded beside the success/timeout/close reap tests.Round 3 (
686fa7a612) — verified all seven; 2 residual findings, fixed (ba296091df): the newdev-server-http.mdxpage still claimedPOST /api/runtime/runsaccepts acorrelationIdcopied onto trace entries (deleted with the runtime source) → sentences removed en/zh;project:runtime.event/diagnostic:runtime.event.diagnosticsat in the "already lowered by a dedicated producer" mirror set although nothing lowers them → entries removed so runtime failures fall back to thelogsource like any other record. Round 3 nit not taken: theBuffer.prototype.toStringspy predicting an invocation id in one service test (brittle, not a merge risk).Trace correlation sources / security posture
Correlation sources on the timeline: route invocations (
RouteInvocationService→TraceHub, kernelEventTraceEvents from the child), MCP session frames (McpSessionTraceLogpublisher,mcpSessionIdas a join key), hook receipts from generated wrappers (POST /api/trace/receipts), dev-log warnings/errors with request-scoped correlation, project diagnostics. Not on it: host PTY sessions (PR 3, pending the owner's answer on #600).Security posture of the receipt endpoint: loopback peer only, bearer token minted per dev-server run and passed to the wrapper through the environment, requests with an
Originheader refused (AB8247), strict body shape and 16 KiB cap (AB8248/AB8249), absolute paths redacted before an entry is stored. The only file the endpoint writes is<projectRoot>/.agent-bundle/hook-receipts.json(mode 0600:pid, token, loopback URL) so hooks a host launches from a dev-installed bundle can find the running dev server; it is removed on shutdown and a wrapper ignores it when the recordedpidis dead, so a crashed server's token is never posted to whatever later binds the port. Outsideagent-bundle devthe wrapper's receipt is a no-op with no network.