cmux NEXT: view a Claude Code or Codex session without taking it over
Leo wants to see a Claude Code or Codex session open in cmux NEXT next to the app that runs it, to compare the UI and move over gradually, without cmux taking the session away. This adds a View Only open: cmux NEXT shows the chat and follows the app as it writes, and it never resumes, writes to or locks the original. Take Over is a separate, explicit step. Viewing never blocks starting a session: new chats, harness picks and folders work as before, and the viewer always offers a new chat of its own.
Behavior
- acpmux watch mode.
session/newwithadopt.watch: true(Claude Code or Codex) looks up the transcript the way adoption does:~/.claude/projects/**/<id>.jsonl, or a Codexrollout-…-<id>.jsonl. It does not start the harness. The chat replays the transcript into acpmux history, then reads only new complete lines every 400 ms and shows each prompt and answer as it lands. It stores the file and the byte offset (SessionMeta.watching), so a daemon restart picks up where it stopped. A transcript that is replaced or truncated is read again from the start. - Nothing touches the original. The transcript file is opened read-only, and no agent process starts, so there is no
--resume,session/loador lock. A prompt is refused withwatch.read_only, and web origins cannot use_acpmux/watch_take_over(watch.origin_refused). - Take Over.
_acpmux/watch_take_overends the watch. The next prompt resumes the conversation as an ordinary adopted chat. If the transcript was written in the last few minutes, Take Over first answers with the sameadopt.livedetails an adopt does. The page then asks whether to fork the chat (Claude Code only) or open it anyway. - Continue in New Chat. The View Only banner offers a way on beside Take Over. For Claude Code, Continue in New Chat forks the conversation into a new chat (
claude --resume <id> --fork-session), so the original is only read. For Codex, which cannot be forked while another app runs it, New Chat Here starts a Codex chat in the same folder. The viewed chat stays as it is. An adopt withifLive: forknow always makes a new chat; before, it could return the chat that already adopted or viewed the conversation. This only adds a button and changes no existing flow, so it has no Debug toggle. - Agent pane. Session summaries carry
watching: "claude" | "codex". The pane shows "Viewing Claude Code session" with a Take Over button, and the composer is off with "Take over this session to send messages". - Entry points, one path. The All chats row menu's View Only (
chats.watch), the palette's View Chat Only (agentChat.view) andcmux agent view <chat>all callChatsOpenCoordinator.watch. That coordinator is the Open Chat path withadopt.watchset.<chat>is an index key (claude-code:<id>,codex:<id>), a transcript path, or a bare session id; a bare id is Claude Code's when its transcript is under~/.claude/projects, otherwise Codex's.cmux agent view show|ensure|…keep their sidebar-view meaning.
Supporting changes:
hub/lifecycle.rswas at the 1000-line limit. Its route check moves toroute_switch.rs::check_new_route.cli.rsis at 993 lines. The positional chat foragent viewlives incli/agent_view.rs.server/requests.rsstays under the line limit: adopt and Take Over share oneIfLive::parse.- CmuxNextApp does not grow (its size cap):
ChatOpenSubjectmoved toCmuxNextAgentPane, and the View Chat Only refusal moved toRefusalStrings. - Merged
feat-cmux-nextat59d0076. The base this branch started from failed to compilecmux-sdk'shome_conversation_livetest, and that is fixed upstream.
Validation
Hosted CI only; nothing was built on a Mac.
- acpmux,
tests/hub_integration/watch.rs:- a watched Codex chat shows the rollout and what is appended to it;
- a snapshot of every file under the Codex home is byte-identical before and after;
- prompts are refused, and no
initializeorsession/loadis recorded; - a watched Claude chat shows prompts and answers and settles ended turns;
- Take Over lets the next prompt run and settles a turn the other app left open.
- The pane's warm-up and a mode change start no agent on a viewed chat.
- A Claude turn ends on its text line, not the thinking line before it.
- Viewing never blocks starting a session: a new chat in the viewed chat's folder runs, and a fork is its own chat while the view goes on (
viewing_a_chat_never_blocks_starting_a_new_one). - A unit test covers complete-line reading and offsets.
- Focused run
test_filter=watch: 39204810572, in progress.
- CLI,
cli/agent_view.rs: key, path and bare-id mapping, and sidebar view verbs are left alone. Focused runtest_filter=agent_view: 39204813047, in progress. - Swift
AgentChatViewTests: View Only sendswatch: truein the adopt handshake, and an ordinary open sends none.chats.watchdecodes, and the action takes only Claude Code or Codex keys. These run in this PR's cmux-next checks. - Webview, bun:
watch-banner.test.tsx: the banner names the app and offers Take Over plus Continue in New Chat (Claude Code) or New Chat Here (Codex);all-chats-view-only.test.tsx: the row menu offers View Only, off for chats acpmux cannot watch, and it calls back with the row's key.tsc --noEmitis clean.
- The action exports (
plans/cmux-next/action-surfaces.json) are left to the autofix bot, which regenerates them on a Mac runner.
Not verified: a live run against a real Claude Code or Codex session in a built app, and the side-by-side capture against the Claude app. That needs a dogfood build (dev-build label) on a machine with those apps signed in.
Limits
- Claude Code and Codex only; other families are refused.
- Viewing polls the file (400 ms) instead of using file events.
- A watched chat shows the transcript's prompts and answers; tool calls and thinking are not mapped yet.
- A transcript replaced by a longer file is not detected (no inode check), and an old Codex rollout with only unannotated items shows nothing while being followed.
- The model and mode pickers stay drawn on a viewed chat. acpmux refuses those changes until Take Over, but the controls are not greyed out.
- A Codex New Chat Here starts without the viewed conversation's context.
Changelog
- Added: View Only for Claude Code and Codex chats in cmux NEXT (All chats menu, palette,
cmux agent view): the chat follows the app that runs it without taking it over, until Take Over.
Context: manaflow-ai/cmuxterm-hq#2503. Related: #21253 (read-only observe of an acpmux session, which complements this one).
No capture: View Only needs a Claude Code or Codex transcript written outside cmux, and a capture slot has none. The banner and the All chats menu row are webview UI, covered by watch-banner.test.tsx and all-chats-view-only.test.tsx.
🤖 Generated with Claude Code
Agent session: cosmic-gray