feat(cli): full conversation preview in the trajectory browser - #382
Open
forrestjgq wants to merge 11 commits into
Open
feat(cli): full conversation preview in the trajectory browser#382forrestjgq wants to merge 11 commits into
forrestjgq wants to merge 11 commits into
Conversation
Add raven/trajectory/conversation.py: attempt_conversation rebuilds an attempt's span snapshot into a flat, causally ordered stream of labeled ConversationRecord entries (User input, LLM input/output, Tool input/output, skill/memory/subagent events). Inputs sort at span start, outputs at span end; ties break by phase, then by nesting depth derived from parentSpanId. Full content is read from artifact files (paths restricted to the trace store, 512 KiB cap) with span previews as a visible fallback; ERROR spans and unreadable expected payloads always yield a record instead of disappearing. LLM inputs omit only the verified common message prefix within one (traceId, parentSpanId) chain and announce omissions and rewrites. Document the Conversation Record term in CONTEXT.md. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Bound artifact reads to the 512 KiB cap plus one byte instead of reading whole files first, and reject non-regular files before opening them. Emit a Turn marker record for body-less session.turn roots so a turn's identity and start time survive an empty body. Render thinking_blocks as reasoning evidence when reasoning_content is absent, without duplicating identical text. Always emit a separate malformed-record notice so a readable payload cannot make a span with corrupt status or attributes read as fully OK. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
A session.turn whose root carries only an output (old or damaged logs) produced no input-phase record, so the turn's own start time was unaddressable for rendering. Emit the Turn marker whenever the root yields no User input record, not only when the body is fully empty. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
An ERROR is a completion event: attach it to the span's last output-phase record instead of whatever was emitted last, so the Turn start marker never carries an end-of-turn failure. A span without any output-phase record gets a completion placeholder at endTime. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Space on an attempt row now renders the causally ordered record stream from raven.trajectory.conversation instead of the two truncated per-turn preview lines: kind-colored labels aligned to one column, hanging-indent bodies wrapped by display width (CJK-aware), degraded/error/meta notes, and turn separators inserted in a single pass over the ordered stream so interleaved traces are never regrouped. Narrow terminals switch to a stacked layout that never drops characters. Streams taller than the screen page through less when an interactive pager is confirmed (both stdio ends a tty, capable TERM, less on PATH); the subprocess is judged by its exit code and only a clean pager run skips the press-any-key waiter. Every dynamic field passes the sanitization gates before layout. A rebuild failure falls back to the legacy per-turn preview. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Ctrl+C while paging now closes the pipe, terminates and reaps the pager (killing it after a bounded wait), then raises the browser-wide cancel instead of unwinding as a raw interrupt; the early-quit BrokenPipe path also releases the pipe before the exit code decides. Turn separators are pre-wrapped like every other line, so the produced line count is the true screen usage that drives the pager threshold, and the minimum layout width is an explicit 2-cell floor (a wide character cannot be split) instead of a silent widening to 4. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Real-trace self-testing showed two readability gaps: bodies were hard-wrapped mid-word (display-width only), and an LLM call repeated the same model/token note on its input, thinking, and output records. Wrap now breaks after the last space when one exists (hard wrap remains for unbroken runs; the width contract is unchanged), and consecutive records of one span sharing a meta show it once, on the last of them. Adds the pager-threshold boundary test (exactly height minus two lines prints directly, one more pages) and a three-trace interleave render snapshot. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Span ids are only unique within one trace, so a merged attempt can interleave records whose span ids and meta match across traces; the consecutive-meta suppression now also requires the same trace id, so each trace keeps its own note. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Replace the external less pager with a prompt_toolkit full-screen viewer: the new bottom bar shows a muted 'h for help' hint plus filter and collapse state, h opens an aligned key-reference page, s globally folds any body longer than five display lines (judged after wrapping) down to five plus a dim ellipsis while never hiding degraded/error/meta notes, and % opens a real input line that filters records by label, case-insensitively (empty input clears; groups left empty lose their turn separators). The viewer passes its actual content width to the line builder on every render, so a resize re-wraps and re-judges folding, and Ctrl+C is the global cancel in all three states (viewing, help, filter input), surfacing as the browser-wide cancel. Rich lines convert to prompt_toolkit fragments with whole-line styles merged explicitly. Without a terminal the preview degrades to direct printing with the press-any-key waiter; the less subprocess management and its tests are removed. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
The bottom bar now budgets by display width: the scroll range and the 'h for help' hint always survive, variable fields (title, filter, collapsed) shrink first with the first non-fitting one clipped, the hint compacts to 'h' before dropping, and a final clip keeps the bar inside any terminal (CJK filters measured by cells, not len). The help page became a scrollable line flow: scroll keys page it, any other key returns to the preview at its kept position, long entries wrap at narrow widths, and its own offset feeds the bar's range. Tests assert against really rendered screens (renderer frames and a synchronous layout render), covering collapse+filter on 40 columns, long and CJK filters, and small-screen help reachability. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
The focused content control painted its caret on the first character of the view and the help page (covering the P of 'Preview keys'). The control keeps focus for a valid layout but no longer shows a cursor; the filter input line still does, where typing happens. Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
gloryfromca
reviewed
Sep 9, 2026
gloryfromca
left a comment
Contributor
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the complete github/main...HEAD diff at 0c4efc30bb95, including AGENTS.md/CLAUDE.md and the Runtime glossary, the conversation rebuild and viewer implementations, their callers and trace/artifact producers, attempt grouping, commit history, backward compatibility/fallback behavior, test changes, and the TUI/Runtime architecture boundary. I found no weakened tests or concrete defect worth raising.
Verification:
uv run pytest tests/test_trajectory_conversation.py tests/test_cli_preview_viewer.py tests/test_cli_trajectory_browse.py -qon Python 3.12: 221 passed.uv run ruff check ...anduv run ruff format --check ...on the six changed Python/test files: passed.- The underlying
check-large-filescommand forgithub/main..HEAD: passed (makeitself is unavailable in this environment). - Full
uv run pytest -qon Python 3.13 reached 7,287 passed, 43 skipped, and one failure, then exited 139 during shutdown. The failure reproduces alone under Python 3.12 intests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare; that test, its implementation, and dependency files are unchanged by this PR. GitHub's Python 3.12 unit job is green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Space on an attempt row in the
raven trajectorybrowser previouslyshowed two truncated per-turn preview lines. This PR replaces it with an
interactive full-conversation viewer:
raven/trajectory/conversation.py: rebuilds an attempt'sspan snapshot into a causally ordered stream of labeled records (User
input, LLM input/output, Tool input/output, skill, memory, and
subagent events). Event-time semantics (inputs at span start, outputs
at span end) plus nesting depth from parentSpanId keep the causal
order. Full content comes from artifact files (paths restricted to
the trace store, streamed 512 KiB cap per file); span previews are
only a visible fallback. ERROR spans and expected-but-unreadable
payloads always yield a placeholder record. LLM inputs omit only the
item-by-item verified common message prefix against the previous call
(omissions and rewrites are announced); tool_calls of unexecuted
tools keep their full arguments.
bodies with word-aware CJK-safe wrapping, degraded/error/meta note
lines, single-pass turn separators (interleaved traces are never
regrouped), and a stacked layout on narrow terminals that never drops
characters.
preview on a terminal): scrolling, a muted bottom bar with an
'h for help' hint, filter/collapsed state and the scroll range (laid
out by display width, never overflowing any terminal), a scrollable
help page reachable in full on small screens, a global collapse (s:
bodies over five display lines fold to five plus an ellipsis; error
and degradation notes never fold), and a label filter (%: real input
line, case-insensitive, empty clears; filtered-out turn groups lose
their separators). Resizes re-wrap and re-judge folding live; the
content caret is hidden (only the filter input shows one). q/Esc
returns to the list; Ctrl+C cancels the whole browser from any viewer
state. A rebuild failure falls back to the legacy per-turn preview;
without a terminal the preview degrades to direct printing.
characters, rich markup, and ANSI never reach the terminal). Adds the
Conversation Record term to CONTEXT.md.
Type
Verification
uv run pytest tests/test_trajectory_conversation.py -q-> 45 passeduv run pytest tests/test_cli_preview_viewer.py -q-> 23 passed(assertions run against really rendered screens)
uv run pytest tests/test_cli_trajectory_browse.py -q-> 153 passeduv run pytest -q(full) -> 7232 passed, 46 skipped; 11 failed +20 errors reproduce identically (same node ids) on the base branch in
a clean worktree - environment baseline, not introduced here
uv run ruff check/ruff format --checkon changed files -> cleanmake check-large-files-> passManual: rendered 6 real local attempts at widths 72/120/30 with zero
over-width lines (largest attempt 7060 lines in 0.35s); drove the
real TUI in a pty at normal and 44x12 sizes (viewer, help paging,
collapse, %llm filtering, Ctrl+C cancel)
Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
Read/display side only; no persisted format changes. The untrusted
input surface (artifact content, typed filter input) is contained by
the store-prefix path check, the 512 KiB streamed cap, and per-field
sanitization.
User-visible change: the Space preview is now a full-screen viewer;
the legacy rendering remains as the automatic fallback when the
rebuild fails, and non-terminal environments print directly.
Rollback: revert this PR, or restore the previous
_preview_screencall site to get the old preview back.
Security impact considered
Backward compatibility considered
Rollback path is clear for risky changes
Related Issues
N/A