feat(desktop,host-service): resume killed agent sessions by id

Merged
KI
3mo ago
+3694-85

Summary

Killed agent sessions are now resumable. When an agent terminal dies without the agent's own goodbye — laptop reset, pty-daemon killed, crashed pty — the pane shows a " was interrupted [Resume]" banner that relaunches the agent with its resume command (e.g. claude --resume <session-id>) in the same pane, restoring the full conversation.

Resume command plumbing (the promptArgs pattern)

  • resumeCommand on the builtin agent catalog, derived to resumeArgs on presets and stored on host_agent_configs (resume_args_json). All defaults verified against the installed CLIs: claude --resume, codex resume (root flags before subcommand confirmed), amp threads continue, opencode/pi/kimi --session, mastracode --thread, --resume for gemini/copilot/vibe/grok/cursor-agent/droid.
  • agents.run accepts resumeSessionId (prompt optional when resuming); validated against the config's resume args. Exposed via CLI (superset agents create --resume-session), MCP agents_create, and the SDK.
  • Settings → Agents gets a "Resume args" field; custom migration backfills existing installs' rows still at the untouched default.

Retention: bindings survive death

terminal_agent_bindings rows are retained with ended_at/end_reason instead of hard-deleted, so agent_session_id survives:

  • "detached" = the agent reported its own end (SessionEnd hook) → not resumable
  • "terminal-exited" = the terminal died under it → resume candidate

Marked at every death path: SessionEnd hooks, killSession, pty exit, daemon disconnect (sweeps all live sessions), cold respawn, and a boot backfill. Live reads exclude ended rows; a new session in the same terminal revives the row.

Death-gasp rule: claude runs SessionEnd hooks on SIGHUP, so a daemon kill initially looked like a clean quit and blocked resume. A detach recorded within 30s of the terminal's death now upgrades to a resume candidate, and terminal-exited is sticky against late goodbyes.

Kill hooks

  • Claude Code already registers SessionEnd — no change needed.
  • Codex has no session-end hook; its wrapper (the only one that survives the process) now reports a synthesized SessionEnd only on normal exit (status < 128). Signal deaths stay unreported so the session remains a resume candidate.

Pane banner

New terminalAgents.resumeCandidate query + TerminalAgentResumeBanner in the terminal pane. Resume re-points the same pane at the new terminal (setPaneData, the session-dropdown pattern) and silently disposes the dead session.

Testing

Unit: ~1550 tests across host-service/shared/agent-setup/CLI, including resume arg building, retention truth table (death-gasp upgrade, sticky terminal-exited, upsert revival), config CRUD, and codex wrapper exit-reporting (clean exit reports, SIGHUP death doesn't).

CDP end-to-end in the real dev desktop app (real clicks, screenshots + DB checks at each step), re-run after merging main:

  1. Settings shows Resume args with backfilled defaults on a real pre-existing DB.
  2. pty kill (kill -9 claude+shell): banner appears on dead pane → Resume → conversation restored (codeword check).
  3. daemon SIGKILL (the headline case): pane cold-respawns, banner appears → Resume → conversation restored.
  4. /exit control: binding marked detached, no banner.

Host-service migrations were regenerated to land after main's 0015/0016 (0017 schema + 0018 backfill).


Summary by cubic

Resume killed agent sessions by session id. The terminal shows a Resume banner and relaunches the agent in the same pane, with stricter daemon-loss handling to avoid false resumes.

  • New Features

    • Desktop: Resume banner in terminal panes; resumes in place (e.g. claude --resume <id>, codex resume, amp threads continue).
    • Configs: Per‑agent resume args added to presets/configs with a Resume args field in Settings; defaults are backfilled.
    • APIs/CLIs: agents.run supports resumeSessionId; CLI adds --resume-session and makes --prompt optional when resuming; MCP agents_create and the SDK accept it. Docs clarify the id is the agent’s own.
    • Persistence/UI: Host-service retains bindings with ended_at/end_reason and exposes terminalAgents.resumeCandidate to power the banner; Codex wrapper reports SessionEnd only on clean exit.
  • Reliability

    • Daemon-loss sweep waits for a daemon response, verifies session ownership using a captured agent session id, and marks only bindings it no longer owns. It requires a concrete baseline id, skips candidates when capture fails or the id changes during the probe, logs the terminal id on failures, and falls back to mark all with one contextual warning if no daemon answers.
    • “Death-gasp” rule: a detach within 30s of terminal death upgrades to terminal-exited, stays sticky against late goodbyes, and blocks straggler hooks from clearing resume state.
    • Excludes sessions that never received a prompt (only “Attached”), preventing resumes that would restore an empty conversation.

Summary by CodeRabbit

  • New Features

    • Resume interrupted agent sessions directly from terminal panes with a dismissible prompt.
    • Configure resume arguments for built-in and custom agents.
    • Resume sessions through the CLI, SDK, and agent creation tools.
    • Detect and restore eligible terminal sessions.
  • Bug Fixes

    • Sessions terminated by signals no longer emit clean-exit notifications.
    • Improved tracking, cleanup, and preservation of ended sessions.
    • Added validation for missing prompts and invalid resume requests.
    • Improved recovery after daemon disconnects.