Skip to content

feat(agents): PTY-agent spike — steer real claude/codex CLIs (Phase 5, flagged)#110

Merged
oratis merged 1 commit into
mainfrom
feat/pty-agents-spike
Jun 18, 2026
Merged

feat(agents): PTY-agent spike — steer real claude/codex CLIs (Phase 5, flagged)#110
oratis merged 1 commit into
mainfrom
feat/pty-agents-spike

Conversation

@oratis

@oratis oratis commented Jun 18, 2026

Copy link
Copy Markdown
Owner

What

Stage C of the agent control plane — the path toward commanding real claude / codex CLIs, not just LISA's own managed agents. Experimental, off by default.

A managed agent (#108) runs LISA's own loop. A PTY agent instead spawns the real claude/codex binary under a pseudo-terminal (node-pty), so you get that CLI's full config — skills / MCP / hooks / model — while LISA owns stdin/stdout: it types your task + follow-ups, can answer prompts, and reads the stream for a coarse state + a viewable output tail. They appear in the roster under their real kind (claude-code/codex), marked controllable: type-into-the-CLI, ▤ output, ⏹ cancel.

Why it's safe to merge while off

  • node-pty is an optionalDependency — zero JS deps, no new vulnerabilities (the tree's existing 8 are unrelated). Installs + CI never fail if it can't build; PTY agents are simply unavailable then (dynamic import → graceful fallback).
  • Off unless LISA_PTY_AGENTS=1 — the start endpoint 503s otherwise (the GUI surfaces the hint). The hub observer is inert at rest (registry stays empty). All endpoints sit behind the standard loopback-or-token auth gate.
  • Honest limit, documented: controls only CLIs LISA spawns — your already-open terminal sessions stay observe-only (no control channel; that would need Claude Code's undocumented peerProtocol).
  • Privacy preserved: the captured terminal (content) is served only on demand via GET /api/agents/pty/<id>/output and is never folded into the structural cross-agent roster.

Implementation

  • src/agents/pty.tsPtyAgent + PtyRegistry; pure stripAnsi (color / OSC-8 hyperlinks / bare control bytes — built from String.fromCharCode so the source carries no raw control bytes), derivePtyState (quiescence → state), resolveCli; flag gate.
  • src/integrations/pty/observer.ts + registry/hub wiring.
  • AgentSession.controllable ("managed"|"pty") unifies which control-endpoint family the UI drives; managed observer now sets controllable:"managed".
  • src/web/server.tsPOST /api/agents/pty/{start, <id>/send, <id>/cancel} + GET /<id>/output.
  • GUI — delegate kind picker (managed/claude/codex) + controllable-family row controls (approve/deny stays managed-only). Snapshot recomputed.
  • docs/PTY_AGENTS.md.

Verification

  • 714 tests pass, 1 skip (the real-node-pty round-trip via cat skips when the native dep can't spawn under the tsx loader — a runner artifact; the shipped path runs against compiled JS). npm run typecheck + npm run build clean; MAIN_HTML parses + snapshot updated.

Result — control plane complete (per plan)

  1. See every agent (Phase 1).
  2. Command LISA-run managed agents end-to-end (Phase 3).
  3. Spawn + steer real claude/codex CLIs under a PTY (this PR, flagged).

Remaining: Phase 4 (advisor one-click) — small, lower-value given the external-session identity mismatch.

🤖 Generated with Claude Code

…, flagged)

Stage C of the agent control plane: the path toward commanding REAL claude /
codex CLIs, not just LISA's own managed agents. EXPERIMENTAL, off by default.

A managed agent (Phase 3) runs LISA's own loop. A PTY agent instead spawns the
real `claude`/`codex` binary under a pseudo-terminal (node-pty), so you get that
CLI's full config (skills/MCP/hooks/model) while LISA owns stdin/stdout: it types
your task + follow-ups, can answer prompts, and reads the stream for a coarse
state + a viewable output tail. They show in the roster under their real kind
(claude-code/codex), marked controllable.

Safety / honesty:
- node-pty is an OPTIONAL dependency (zero JS deps, no new vulns); installs + CI
  never fail if it can't build — PTY agents are just unavailable then.
- Off unless LISA_PTY_AGENTS=1; the start endpoint 503s otherwise (GUI surfaces
  the hint). All endpoints sit behind the standard loopback-or-token auth gate.
- Controls only CLIs LISA SPAWNS — your already-open terminal sessions stay
  observe-only (no control channel). Documented, not hidden.
- Privacy: the captured terminal (content) is served only on demand via
  /api/agents/pty/<id>/output and is NEVER folded into the structural roster.

Implementation:
- src/agents/pty.ts — PtyAgent + PtyRegistry; pure stripAnsi (color/OSC-8/control,
  built from String.fromCharCode so the source carries no raw control bytes),
  derivePtyState (quiescence→state), resolveCli; dynamic node-pty import with
  graceful fallback; flag gate.
- src/integrations/pty/observer.ts + registry/hub wiring — surfaces PTY agents in
  the hub roster (real kind, controllable:"pty").
- AgentSession.controllable ("managed"|"pty") unifies which control-endpoint
  family the UI drives; managed observer now sets controllable:"managed".
- src/web/server.ts — POST /api/agents/pty/{start,<id>/send,<id>/cancel} +
  GET /<id>/output.
- GUI: delegate kind picker (managed/claude/codex) + controllable-family row
  controls (send/output/cancel; approve/deny stays managed-only). Snapshot updated.
- docs/PTY_AGENTS.md.

Tests: 714 pass / 1 skip (the real-node-pty round-trip skips when the native dep
can't spawn under the tsx loader). typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oratis oratis merged commit a600110 into main Jun 18, 2026
1 check passed
@oratis oratis deleted the feat/pty-agents-spike branch June 18, 2026 11:56
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