Skip to content

feat(tui): render headless session conversation history#195

Merged
edwin-zvs merged 1 commit into
mainfrom
headless-history-tui
May 24, 2026
Merged

feat(tui): render headless session conversation history#195
edwin-zvs merged 1 commit into
mainfrom
headless-history-tui

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Problem

Headless sessions (zarvis, claude, codex, antigravity — any harness run with --mode headless) emit their conversation as structured Message/Reasoning/ToolUse/ToolResult events with no PTY. The TUI renders the selected session via ItemHistory.replay, which only ingested PTY bytes and tool blocks — so a headless session showed its tool blocks (and (no PTY history yet …)) but never the assistant prose. The webui already renders the full transcript in chat mode; the TUI didn't.

Approach (2): a first-class message item

  • Item::Message { kind, text, break_before } added to ItemHistory (MessageKind = User / Assistant / Reasoning).
  • feed_message coalesces the adapter's per-delta Message/Reasoning stream into contiguous runs. It pushes one item per delta (append-only) so replay stays incremental — mutating one growing item would force a full vt100 reparse per delta (the old "typing lag" failure mode). Only the first item of a run sets break_before.
  • synth_message renders role-styled bytes (assistant plain, user gray , reasoning dim), CRLF-normalized, through the existing replay_full vt100 interleaving path. The fast/shadow-scrollback paths now defer to replay_full whenever any non-PTY item exists.

Harness-agnostic gating

Wiring is gated on is_headless(session) (mode == "headless"), which mirrors the webui's isHeadlessSession exactly — so it covers headless zarvis/claude/codex/antigravity uniformly (shell is never headless). Applied in both:

  • the live event handler, and
  • transcript replayapply_transcript_to_local_state gains an is_headless arg, threaded through SessionHydrationRequest (derived from the session summary's mode).

PTY-backed (interactive) sessions keep their prose in the PTY stream and are left untouched — no double-rendering.

Tests

  • feed_message_coalesces_runs_and_marks_breaks — run coalescing + break_before flags + empty-delta ignore.
  • headless_message_history_renders_prose_and_tool_blocks — assistant prose interleaved with tool blocks renders (no PTY).
  • reasoning_and_user_messages_render — user + reasoning + assistant.
  • transcript_replay_renders_messages_only_when_headless — replay folds Message/Reasoning into history when headless, and ignores them when PTY-backed.
  • Full agentd-cli suite (169) green; --locked workspace build clean.

Notes / styling choices (flag if you'd prefer otherwise)

  • Assistant = plain prose, user = gray prompt, reasoning = dim. A single \r\n separates message runs / tool blocks (mirrors the tool-block layout).
  • Whether a headless session emits user-prompt Message events depends on the adapter; this renders whatever roles arrive.

🤖 Generated with Claude Code

Headless sessions (zarvis, claude, codex, antigravity — any harness run
with `--mode headless`) emit their conversation as structured
Message/Reasoning/ToolUse/ToolResult events with no PTY. The TUI's
ItemHistory only ingested PTY bytes and tool blocks, so headless sessions
showed their tool blocks (and "no PTY history yet") but never the
assistant prose — while the webui rendered the full transcript in chat
mode.

Add a first-class `Item::Message { kind, text, break_before }` to
ItemHistory (approach 2): `feed_message` coalesces the per-delta
Message/Reasoning stream into contiguous runs (append-only, so `replay`
stays incremental — no full reparse), `synth_message` renders role-styled
bytes (assistant plain, user gray `❯`, reasoning dim) through the existing
vt100 interleaving path, and the fast/shadow paths now defer to
`replay_full` whenever any non-PTY item exists.

Wiring is gated on `is_headless` (session mode == "headless"), mirroring
the webui's `isHeadlessSession`, in both the live event handler and
transcript replay (`apply_transcript_to_local_state` gains an `is_headless`
arg, threaded through the hydration request). PTY-backed sessions keep
their prose in the PTY stream and are untouched, so there's no
double-rendering for interactive sessions.
@edwin-zvs
edwin-zvs force-pushed the headless-history-tui branch from eb3e22c to aa3819c Compare May 24, 2026 16:34
@edwin-zvs
edwin-zvs merged commit f511978 into main May 24, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the headless-history-tui branch May 24, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant