Skip to content

feat(agents): pocket-control CLI + dispatch list + iOS companion plan#113

Merged
oratis merged 1 commit into
mainfrom
feat/ios-companion-and-pty-cli
Jun 19, 2026
Merged

feat(agents): pocket-control CLI + dispatch list + iOS companion plan#113
oratis merged 1 commit into
mainfrom
feat/ios-companion-and-pty-cli

Conversation

@oratis

@oratis oratis commented Jun 19, 2026

Copy link
Copy Markdown
Owner

What

Two things, both driven by a new iOS companion design.

Designdocs/IOS_COMPANION_PLAN.md: a full plan for an iOS app that turns the phone into a remote telemetry + control terminal for Dispatch (see/command Claude Code · Codex · OpenCode · Aider), plus the other pillars (chat, mood, Reve recap, Sense consent). Covers connectivity (Tailscale-first), QR pairing, push (operational + opt-in, not proactive outreach), Live Activity / Dynamic Island, and — the hard part — controlling sessions LISA didn't start: a live external session is untappable (the Claude desktop app owns its stream-json pipe), so the real answer is adopt-at-launch + resume-adopt of idle sessions (claude --resume, guarded against live ones).

Code — three backend increments the plan leans on, all behind the existing loopback-or-token auth gate:

  • lisa agents pty <agent> <task>adopt-at-launch: a thin client to the running lisa serve --web that spawns the real CLI under the server's PTY, so the session is born controllable:"pty" (in the roster, steerable from island/GUI/phone), mirrors output here (SSE), and forwards each typed line. --resume <id> bridges to the resume-adopt path (continue an idle session; the server 409s a live one).
  • GET /api/agents/pty/<id>/stream — live PTY output SSE (snapshot then chunks; ends when the agent finishes).
  • GET /api/dispatch/list — structured view of the dispatch ledger (complements /api/agent/signal's prose list); reduces logPath to a boolean so the raw path never leaks.

Notes

Verification

  • npm run typecheck + npm run build clean.
  • 728 tests / 727 pass / 1 skip / 0 fail (the skip is the real-node-pty round-trip).
  • Live-checked /api/dispatch/list and /api/agents/sessions against a running server (the latter shows externally-started claude-code sessions as observe-only — no controllable — confirming the honesty distinction).
  • ⚠️ The live PTY spawn path can't run locally: node-pty throws posix_spawnp failed under Node v26 (CI uses Node 24). The logic is unit-tested with an injected fake pty.

Follow-ups

Backend — finish the control story

  • remoteAdoptExternal / remoteControl gating (/api/control/policy): remote (phone) callers must not adopt/control external sessions without a Mac-side opt-in + per-action confirm. (security gap — do first)
  • Raw-mode terminal attach for lisa agents pty (keystroke passthrough + resize) so it can drive full TUIs.
  • Roster de-dup: a resume-adopted PTY session is also seen by the claude-code on-disk observer → the same real session double-counts; dedupe by sessionId/cwd.
  • GET /api/dispatch/status?id= — gated log-tail readback (structured list exists; raw stdout is separate + gated).
  • Investigate Codex resume parity (resume-adopt is claude-only today).

iOS app (the companion itself)

  • SwiftUI skeleton: 5 tabs (Dispatch / Chat / Reve / Sense / Settings) + Net layer (LisaClient, SSE parser, models incl. controllable/resumable).
  • QR pairing + Keychain + per-device tokens (/api/pair/start, /api/devices[/revoke]).
  • Push: APNs registration (/api/push/register) + Mac-side push-bridge + trigger matrix (done/error/permission/idle) + E2E payload (minimal relay default; self-host ntfy option).
  • System surfaces: Live Activity + Dynamic Island; WidgetKit (small/medium); notification deep-links.
  • Control UI keyed off controllable/resumable: managed approve/deny/send/cancel; pty send/output/cancel; resumable → "adopt" button.
  • Connectivity UX: Tailscale onboarding; LAN-direct fallback; (later) optional E2E relay.

Polish / cross-cutting

  • M0 PWA polish (cheap, ships now): mobile viewport, add-to-home, Tailscale docs.
  • Optional "remote read-only chat" mode (today /chat is full-tool).
  • Document the Node 26 / node-pty local-dev caveat.

🤖 Generated with Claude Code

- lisa agents pty <agent> <task>: adopt-at-launch — spawn the real claude/codex under the running server's PTY so the session is born controllable (roster / island / phone). --resume <id> bridges to #111's resume-adopt (continue an IDLE session; the server 409s a live one).

- GET /api/agents/pty/<id>/stream: live PTY output SSE (snapshot + chunks; ends when the agent finishes) — drives the local attach mirror.

- GET /api/dispatch/list: structured view of LISA's fire-and-forget dispatches (complements /api/agent/signal's prose list); never leaks the raw log path.

- docs/IOS_COMPANION_PLAN.md: full iOS companion design — Dispatch telemetry + control, connectivity (Tailscale-first), QR pairing, push, and controlling non-LISA sessions (adopt-at-launch + resume-adopt; live sessions are untappable).

Verification: typecheck + build clean; 728 tests / 727 pass / 1 skip / 0 fail. Live PTY spawn needs node-pty, which fails under local Node 26 (CI = Node 24) — logic is unit-tested via an injected fake pty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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