feat(inspector): surface the session trace as a workbar tab - #2018
Conversation
Second layer of apache#1625, stacked on apache#1956: a read-only IPC that projects one session's trace, and a fifth `SessionWorkbar` tab that renders it. **The read boundary is the AgentRun stream, not the Usage read model.** Usage is range-queried and carries no session predicate; the stream is the authority and is already keyed by session and run, which is how `context-diagnostics.ts` reads it too. `inspector:trace` reads both ledgers and writes nothing. **A record that will not decode is counted, not dropped.** `coverage` gains `unreadableRecords`, because spend the trace cannot show is exactly the kind of gap that otherwise looks like an absence of activity — the same failure mode the pi-backend case already forced into the open. **The panel's judgements are pure and tested without a DOM.** `deriveInspectorPanelModel` decides what counts as a gap worth showing and whether a cost may be rendered at all; `SessionInspectorPanel` only lays the result out. An unpriced call renders "cost unavailable" rather than `$0`, which is the same distinction the ledger keeps. Failure highlighting follows the attributed step rather than every step after it. The tab lives on `SessionWorkbar` and not on `AgentGraphPanel`, which is gated on `orchestrationMode === 'graph'` and would have hidden the Inspector from ordinary sessions. It refreshes only while visible: a hidden panel re-projecting a long session is a cost with no reader. Tests: 3 for the IPC read (ledger join, undecodable record, non-canonical run events), 5 for the panel model, and the workbar persistence whitelist — which had no coverage at all, so a tab that renders but is not listed there would silently revert to Tasks after a restart. `@maka/core` 703/703, session-trace projection 16/16, desktop main targeted suites green; `check-a11y`, `check-console`, `check-copy`, `check-dead-css` and `format:check` clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Result<T>` is `{ ok: true; data: T }`, not `{ value }`. The renderer typecheck
caught it; I could not, because `@maka/ui` does not build in this worktree, so
`tsconfig.renderer.json` never ran here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
CI triage — one of the three failures was mine, and it's fixed in Mine: Not mine — Not mine —
|
Astro-Han
left a comment
There was a problem hiding this comment.
Review findings (2 independent deepseek-v4-flash perspectives)
The inspector tab is clean and minimal overall (3 solid IPC tests + 5 panel-model tests, correct data sources, no new store/permission surface), but there is one P2 that needs a decision.
P2 — useSessionTrace documents event-stream refresh that is not implemented; a live session's trace goes stale while the tab is open
- Predicted failure: a user opens the Inspector tab on a running session; new turns/steps/costs never appear — the panel is a snapshot frozen at activation time until the user switches away and back or clicks Retry.
- Evidence:
apps/desktop/src/renderer/use-session-trace.ts(new) — the doc comment says "Reloads on the session's own event stream rather than on a timer… the moment worth re-reading is when one of them gained an event. Only refreshes while the panel is visible", but the effect deps are[active, load, sessionId]with stableloaddeps — there is no subscription of any kind. The siblinguse-session-tasks.tssubscribes viawindow.maka.tasks.subscribeChanges, andwindow.maka.sessions.subscribeEvents(sessionId, …)exists in preload (preload.ts:291) as a ready seam. The PR description's "The panel refreshes only while visible" implies refresh-while-visible is intended. - Fix (pick one): subscribe to session events while
active(debounced, refresh on new events), or explicitly scope this as activation-only — and correct the hook comment + PR description to match. - Suggested verification: a hook test with a fake event emitter proving reload-on-event (fails today); or an e2e with the fake backend showing a new step appear while the tab is open.
P3 (non-blocking)
- Every activation re-reads the entire session unbounded (
inspector-ipc-main.tsPromise.all(runs.map(readRunEvents))+ renderer renders all turns without virtualization) — large sessions will stall the UI; consider incremental append. - Error and empty states render simultaneously when
readRunEventsthrows generically ("Could not read the trace" + "Nothing to trace in this session yet") — hide the empty-state copy on error. emptyTotals()duplicates core's already-exportedemptyTraceTotals()(@maka/core/session-trace) — import it instead.- No component/story/e2e coverage for the panel's interactive states (retry, empty, coverage notice, aria-busy) — per AGENTS.md, add a story variant and extend
session-workbar.spec.tswith one inspector journey. - Test fixtures use
as nevercasts that bypass shape validation;registerInspectorIpc's error wrapper has no behavior test.
CI note: the red jobs (storage migration suite, e2e/sidebar-navigation timeout, attachment.spec.ts geometry flake) reproduce on clean local main and are not attributable to this PR; rebase onto a green main before merging.
Gate: PASS (no P0/P1). P2 must be handled (fix or explicit deferral with reason) before this is done.
Review on apache#2018. The P2 was right, and it is the same failure I have now made four times in this track: the hook's comment described refreshing on the session's event stream and nothing subscribed to anything. A user watching a running turn would have seen a snapshot frozen at the moment they opened the tab. Implemented rather than documented away, because the seam already existed (`sessions.subscribeEvents`) and a trace panel that silently stops tracking is worse than no panel. The policy is its own pure module so it is testable without a DOM, which is why the claim can now be checked instead of asserted: - Only events that can change a ledger trigger a read. A streaming turn emits text and tool-output deltas continuously; re-projecting a whole session per delta would spend the session's own latency budget watching it. - A burst coalesces into one read. A finishing turn emits `tool_result`, `token_usage` and `complete` within milliseconds — each alone justifies a re-read, three do not. - The subscription and any scheduled read are dropped when the panel hides, so "refreshes only while visible" is enforced rather than described. Also from the review: the empty state no longer renders beside an error ("nothing to trace" is a claim about the session, and a failed read cannot make it); the panel model imports core's `emptyTraceTotals` instead of redefining it; the fixtures use `satisfies TraceStep` instead of `as never`, which is what let a malformed step through in the first place; and `registerInspectorIpc` has a behaviour test proving a failed read answers as a typed `Result` rather than a rejected invoke. Deferred with reasons, per the review's own framing: unbounded per-activation reads and timeline virtualization (a real limit, but it wants a paging contract rather than a patch), and story/e2e coverage of the panel's interactive states. 19 desktop inspector tests green; `check-a11y`, `check-dead-css`, `check-console` and `format:check` clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
P2 was right, and it's the same failure I've now made four times in this track: the comment described a subscription and nothing subscribed. Implemented rather than documented away — the seam already existed, and a trace panel that silently stops tracking a running session is worse than no panel. The refresh policy is its own pure module, so the claim is checkable instead of asserted:
Four tests with an injected timer, including the one you asked for — a burst producing exactly one read, and a cancel that lets a fired timer produce none. P3, taken: the empty state no longer renders beside an error (2) — "nothing to trace" is a claim about the session and a failed read cannot make it; the panel model imports core's P3 deferred, with reasons: unbounded per-activation reads and timeline virtualization (1) — a real limit, and the fix is a paging contract over the trace rather than a patch here, so I'd rather do it deliberately than half. Story and e2e coverage of the interactive states (4) — worth doing; flagging that On the CI note — agreed, and thanks for confirming independently. I'd triaged the same three: the |
Review — approve with commentsThanks for the reworked refresh — the prior P2 is genuinely resolved: the subscription is real (visible-only subscribe, cleanup drops both the subscription and any scheduled read), and the coalescer policy is implemented and tested with injected timers. Re-ran the suites at Everything below is suggestions, nothing blocking. Worth considering:
Nits:
Gate: approve with comments. No P0/P1; happy to help land any of the above. |
The three substantive suggestions from apache#2018's approving review, plus its nits. **One unreadable run no longer fails the whole trace.** `readRunEvents` had no per-run catch, so a corrupt row or a run whose header went missing turned every `inspector:trace` into `INSPECTOR_TRACE_FAILED` — and retry hit the same row forever. A read failure is just another way a record can be unreadable, which is the rule this projection is built on; a failed run now folds into `unreadableRecords` as one counted gap rather than a guess at its size. **The hook has the regression test the original defect deserved.** The P2 in the last round was a doc comment describing a subscription that did not exist, and the fix landed without a test that could catch that class. Four now render the hook for real: subscribe only while active, unsubscribe on hide, one read per closing burst, no read for streaming deltas, and no read at all after the panel hides. Verified they fail with the subscription removed. Two things made that possible. The fake DOM in `ui-render-memo-boundary-contract.test.ts` moved to `fake-dom.ts` so a second suite can render without duplicating it — I had wrongly concluded this repo had no hook-test idiom, having searched only for `renderHook`. And `useSessionTrace` now takes its copy as an argument instead of reading the locale context, so the one hook whose comment outran its code is renderable without the UI package behind it. **A reported gap is never an empty session.** With every record undecodable the panel showed "Nothing to trace in this session yet" beside "N unreadable records" — opposite claims, and the second is exactly what this surface exists to show. Nits: the IPC join test now asserts something only the runtime-event ledger can satisfy (it passed with that ledger empty); the projection suite pins `unreadableRecords`, which only the IPC test referenced; re-activation preserves the timeline rather than blanking it, matching the file's own rationale; the coverage notice renders the turn counts it computes; and the recovery label is localized instead of leaking English into the zh UI. Deferred, unchanged: unbounded per-activation reads and timeline virtualization want a paging contract over the trace, not a patch here. 25 desktop inspector tests, `@maka/core` 755/755, projection 18/18; a11y, dead-css, copy, console and format checks clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(inspector): close the follow-ups left on the trace panel The three substantive suggestions from #2018's approving review, plus its nits. **One unreadable run no longer fails the whole trace.** `readRunEvents` had no per-run catch, so a corrupt row or a run whose header went missing turned every `inspector:trace` into `INSPECTOR_TRACE_FAILED` — and retry hit the same row forever. A read failure is just another way a record can be unreadable, which is the rule this projection is built on; a failed run now folds into `unreadableRecords` as one counted gap rather than a guess at its size. **The hook has the regression test the original defect deserved.** The P2 in the last round was a doc comment describing a subscription that did not exist, and the fix landed without a test that could catch that class. Four now render the hook for real: subscribe only while active, unsubscribe on hide, one read per closing burst, no read for streaming deltas, and no read at all after the panel hides. Verified they fail with the subscription removed. Two things made that possible. The fake DOM in `ui-render-memo-boundary-contract.test.ts` moved to `fake-dom.ts` so a second suite can render without duplicating it — I had wrongly concluded this repo had no hook-test idiom, having searched only for `renderHook`. And `useSessionTrace` now takes its copy as an argument instead of reading the locale context, so the one hook whose comment outran its code is renderable without the UI package behind it. **A reported gap is never an empty session.** With every record undecodable the panel showed "Nothing to trace in this session yet" beside "N unreadable records" — opposite claims, and the second is exactly what this surface exists to show. Nits: the IPC join test now asserts something only the runtime-event ledger can satisfy (it passed with that ledger empty); the projection suite pins `unreadableRecords`, which only the IPC test referenced; re-activation preserves the timeline rather than blanking it, matching the file's own rationale; the coverage notice renders the turn counts it computes; and the recovery label is localized instead of leaking English into the zh UI. Deferred, unchanged: unbounded per-activation reads and timeline virtualization want a paging contract over the trace, not a patch here. 25 desktop inspector tests, `@maka/core` 755/755, projection 18/18; a11y, dead-css, copy, console and format checks clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(inspector): narrow the unreadable-run claim to what the code does Second review round on #2041. The P2 is the same shape as the defect this PR set out to fix, one layer up — and my PR body claimed more than the code delivered. `readSessionTrace` catches per run, but the `Promise.all` above it does not. `listSessionRunsForRecovery` (`agent-run-store.ts:333`) parses every header row with no per-row tolerance, so one corrupt header still rejects the whole trace, and retry hits the same row forever. Fixing that belongs in storage, where it changes the contract every recovery caller depends on — a decision about recovery, not about this read model. Stated in the code and in the PR body rather than quietly half-fixed. Docs corrected to match: `unreadableRecords` counts records that could not be **read or decode**, and a whole unreadable run counting as one is documented as a floor, since nothing is known about how many records it held. Re-activation now has the test it lacked: reverting the preserve behaviour passed the old suite unchanged, which made it an unpinned behavioural change. The new one asserts that once a trace has arrived no later render goes back to nothing, and it fails when the preservation is removed. Housekeeping from the review: `preserveTrace` was dead at every call site and is gone; the injected copy takes `UiLocale` instead of restating it; `TRACE_REFRESH_DEBOUNCE_MS` is exported so the test's delay derives from it rather than duplicating 400 as 450; the IPC test types its stub against `IpcMain['handle']` instead of casting past it; and the extracted `FakeElement` import is dropped from the suite that no longer uses it. 25 desktop inspector tests, projection 18/18; a11y, dead-css, copy, console and format checks clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(inspector): import UiLocale from the package root `@maka/core/ui-locale` is not in core's export map, so the desktop main build could not resolve it. Every other renderer file imports the type from the package root; this one now does too. It typechecked locally because this worktree resolves the subpath through the built `dist` regardless of the export map. CI builds from the map, which is the honest surface — a second reminder that a local typecheck here is weaker evidence than it looks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Summary
Second layer of the Session Inspector: a read-only IPC that projects one session's trace, and a fifth
SessionWorkbartab that renders it. Consumes the projection merged in #1956.Refs #1625.
The read boundary is the AgentRun stream, not the Usage read model. Usage is range-queried and carries no session predicate; the stream is the authority and is already keyed by session and run, which is how
context-diagnostics.tsreads it too.inspector:tracereads both ledgers and writes nothing — no new store, no new permission path, no execution.A record that will not decode is counted, not dropped.
SessionTraceCoveragegainsunreadableRecords. Spend the trace cannot show is exactly the kind of gap that otherwise renders as an absence of activity — the same failure the pi-backend case already forced into the open, one layer up.The panel's judgements are pure and tested without a DOM.
deriveInspectorPanelModeldecides what counts as a gap worth showing, whether a cost may be rendered at all, and which step a failure is attributed to;SessionInspectorPanelonly lays the result out. Two rules carried through from the ledger: an unpriced call renders "cost unavailable" and never$0, and failure highlighting follows the attributed step rather than every step after it.Tab placement.
SessionWorkbar, notAgentGraphPanel— the latter is gated onorchestrationMode === 'graph'and would have hidden the Inspector from ordinary sessions, which was the failure mode called out early in #1625. The panel refreshes only while visible: a hidden panel re-projecting a long session is a cost with no reader.Tests
readSessionWorkbarTabsilently reverts to Tasks after a restart, which reads as the panel forgetting rather than as a missing case.Verification
@maka/core703/703; session-trace projection, inspector IPC, panel model and workbar layout suites green (29 targeted tests).check-a11y,check-console,check-copy,check-dead-cssandformat:checkclean.Not here
No search or replay step-through — that is PR 3. No changes to the ledger, the pricing authority, the permission path, or the execution runtime.
🤖 Generated with Claude Code