implement-tmux-wrapped

Implement: tmux-wrapped chat agents (per design-chat-agent) + close-vs-leave exit prompt

Metadata

Statusdone
Assignedagent-1170
Agent identityf51439356729d112a6c404803d88015d5b44832c6c584c62b96732b63c2b0c7e
Created2026-04-29T19:30:23.667729077+00:00
Started2026-04-29T19:30:56.761251726+00:00
Completed2026-04-29T20:03:10.818948233+00:00
Tagspriority-high,fix,tui,pty,chat-persistence, eval-scheduled
Eval score0.60
└ blocking impact0.50
└ completeness0.50
└ constraint fidelity0.85
└ coordination overhead0.60
└ correctness0.65
└ downstream usability0.55
└ efficiency0.60
└ intent fidelity0.47
└ style adherence0.75

Description

Description

Implement chat agent persistence per design-chat-agent (commit 65612d6c1, doc at docs/design/chat-agent-persistence.md). Use option A (tmux wrapper with graceful fallback). Read the design doc for the full spec, file pointers, lifecycle invariants, and smoke scenarios.

User UX refinement on top of the design

User quote 2026-04-29: 'should we make it so we ask on exit close (so we resume) or leave running unclear how to do that. also codex resume not great.'

Per-chat close-vs-leave prompt at TUI exit time. Reasoning:

  • Default 'leave running' is correct for the common case (resume next time, keep work alive across host crashes / SSH disconnects)
  • 'Close' is needed when codex resume would corrupt the session, OR when the user is genuinely done with that chat and wants a clean end
  • User flagging 'codex resume not great' means the 'close' option matters more than the design assumed

Spec

Default behavior (per design)

  • Chat tab spawn → tmux new-session -d -s wg-chat-<project>-<chat_ref> -- <vendor-cli> <args>
  • TUI's PtyPane wraps tmux attach -t <session_name> -d
  • Drop on the attach client does NOT kill the underlying tmux session (the persistence trick)
  • Archive/delete chat → kill_underlying_session() explicitly
  • Startup orphan sweep at fn ensure_user_coordinator (state.rs:13294) — kill any wg-chat-* session whose chat_ref doesn't resolve to a live task

NEW: exit prompt

On user-initiated TUI exit (Ctrl+C / 'q' / explicit quit), if there are 1+ active chat tabs:

Render an exit prompt:

You have N active chats. What do you want to do?

  [a] Leave all running (default — resume next time)
  [c] Close all (kill tmux sessions, can't resume)
  [s] Select per-chat (granular choice for each)
  [esc] Cancel exit
  • 'Leave all' (default, Enter or 'a'): TUI exits, tmux sessions survive, next wg tui reattaches
  • 'Close all' ('c'): TUI calls kill_underlying_session() on every chat, then exits. Next wg tui opens fresh chat tabs.
  • 'Per-chat' ('s'): step through each chat, prompt close/leave individually. Useful when the user wants to leave 1 codex chat running but close 3 stale claude chats.
  • 'Cancel' (Esc): abort the exit, return to TUI

If there are 0 active chats, no prompt — exit immediately.

If exit was triggered by a signal (SIGTERM, SIGHUP, SIGKILL — though SIGKILL is uncatchable), no prompt is possible. Default to 'leave running' (do nothing special on Drop, which the design already handles).

Codex resume integrity (related but separate)

The user's 'codex resume not great' comment is the original motivation for tmux persistence — by keeping codex alive across TUI exit, codex flushes its tool-call rollouts cleanly. The exit prompt's 'close' option is for when the user WANTS to abandon the session despite that.

If codex resume is observed to be flaky even WITH tmux persistence (rollout still corrupts in some path), file a follow-up bug — that's a codex CLI / wg integration issue, not this task's scope.

Implementation surface (per design)

  1. src/tui/pty_pane.rsspawn_via_tmux() constructor + kill_underlying_session() method. CRITICAL: Drop must NOT kill the tmux session, only the attach client.
  2. src/tui/viz_viewer/state.rs — chat spawn site routes through tmux when available, falls back to direct spawn with one-time stderr warning when tmux missing.
  3. src/tui/viz_viewer/state.rs — orphan sweep at startup (ensure_user_coordinator).
  4. src/tui/viz_viewer/state.rs (or new module) — exit prompt UX. Triggered on user-initiated quit when active chats > 0.
  5. src/tui/viz_viewer/state.rs (chat archive path) — kill_underlying_session() on archive/delete.
  6. src/commands/chat_cmd.rs (optional v1) — wg chat attach <chat_ref> for terminal-side reattach.
  7. src/commands/setup.rs — bump tmux description from 'needed for wg server' to 'needed for chat persistence'.

Validation

  • Failing tests written first (TDD)
  • Smoke scenarios from design (all 7) pass: chat_persists_across_tui_exit_claude.sh, chat_persists_across_tui_exit_codex.sh, chat_persists_mid_tool_call_codex.sh, chat_no_orphan_tmux_after_archive.sh, chat_orphan_tmux_swept_on_tui_start.sh, chat_falls_back_when_tmux_missing.sh, chat_attach_command_works.sh (if v1 ships)
  • NEW: chat_exit_prompt_leave_all.sh — exit prompt 'a' choice → TUI exits, tmux sessions intact
  • NEW: chat_exit_prompt_close_all.sh — exit prompt 'c' choice → TUI exits, all wg-chat-* tmux sessions killed
  • NEW: chat_exit_no_prompt_when_zero_chats.sh — TUI with no chat tabs exits immediately, no prompt
  • Live smoke: open a codex chat, exit TUI with 'leave all', restart TUI, confirm chat is reattached and shows prior conversation
  • Live smoke: open a codex chat, exit TUI with 'close all', restart TUI, confirm chat opens fresh (no resume needed)
  • Live smoke: SIGKILL the TUI mid-conversation, verify chat survives and reattaches correctly on restart (no prompt possible, default-survive path)
  • cargo build + cargo test pass
  • cargo install --path . was run before claiming done — and live smoke evidence pasted before claim of done

Process note

This is a substantial implementation. The Drop-doesn't-kill-the-session invariant is the single tricky thing per the design. Validate that invariant explicitly (e.g., a unit test that constructs a PtyPane via spawn_via_tmux, drops it, then asserts tmux has-session returns 0 i.e. session still exists).

Depends on

Required by

Log