fix(terminal): preserve agent paste mode across host restarts

Merged
KI
2mo ago
+1312-255

What & why

Sending New page to an existing Claude Code terminal can lose the beginning of the prompt after enough output and a host restart. #7670 routed callers through terminal.send, but paste framing still depended on recovering mode-setting escape sequences from a bounded replay buffer. Once those bytes were evicted, the host incorrectly treated paste mode as disabled.

Keep bounded input-mode state in the persistent PTY daemon and restore it through an explicit replay-complete checkpoint before attach or text input. Preserve parser state, keyboard stacks, and reclamation state during daemon handoff. The control scanner and replay completion lifecycle have separate responsibilities, and legacy-daemon reclamation is selected when the host tracker is constructed. Bump the daemon to 0.3.4 so existing supervisors discover the update.

Replay failures now return typed session errors across send, snapshot, and WebSocket attach. A failed replay drops only the incomplete host session, allowing the next request to re-adopt the existing PTY and obtain a fresh checkpoint. Timeout/disconnect errors remain retryable; protocol and callback errors remain visible permanent failures.

Old clients retain their protocol behavior. Modes already lost by a pre-upgrade daemon cannot be recovered from absent bytes; those sessions may need an agent restart.

How I tested it

  • Real UI before/after: Pages → New page → existing Claude → Send to agent, after 1,095,680 bytes of output and a host-worker restart. Before: 585/1,607 characters received. After: the exact complete 1,607-character prompt. Claude's usage limit prevented page generation, so this verifies delivery/submission, not publishing.
  • Before/after screenshots and detailed report (Superset organization access required). The lower message in the before screenshot is the failing send; the final shared-fix run uses a newer disposable Claude session.
  • 152 focused regression tests passed: 91 daemon unit tests, 25 host mode/replay unit tests, 29 native send/client/handoff tests, and 7 sequence-catchup tests. Includes differential mode parsing against pinned xterm, eviction, partial controls, delayed replay, failure handling, concurrent sends, and restart recovery.
  • Packaged handoff passed: built a local macOS app and exercised its packaged Electron executable, app.asar daemon entrypoint, and native node-pty. Three sessions retained their original PIDs, mode state, keyboard stacks, and working input through successor handoff. Signed installer/auto-update delivery was not tested.
  • Review follow-up: six new regression cases cover resident-session replay failure, concurrent adoption, and WebSocket attachment retry while preserving the original PTY PID. The three initial failure cases failed on the previous commit, then passed with the fix. All 39 native send/snapshot, daemon-client, and attach-retry tests passed.
  • bun run lint passed; bun run typecheck passed (42 tasks).
  • Fresh complete rerun: 164 targeted checks passed (91 daemon units, 25 host mode/replay units, 47 native tests, 1 packaged handoff). Lint and all 42 uncached typecheck tasks passed. The full repository suite, forced and run one package at a time with continue-on-failure, finished with 9,442 passes and 39 failures outside this patch: workspace-fs 1, host-service 4, desktop 34. Failures involve filesystem watchers, configured Claude account command expectations, Git status timeouts, and desktop Git/worktree/shell environment tests; no baseline comparison was performed. Initial concurrent native/handoff runs timed out; sequential reruns passed without code changes. All current CI checks are green after retrying a sandbox Docker-registry connection reset. UI before/after was not repeated in this pass; the linked screenshots remain the prior UI evidence.

Checklist

  • PR title follows conventional commits (type(scope): subject)
  • bun run lint and bun run typecheck pass (CI fails on lint warnings too)
  • "Allow edits from maintainers" is checked on fork PRs — not applicable; same-repository branch

Summary by cubic

Fixes "send to agent" dropping the beginning of long prompts in existing Claude Code terminals after a host restart. The host previously rebuilt terminal mode state from the daemon's bounded replay buffer; once the mode-setting bytes were evicted, paste mode looked disabled and paste framing was omitted. The daemon now keeps a bounded terminal-mode checkpoint and sends a replay-complete message so the host restores paste mode before attach or text input, including across daemon handoff.

Replay waits up to 5 seconds for that checkpoint; on timeout, disconnect, or a rejected subscription, the host returns a typed error, drops only the incomplete host session, and leaves the daemon PTY alive so the next request re-adopts it with a fresh checkpoint. Timeout and disconnect errors stay retryable; protocol and callback errors stay permanent. Subscribers joining after a completed replay re-request the checkpoint instead of waiting again.

Compatibility

  • Bumps @superset/pty-daemon to 0.3.4 so existing supervisors pick up the update.
  • Old clients keep their protocol behavior; mode snapshots are only requested after the new handshake.
  • Modes already lost by a pre-upgrade daemon cannot be recovered, so those sessions may need an agent restart.

Summary by CodeRabbit

  • New Features

    • Terminal sessions now preserve terminal modes, including bracketed paste, mouse settings, alternate screen, and keyboard protocols, during reconnects and handoffs.
    • Session replay now signals when restored output and terminal state are ready, improving continuity for existing sessions.
  • Bug Fixes

    • Bracketed-paste input remains correctly framed after service restarts and large amounts of terminal output.
    • Terminal mode state is restored even when the original control sequences are no longer retained in replayed output.
    • Temporary replay or daemon interruptions now support reliable terminal reattachment and retry.