Skip to content

A worker's conversation as a chat: a 💬 Chat tab beside its terminal, the one a phone opens on - #378

Open
rivshughes wants to merge 1 commit into
AgentSystemLabs:mainfrom
rivshughes:chat-view
Open

rivshughes wants to merge 1 commit into
AgentSystemLabs:mainfrom
rivshughes:chat-view

Conversation

@rivshughes

Copy link
Copy Markdown

What

A 💬 Chat tab on a Claude Code worker's terminal window: the conversation itself rather than a picture of the TUI. Your prompts as bubbles, what the worker said back rendered as markdown, and each tool it used as a collapsible row (what ran, what came back, ✓/✗/⏳), in a page that scrolls like any chat. On the 2D view (/lite) the chat is the tab a worker opens on; on a computer the terminal still is, with the chat a tab away. Workers on other providers keep a terminal only.

Why: on a phone the terminal is a screen-sized window onto a TUI with the history behind it, hard to read and (until #311) impossible to scroll.

How

  • src/shared/protocol/transcript.ts (new domain, registered in protocol.ts): ChatItem (you / agent / tool / note), transcript.attach / detach / more from the browser, transcript.items (reset / append / prepend / patch) and transcript.none back.
  • src/server/transcript.ts: TranscriptLog tails the session's JSONL (the path the Claude Code hooks already give tracker.transcript), reading only what's new, and turns user and assistant records into items. Sidechain (subagent), meta and bookkeeping records are skipped, slash commands read as typed (/clear, not the tags around it), a tool's tool_result fills in the call it belongs to, and inputs/results are cut to a size that suits a phone. Defensive throughout: the format is Claude Code's.
  • src/server/ws/handlers/transcript.ts: who follows which worker; one 1 s poll while anyone does (statSync size check, so an idle worker costs a stat a second and nothing else); a new transcript path (a /clear) resets the chat. FeatureHooks drop a browser's follows when it leaves the floor or closes.
  • WorkerManager.transcriptOf(id): the one accessor the handler needs. manager.ts is at its size ceiling, so owners() is folded onto one line to pay for it (net −1 line).
  • src/client/ui/transcript.ts + .css: the pane. Native scrolling, stays at the bottom while you're reading the latest, keeps your place when older items are prepended, re-renders a tool row in place when its result arrives (keeping it open if you'd opened it). The composer sends worker.prompt (with dictateField's 🎤), ⏹ sends Esc, and needs_input shows the worker's activity with 1 · Yes / 2 · Always / 3 · No for a permission prompt (plain 1/2/3 and a "Terminal" button for anything else), all through term.input as the keypad does.
  • termtabs.ts: a chat option and a start tab; show() is returned so the pane can jump to the terminal. terminal.ts: builds the pane for Claude workers, routes transcript.* messages to it, refreshes it with the worker, detaches on close.
  • Docs: README's phone bullet, docs/features.md (a bullet of its own), docs/controls.md.

Checks

  • npm run typecheck, npm test (619 passing, including new tests/transcript.test.ts: items, incremental reads and result patches, what's skipped, prompt cleaning, tool gists), npm run build; tests/size.test.ts green (manager.ts 1028 ≤ 1029).
  • A real 27 MB session transcript (4,150 records) parses in ~60 ms into 734 items with every tool result matched to its call; a page of 80 items is ~110 KB on the wire.
  • An emulated iPhone on /lite against a live office: the Chat tab opens by default with the latest items at the bottom, Earlier… prepends 80 more without moving what you're reading, tool rows open to their input and result, tab switching detaches and re-attaches. Also used on a real iPhone over HTTPS.

Not included: AskUserQuestion's choices as buttons (they're a TUI menu; the tab points you to the terminal), and transcripts of other providers.

🤖 Generated with Claude Code

…the one a phone opens on

A Claude Code worker's terminal is a poor way to read what it's been doing on a phone: a
screen-sized window onto a TUI, with the history behind it. The chat tab shows the conversation
itself instead: what you told it, what it said back (as markdown) and each tool it used, as a row
that opens to show what ran and what came back, in a page that scrolls like any chat.

It's read off the transcript Claude Code keeps for the session (the file its hooks name, see
providers/claude.ts), tailed while anyone has the tab open: the server parses the user and
assistant records into chat items, sends the latest 80 on attach, each new one as it's written,
and a tool's result as a patch of the row it belongs to. Earlier… pages further back. A /clear
names a new transcript, which starts the chat over.

The box at the bottom sends a prompt the way the terminal's does (worker.prompt, with the 🎤), ⏹
interrupts, and when the worker wants permission its choices show above the box as buttons (Yes,
Always, No), the same keys the keypad sends. A question with choices of its own still wants the
terminal, which is the next tab. On the 2D view the chat is the tab that opens; on a computer the
terminal is, as before. Other providers' workers keep a terminal only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
muguetdev pushed a commit to muguetdev/agent-office that referenced this pull request Oct 27, 2026
…the one a phone opens on (from Agent Office AgentSystemLabs#378)

A Claude Code worker's terminal is a poor way to read what it's been doing on a phone: a
screen-sized window onto a TUI, with the history behind it. The chat tab shows the conversation
itself instead: what you told it, what it said back (as markdown) and each tool it used, as a row
that opens to show what ran and what came back, in a page that scrolls like any chat.

It's read off the transcript Claude Code keeps for the session (the file its hooks name, see
providers/claude.ts), tailed while anyone has the tab open: the server parses the user and
assistant records into chat items, sends the latest 80 on attach, each new one as it's written,
and a tool's result as a patch of the row it belongs to. Earlier… pages further back. A /clear
names a new transcript, which starts the chat over.

The box at the bottom sends a prompt the way the terminal's does (worker.prompt, with the 🎤), ⏹
interrupts, and when the worker wants permission its choices show above the box as buttons (Yes,
Always, No), the same keys the keypad sends. A question with choices of its own still wants the
terminal, which is the next tab. On the 2D view the chat is the tab that opens; on a computer the
terminal is, as before. Other providers' workers keep a terminal only.

Ported to the fork: the tab sits beside 💻 Terminal and 📝 Code in termtabs.ts,
the terminal keeps 📋 Copy, 📎 and Hide, every word is in en, pt-BR and ko, and
the server only follows a worker for someone who may go to its floor (checked
again on every poll). The transcript is read with a UTF-8 decoder, so a
character cut by a read comes through whole.
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