feat(ui): finding → analysis trace entry points and the agent-trace viewer - #2082
feat(ui): finding → analysis trace entry points and the agent-trace viewer#2082GabrielDrapor wants to merge 24 commits into
Conversation
761ed0b to
861f455
Compare
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
There was a problem hiding this comment.
2 issues found and verified against the latest diff
Confidence score: 3/5
- In
frontend/ui/src/features/ai-assistant/components/message-list.tsx, streamed assistant messages do not receive the finaltraceIdortraceStatus, so the completed turn lacks itsView tracelink; handle the finaltraceSSE event and annotate the current message. - In
frontend/ui/src/features/ai-assistant/components/ai-assistant-panel.tsx, tool-stepOpen spanclicks storespanIdbut pass onlytraceIdto the sheet, so the viewer opens at the trace root instead of the requested span; threadspanIdthroughAgentTraceSh….
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/ui/src/features/ai-assistant/components/message-list.tsx">
<violation number="1" location="frontend/ui/src/features/ai-assistant/components/message-list.tsx:487">
P2: After a streamed turn completes, the new `View trace` link is absent because the live message never receives `traceId` or `traceStatus`. Handle the final `trace` SSE event and annotate the current assistant message, or reload the session messages when the run finishes.</violation>
</file>
<file name="frontend/ui/src/features/ai-assistant/components/ai-assistant-panel.tsx">
<violation number="1" location="frontend/ui/src/features/ai-assistant/components/ai-assistant-panel.tsx:250">
P2: Clicking a tool step's `Open span` link loses the requested span because this callback stores `spanId` but the sheet receives only `traceId`, opening the viewer at the trace root. Thread `spanId` through `AgentTraceSheet` and initialize or select that span in the viewer.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| <span title="Estimated cost">${msg.costUsd.toFixed(4)}</span> | ||
| </> | ||
| )} | ||
| {msg.traceId && msg.traceStatus === "available" && onOpenTrace && ( |
There was a problem hiding this comment.
P2: After a streamed turn completes, the new View trace link is absent because the live message never receives traceId or traceStatus. Handle the final trace SSE event and annotate the current assistant message, or reload the session messages when the run finishes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/ui/src/features/ai-assistant/components/message-list.tsx, line 487:
<comment>After a streamed turn completes, the new `View trace` link is absent because the live message never receives `traceId` or `traceStatus`. Handle the final `trace` SSE event and annotate the current assistant message, or reload the session messages when the run finishes.</comment>
<file context>
@@ -460,6 +484,19 @@ function UsageFooter({ msg }: { msg: AIMessage }) {
<span title="Estimated cost">${msg.costUsd.toFixed(4)}</span>
</>
)}
+ {msg.traceId && msg.traceStatus === "available" && onOpenTrace && (
+ <>
+ <span>·</span>
</file context>
There was a problem hiding this comment.
Fixed in 5ba2f54. The stream parser only read data: lines, so the agent's NAMED trace event (event: trace) was dropped; it now tracks the event name and annotates the live assistant message with traceId/traceStatus, so the View trace link appears without a reload. Covered by two new use-ai-stream tests (available and disabled outcomes).
| <MessageList | ||
| messages={messages} | ||
| sessionStreaming={isStreaming} | ||
| onOpenTrace={(traceId, spanId) => setOpenTrace({ traceId, spanId })} |
There was a problem hiding this comment.
P2: Clicking a tool step's Open span link loses the requested span because this callback stores spanId but the sheet receives only traceId, opening the viewer at the trace root. Thread spanId through AgentTraceSheet and initialize or select that span in the viewer.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/ui/src/features/ai-assistant/components/ai-assistant-panel.tsx, line 250:
<comment>Clicking a tool step's `Open span` link loses the requested span because this callback stores `spanId` but the sheet receives only `traceId`, opening the viewer at the trace root. Thread `spanId` through `AgentTraceSheet` and initialize or select that span in the viewer.</comment>
<file context>
@@ -238,7 +244,11 @@ export function AiAssistantPanel({
+ <MessageList
+ messages={messages}
+ sessionStreaming={isStreaming}
+ onOpenTrace={(traceId, spanId) => setOpenTrace({ traceId, spanId })}
+ />
)}
</file context>
There was a problem hiding this comment.
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
861f455 to
8c0b839
Compare
4143fd4 to
f141930
Compare
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
f141930 to
f15c1f3
Compare
8c0b839 to
e687271
Compare
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
f15c1f3 to
c3185b4
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
…e in-page viewer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
…es (user viewer snapshot unchanged) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
…t-panel swap shows it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
…trace sheet Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
The header id stayed on the user trace while the panel had swapped to the RCA's agent trace (badge said Agent, id said otherwise). Use effectiveTraceId. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018DopjNH21gLaDtWeeMDEHX
The three id columns each open something different — Run ID the run's self-trace, Trace ID the scanned customer trace, Finding ID the RCA's analysis trace — but the handler sat on the inner button, which is only as wide as the id text. Clicking the padding beside a short id fell through to the row handler, so aiming at a Finding ID and missing opened the scanned trace instead of the analysis: the wrong one of three, with no way to tell it had happened. Moves the handler onto the <td> so the whole cell routes to its own id, via one IdCell that replaces the three near-identical cell bodies. The inner button stays for keyboard users and its click bubbles to the same handler, so a cell fires exactly once. Cells with nothing to open still fall through to the row. Co-Authored-By: Claude <noreply@anthropic.com>
… bounds Four review findings on the surfaces this PR adds. **A tool step could open another turn's trace.** Resolving the step's trace by scanning forward for the next assistant bubble runs past the turn boundary when a tool-only run produces none, attaching the step to the *following* turn's trace — a different trace, which does not contain that span. The search now stops at the next user message, and the link is offered only when that turn's trace is `available`, the same gate the turn's own "View trace" link uses. A new test file covers all three cases. **The embedded viewer ignored the sheet.** TraceViewerPanel is a fixed 70%-viewport overlay, so nesting it in the drawer painted over the page rather than filling it; the AI Assistant button was also inert, since the sheet hard-codes `aiPanelOpen: false` while `setAiPanelOpen` still reached the outer panel. An explicit `embedded` prop drops the fixed positioning and hides that control. The non-embedded class string is written out unchanged rather than composed — the user-trace snapshot test compares this markup byte for byte, and caught a reordering that was visually identical. **A tooltip claimed a finding predated tracing.** Absent execution status means different things depending on whether an analysis ran at all; `rca_status` distinguishes them, so a never-analyzed finding now reads "not analyzed" and a failed one says so, instead of both claiming they ran before tracing existed. Co-Authored-By: Claude <noreply@anthropic.com>
… tests Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
… a metadata chip The header 'View analysis trace' button read heavier than the action warrants; the entry point now sits with the User/Session navigation chips (Langfuse-style) as 'Analysis: RCA trace'. Only rendered when the finding's RCA trace is available, so the chip row on ordinary user traces is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
… target them A detector self-trace or an agent (RCA) trace can never carry findings (the judge's read asserts source = 'user' server-side), so the tab only ever rendered empty. Falls back to the tree view if the tab was active when the viewer swaps to the agent trace. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…-table tooltip states apart - The stream parser only read data: lines, so the NAMED trace event the agent emits at run end was silently dropped and the live message never grew its View trace link until a reload. - The finding tooltip now distinguishes failed from disabled trace status, and an absent rca_status (enrichment lookup failed) from an explicit null (no analysis ran). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
… swap resets selection - 'Root cause analysis ›' replaces the vaguer 'Analysis: RCA trace' chip; the agent-trace view gains an 'Analyzed trace: <id> ›' return chip and the header drops the '← Back to trace' button. - Selection/collapse reset is keyed on the EFFECTIVE trace id, so a customer span selected before the swap no longer renders (or fetches I/O) against the agent trace. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
The trace event is a separately enqueued chunk; asserting after the text delta alone passed whether or not the disabled event was consumed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
The runs proxy read `detector_rcas.latest_execution_id`, which #2070 dropped in favour of the append-only executions table. Read the finding's executions newest-attempt first and apply the same rule as the RCA route: the status is the current attempt's, the trace link is the newest attempt whose export landed (so a pending retry does not hide a working trace), falling back to the current attempt's pending/failed/disabled status. `TraceStatus` is now exported from @traceroot/core (#2072); the ai-assistant types import it from there instead of reaching into the detectors hook module. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
"Open in new tab" while viewing an RCA agent trace (the Alert chip swap, or the sidebar sheet) sends ?traceId=<agent id>&source=agent to the traces page, which ignored the param and pinned source="user" — the popped-out tab asked for the agent trace under the customer scope and 404'd with "Error loading trace". Open the deep-linked id under the agent scope, and only while the selection is that id: the list stays customer-only and navigating to a row drops back to "user". Also exercises the viewMode fallback effect (Detectors tab active when the tab disappears) through the prop change that reaches it in production. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…ts trace A tool step looked for the first assistant bubble after it, but the trace outcome is stamped on the run's last text segment only (persister and live hook alike). In a text → tool → text turn the segment right after the first step carries nothing, so that step silently lost "Open span". Look for the bubble that carries an available trace instead. Also renders and clicks the per-turn "View trace" footer link, which no test reached before (the assistant fixture set no usage, so the footer never mounted). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…ctor-run clock The 404 branch fired for source=agent but kept the detector self-trace copy, and the detectors page fed it the detector run's timestamp for a finding's agent trace too. The RCA starts after the run and takes minutes, so any ingest lag on an available analysis trace opened from the Findings tab read as "No trace was recorded for this run — the self-trace export didn't reach the backend". Every entry point into an agent trace gates on the execution's traceStatus being "available" — the export already succeeded — so a 404 is ingest lag: say so, in analysis terms, and withhold the run timestamp for a finding's trace. The detectors page tests now run the real id helpers (the mock did not export agentTraceId, so the finding path would have thrown) and cover the Finding cell click and the ?traceId=&source=agent deep link. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
The name from an `event:` line was cleared only after a successful JSON.parse of the next `data:` line, and never on the blank line that ends the frame. A dataless named event, or one whose data is not JSON (the agent's `event: error` can carry a raw string), left the name set and the next plain data line was read as the trace payload and dropped. Reset on the blank line and consume the name before parsing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
…e finding tooltip, gate agentTraceId on availability Ablation results from the #2082 review: - The sheet shadowed an exported `LayoutContext` with a no-op registerAiHost and aiPanelOpen: false, but the same PR already gives the viewer an `embedded` prop that means exactly that. Gate registerAiHost() and the nested AiAssistantPanel on `embedded`, un-export the context, drop the Provider. The sheet's interception test (against a stand-in viewer) is replaced by a stronger one on the real panel: embedded + aiPanelOpen: true → registerAiHost never called, no nested panel, no AI button. - The Finding ID tooltip was a 10-branch nested ternary in JSX with one branch (`execution_trace_status != null`) unreachable by type. Extract describeFindingTraceTitle and drop the dead branch. - agentTraceId() fell back to the dashless finding id, which the click path could never reach (only an available status makes the cell clickable) and which, for a legacy finding, named a trace that does not exist. Return the execution's trace id only once its export landed, so the ?source=agent deep link shares the cell's gate. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
Since #2071 the persister stores a tool step's result as text (serialised, then redacted and truncated) and records why a result was not kept (`withheld`), whether the capture was cut (`truncated`) and the real output size (`outputBytes`). The sidebar still JSON.stringified the metadata value, so a reloaded step showed a one-line escaped string where the live stream had shown a pretty-printed object, and a withheld `bash` result reloaded as a bubble with no output and no explanation. Parse an intact JSON result back into the value the live stream rendered (a truncated one cannot be valid JSON, a plain-string result never was; both stay text), and render the capture verdict: [output withheld: not-allowlisted · 44 bytes] [captured I/O truncated · 90,000 bytes of output] Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
- Say in code that setAiContext({ traceId }) uses the customer id on purpose
while the agent trace is on screen (the assistant's tools read customer
traffic only).
- Key the "Analyzed trace" back-chip on what is displayed (effectiveTraceId
!== traceId) rather than on the swap intent, so an RCA refetch that drops
the agent trace cannot leave a back-chip to the trace itself.
- Skip the findings lookup for an internal trace opened directly; detectors
never target those, so the answer is always empty.
- Pass no onOpenTrace to the message list when there is no projectId — the
sheet does not mount without one, so the links opened nothing.
- Give the agent-trace drawer an sr-only description (Radix warned), and
move the sheet test's stray `it` into its describe.
- Note why the live SSE URL carries no source while the cache key does.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
… user and session The chrome snapshot mocks SpanInfoPanel away, and its chip row is the one place the agent-trace work touched the customer path. Pin the real panel for an RCA-less customer trace carrying user_id + session_id: User:/Session: chips, nothing beside them. The header states the intended scope — a customer trace with an available RCA gains the "Root cause analysis" chip on purpose. The existing byte-compare stays untouched. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
9adb429 to
0ff17ea
Compare
…-closed ui and span-focus PRs Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DXLChvn8HPA42wsuDUFYo
Makes the traces reachable. Four entry points:
trace_status = available.The invariant to review hardest: analysis traces never appear in the Traces list, and the viewer for customer traces is unchanged. The snapshot test of the user-trace viewer is taken in its own commit before the changes, so the diff proves it stayed byte-identical.
Closes #2064. Part of #2058.