Skip to content

feat(agents): adopt idle external claude sessions via claude --resume (Phase 6)#111

Merged
oratis merged 1 commit into
mainfrom
feat/adopt-idle-sessions
Jun 18, 2026
Merged

feat(agents): adopt idle external claude sessions via claude --resume (Phase 6)#111
oratis merged 1 commit into
mainfrom
feat/adopt-idle-sessions

Conversation

@oratis

@oratis oratis commented Jun 18, 2026

Copy link
Copy Markdown
Owner

What

The headline gap from the control plane: command sessions LISA did not spawn — your own claude sessions from the app/terminal.

You can't tap a live session (the Claude app owns its stdin/stdout pipe — confirmed by inspecting the running processes: every session is driven over --input-format stream-json). But every session has a stable sessionId, and claude --resume <id> continues that exact transcript in a fresh process. So:

  • An idle claude session in the agents card now shows an ⇲ adopt button → LISA spawns claude --resume <id> under its own PTY → you drive a continuation (send / answer / cancel / ▤ output). New turns append to the same transcript (visible in the app's history too).

Safety — this touches real session data

  • Liveness guard: liveClaudeSessionIds() reads ~/.claude/sessions/<pid>.json and checks the pid; only sessions with no running owner are marked resumable. The start endpoint 409s on a live session — two writers to one JSONL transcript interleave and corrupt it.
  • Smoke-tested (no mutation): adopting this live session → 409; /api/agents/sessions correctly split 9 claude rows into 5 idle / 4 live; missing-task → 400.
  • Honest limit (documented): a session open+running in the app right now still can't be commanded from outside — close it, then adopt. (Live control would need the undocumented peerProtocol; not touched.)

Implementation

  • src/integrations/claude-code/liveness.tsliveClaudeSessionIds() + pidAlive().
  • pty.tsPtyStartOpts.resumeSessionId--resume <id> (claude only); detectClaudeBinary() prefers LISA_PTY_CLAUDE_CMD → newest app-bundled claude (version-matched to your sessions) → PATH claude.
  • AgentSession.resumable; /api/agents/sessions enriches idle claude rows; /api/agents/pty/start accepts resumeSessionId with the 409 guard (task optional when resuming).
  • GUI: ⇲ adopt button on resumable rows (errors surface in the modal). Snapshot updated.
  • docs/PTY_AGENTS.md.

Verification

719 tests pass, 1 skip (real-node-pty round-trip); typecheck + build clean; live endpoints smoke-tested as above.

Still flagged behind LISA_PTY_AGENTS=1 (it spawns real claude under a PTY).

🤖 Generated with Claude Code

…e` (Phase 6)

The headline gap from the control plane: command sessions LISA did NOT spawn —
the user's own claude sessions from the app/terminal. A live session can't be
tapped (the app owns its stdin/stdout pipe), but every session has a stable id
and `claude --resume <id>` continues that exact transcript in a fresh process.

So: an IDLE claude session in the roster gets an ⇲ adopt button → LISA spawns
`claude --resume <id>` under its own PTY → you drive a continuation of that
conversation (send / answer / cancel / view output); new turns append to the same
transcript (visible in the app's history too).

Safety (this touches the user's real session data):
- Liveness guard: liveClaudeSessionIds() reads ~/.claude/sessions/<pid>.json and
  checks the pid; only sessions with no running owner are marked `resumable`.
  The start endpoint 409s on a live session — two writers to one JSONL transcript
  interleave and corrupt it. Smoke-tested: adopting THIS live session → 409;
  /api/agents/sessions correctly split 9 claude rows into 5 idle / 4 live.
- Honest limit (documented): a session open+running in the app right now still
  can't be commanded from outside — close it, then adopt. (Live control would
  need the undocumented peerProtocol; not touched.)

Implementation:
- src/integrations/claude-code/liveness.ts — liveClaudeSessionIds() + pidAlive().
- pty.ts — PtyStartOpts.resumeSessionId → `--resume <id>` (claude only);
  detectClaudeBinary() prefers LISA_PTY_CLAUDE_CMD → newest app-bundled claude
  (version-matched) → PATH claude.
- AgentSession.resumable; /api/agents/sessions enriches idle claude-code rows.
- /api/agents/pty/start accepts resumeSessionId with the 409-on-live guard;
  task optional when resuming.
- GUI: ⇲ adopt button on resumable rows (errors surface in the modal).
- docs/PTY_AGENTS.md.

Tests: 719 pass / 1 skip. typecheck + build clean. Snapshot updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oratis oratis merged commit 432c56a into main Jun 18, 2026
1 check passed
@oratis oratis deleted the feat/adopt-idle-sessions branch June 18, 2026 12:40
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