feat(tui): render headless session conversation history#195
Merged
Conversation
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
force-pushed
the
headless-history-tui
branch
from
May 24, 2026 16:34
eb3e22c to
aa3819c
Compare
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.
Problem
Headless sessions (zarvis, claude, codex, antigravity — any harness run with
--mode headless) emit their conversation as structuredMessage/Reasoning/ToolUse/ToolResultevents with no PTY. The TUI renders the selected session viaItemHistory.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 toItemHistory(MessageKind= User / Assistant / Reasoning).feed_messagecoalesces the adapter's per-deltaMessage/Reasoningstream into contiguous runs. It pushes one item per delta (append-only) soreplaystays 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 setsbreak_before.synth_messagerenders role-styled bytes (assistant plain, user gray❯, reasoning dim), CRLF-normalized, through the existingreplay_fullvt100 interleaving path. The fast/shadow-scrollback paths now defer toreplay_fullwhenever any non-PTY item exists.Harness-agnostic gating
Wiring is gated on
is_headless(session)(mode =="headless"), which mirrors the webui'sisHeadlessSessionexactly — so it covers headless zarvis/claude/codex/antigravity uniformly (shell is never headless). Applied in both:apply_transcript_to_local_stategains anis_headlessarg, threaded throughSessionHydrationRequest(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_beforeflags + 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.agentd-clisuite (169) green;--lockedworkspace build clean.Notes / styling choices (flag if you'd prefer otherwise)
❯prompt, reasoning = dim. A single\r\nseparates message runs / tool blocks (mirrors the tool-block layout).Messageevents depends on the adapter; this renders whatever roles arrive.🤖 Generated with Claude Code