Skip to content

feat(terminal): Log view — transcript live tail (main + subagent transcripts) - #5

Open
thanhsmind wants to merge 2 commits into
vantt:mainfrom
thanhsmind:main
Open

thanhsmind wants to merge 2 commits into
vantt:mainfrom
thanhsmind:main

Conversation

@thanhsmind

Copy link
Copy Markdown

What

A second, additive observation surface for Claude Code panes: a Log toggle in the terminal view that tails the agent's own on-disk session transcript instead of screenshotting the terminal grid.

The existing screen view polls pane.read — an 80-line ANSI snapshot every 1.5s — so anything that scrolls past between ticks is unrecoverable, and there is no concept of a tool call, command output, or subagent. The transcript (~/.claude/projects/<encoded-cwd>/…) is append-only and written by Claude Code itself, so a byte cursor over it is gap-free by construction: every command with its full output, thinking, edits, and subagent activity between two polls arrives, regardless of poll interval. This is the transcript-as-history-source candidate from the distillery (collie, docs/distillery/porting-log.md:104), finally built.

How

  • src/transcript/ (new): resolve pane → transcript via the snapshot's foreground_cwd ?? cwd (D5 precedence) and Claude Code's project-dir encoding; gap-free tail reading complete lines only (a mid-append partial line waits for the next poll); defensive per-record rendering — unknown record shapes pass through raw, nothing is silently dropped; cursor validated against path escape.
  • Subagents: Claude Code ≥2.1.x writes Task/teammate activity to <session-id>/subagents/agent-*.jsonl, not as isSidechain records in the main file (verified live against 2.1.222). The watch set is therefore multi-file: main session + every subagent transcript, merged by record timestamp, subagent lines prefixed , files born mid-watch tailed from byte 0.
  • GET /api/panes/:pane/activity (new route): stateless per request like every existing route — the (opaque, multi-entry) cursor round-trips through the client; the gateway stores nothing, keeping the never-store rule (src/store/mod.rs) intact. Panes without a transcript answer available:false (data, not an error).
  • Web UI: a "Log" footer toggle swaps the xterm screen for an append-only <pre> with a client-owned 200-line ring and stick-to-bottom (release on scroll-up). Opening backfills the tail so the ring lands full. Screen view, PageUp history, keys/reply sheets are untouched.
  • Drive-by fix: demo_config() now builds its JSON via serde_json — the Windows temp dir's backslashes made --demo panic on invalid JSON escapes.

Scope & limits

  • Open backfill window 512 KiB, per-record caps (tool output 40 lines, text 80 lines, 400 chars/line), 400 lines per poll, 200-line client ring. Truncation is always marked (… (+K lines)), never silent.
  • Claude Code panes only for now — other panes show "No transcript" and keep the screen view as before. No WebSocket (consistent with decision 675fc93a), no persistence, no process ownership.
  • Design notes and resolved decisions: plans/260805-1500-transcript-as-history-source/plan.md.

Testing

  • 16 unit tests on the transcript module (encoding, EOF-open backfill, incremental append, partial-line handling, torn-window start, subagent merge, mid-watch subagent pickup, session-switch divider, cursor path-escape rejection, render shapes, ANSI strip/clip).
  • 3 handler tests (auth-opaque 404, unknown pane 404, available:false).
  • 5 new vitest tests on the Log toggle (viewport swap, cursorless open, cursor round-trip across toggles, 200-line ring, screen-poll resume). 112 web tests / 338 lib tests green (the one failing lib test, doctor::checks::agent_presets_fix_seeds…, fails on a clean checkout of main too — pre-existing, unrelated).
  • Verified end-to-end against a live deployment: real Claude Code 2.1.222 session with an active teammate; open lands with the ring full, subagent lines stream live.

🤖 Generated with Claude Code

ThanhsMind and others added 2 commits August 5, 2026 15:42
A second, semantic observation source for Claude Code panes: tail the
agent's own on-disk session transcript (~/.claude/projects/<encoded-cwd>/
*.jsonl) with a client-held byte cursor, so every command, output, thinking
line and subagent action appended between polls arrives — nothing scrolls
away unseen between 1.5s ticks, unlike the pane.read pixel path.

- src/transcript/: resolve (cwd -> project dir -> newest session file),
  gap-free tail (complete lines only, partial appends wait), defensive
  per-record rendering (unknown shapes pass through raw), cursor validated
  against path escape. Open = EOF: no retroactive history by design.
- GET /api/panes/:pane/activity: stateless like every route; cursor
  round-trips through the client; gateway stores nothing (never-store rule
  untouched). available:false when a pane has no transcript.
- web: "Log" footer toggle swaps the xterm screen for an append-only <pre>
  with a client-owned 200-line ring and stick-to-bottom; screen view,
  PageUp history and input sheets untouched.
- drive-by: demo_config() now builds JSON via serde_json — the Windows temp
  dir's backslashes made --demo panic on invalid JSON escapes.

Plan: plans/260805-1500-transcript-as-history-source/plan.md (executed
directly at user request, outside the bee flow).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Field feedback on the first Log view build: opening to an empty pane read
as "still limited", and subagent work never showed up.

- Open now backfills: render the last 512 KiB of the session tail and land
  with the client's 200-line ring already full, cursor at the end of the
  last complete line so the next poll continues seamlessly.
- Subagents: Claude Code ≥2.1.x writes Task/teammate activity to
  <session-id>/subagents/agent-*.jsonl — the main file's isSidechain is
  always false (verified against 2.1.222) — so the watch set is now the
  main file plus every subagent transcript, merged by record timestamp,
  subagent lines prefixed with ⑂, files born mid-watch tailed from byte 0.
  Cursor becomes a ;-joined multi-entry set; v1 cursors still parse.
- Caps loosened: tool output 20→40 lines, text blocks 40→80.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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