feat: cli-agent executor — interactive CLI coding agents in tasks and chat#1446
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (U16) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Engine-owned PTY lifecycle for CLI agent sessions: - adapter.ts: CliAgentAdapter interface (launch/env-allowlist builders, capability flags, readiness detection, injection formatter, resume builder, telemetry wiring) + CliAdapterRegistry with typed unknown/duplicate errors. - session-manager.ts: CliSessionManager owning node-pty processes via the U16 shared loader. Byte-bounded scrollback ring (default ~512KB), single serialized write queue shared by injections + user input (FIFO, deferral in quiet windows), latest-active-client resize, scoped-SIGKILL process registry on process exit (never port 4040), explicit async attach interface (scrollback + AsyncIterable<Uint8Array> + write/resize/detach), requestPause/requestResume watermark hooks, separate concurrency pool with typed CliConcurrencyLimitError at the ceiling. - Security: bracketed paste only when ?2004h observed; unconditional control-char neutralization on the raw path; user keystrokes bypass neutralization. - Persists lifecycle into the U1 CliSessionStore (create on spawn, update state/termination). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure engine code (no HTTP) for the CLI agent executor: - state-machine.ts: authoritative per-session state machine implementing the HTD diagram (starting→ready→busy⇄waitingOnInput→done; done→busy follow-up; dead-classification choice → killed/userExited/authFailed/resuming; resume cap of 2 with backoff → needsAttention). Positive completion is distinct from idle (idle never produces done); inactivity stall backstop re-armed by output/ telemetry events (no fixed turn timeout); termination classification helper for all five paths; per-turn latches reset between turns. Persists every transition via CliSessionStore (the transient `resuming` machine state maps onto the U1 `dead` store enum) and exposes a throttled `onStateChange` subscription for the later SSE bridge — no dashboard imports. - telemetry-hub.ts: in-process ingestion contract (ingest(sessionId, event)) for the U17 route and log-tailing adapters. Mints high-entropy per-session hook tokens (issueToken/validateToken/invalidate); rebuilds the registry only from live sessions in CliSessionStore so stale tokens for non-live sessions never validate; a token validates only for its own session. Bounds everything ingested: per-event size caps, per-turn count caps (lifecycle events exempt), ANSI/control stripping before pattern matching, and secret redaction that survives chunk boundaries via a held-back carry window (redactSecrets from @fusion/core). Tests: 35 new (state-machine.test.ts, telemetry-hub.test.ts) covering AE1/AE2, stall backstop, all termination paths, resume caps, token registry, two-turn latch reset, oversized capping, ANSI stripping, and cross-chunk redaction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…scripts (U17) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E state, output hardening (U10) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, output hardening) # Conflicts: # packages/engine/src/index.ts
Suspend-and-handoff passthrough from the Ink TUI to a cli-agent PTY session: mint an attach ticket, open the cli-session WebSocket, enter the alternate screen + raw mode, stream WS scrollback/data frames to stdout, frame stdin bytes into input messages, propagate resizes, and ACK consumed bytes for flow control. Detach chord Ctrl-] restores the terminal and remounts Ink; a dropped WS surfaces the error and restores the terminal cleanly. Untrusted terminal output is neutralized through the same hardening filter the dashboard WS bridge uses (re-exported from @fusion/dashboard) so OSC 52, non- http(s) OSC 8 links, and device-status queries are stripped before reaching the host TTY — the riskiest leg, since the host terminal honors more sequences than xterm.js. CJK/double-width bytes pass through verbatim. - packages/cli/src/commands/dashboard-tui/terminal-attach.ts (passthrough loop + injectable WS transport for tests) - controller.openTerminalAttach() Ink integration (unmount/run/remount) - adds `ws` runtime dep to packages/cli - re-exports neutralizeTerminalOutput/flushTerminalOutput from @fusion/dashboard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex (hybrid tier): native done via session-scoped notify config, heuristic PTY waiting detection, codex resume <thread-id>, probed rollout JSONL tailer. Droid (native tier): Claude-style hooks with a Notification permission-vs-idle classifier, --resume / exec -s resume (never -r in exec mode). Pi (native tier): session-JSONL telemetry + transcript tailing, pi --session resume. Adds the session-jsonl transcript source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xec -r footgun guarded)
… (U7) Add `cli-agent` as a task-execute executor kind. A workflow node with `config.executor === "cli-agent"` drives an engine-owned CLI coding agent through the execute step via the new cli-agent/task-session.ts orchestration: spawn in the worktree, issue the hook token + write hook scripts, inject the prompt after readiness, subscribe to the state machine, and resolve on a positive completion signal (R20 gating). Config is snapshotted at launch; the PTY is reaped (completed) at the in-review handoff. Executor seam: runGraphCustomNode gains a cli-agent branch delegating to runCliAgentNode; the hard-cancel/abort path (awaitAbortInFlightTaskWork + abortAllInFlight) claims and SIGKILLs the CLI session as a first-class surface, marking it killed (never resume-eligible). Re-entry kills any prior live session and launches fresh; follow-up resumes the recorded native session id when supported. A PTY-pool ceiling surfaces as a typed task value, not a stall. Node-config typing extended minimally (WorkflowNodeExecutorKind / WorkflowNodeExecutorConfig in @fusion/core). Tests: cli-agent/__tests__/task-session.test.ts (12) and __tests__/cli-agent-executor.test.ts (9) cover AE1/AE5, hard cancel, re-entry, follow-up, config snapshot, ceiling, and the generic-tier confirm-advance path, using scripted adapters + a mock PTY seam. Engine typecheck clean; full src/cli-agent suite + new tests green (144 passing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d CE (U9) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on (U8) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…itor config (U15) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation banner support (U11) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… keys (U13) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oggle (U12) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le engine mocks (U12 completion) Mount CliChatSurface in ChatView for cli-backed chat sessions (sessions carrying cliExecutorAdapterId): the message-pane + composer region is delegated to the surface (transcript/raw-terminal toggle for hybrid/native adapters, terminal-only for the generic adapter), while regular sessions keep the standard composer. The existing message list and composer JSX are captured once as render thunks and passed through, so there is no parallel message/composer UI. Add a narrow telemetry seam: TelemetryHub gains an optional onEvent tap (also settable post-construction via setEventListener) invoked with each sanitized event after routing — best-effort, a throwing listener never breaks ingest. This is the seam the CliChatSessionRunner uses to build the durable transcript from the same sanitized events the hook route already feeds the hub, without the hub becoming a general subscriber bus. Fix the stale @fusion/engine vi.mocks across dashboard tests: object-literal mocks that fully replace the module now also return listCliAdapterDescriptors (added by U15's cli-agent-settings route, evaluated at module load). Mocks that spread importOriginal/importActual already pick it up. Tests: new ChatView.cli-mount.test.tsx (cli session → CliChatSurface, regular session → normal composer, generic → terminal-only); telemetry-hub onEvent tap coverage. chat-attachment-routes, chat-cli-sessions, cli-agent-hooks-route, ChatView.cli-toggle all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… and recovery seams Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c double-wrap, follow-up resolution Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 169 files, which is 19 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (169)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The cli-agent executor feature added engine.getCliAgentRuntime() (called by the CLI dashboard command at load) and bumped @fusion/core SCHEMA_VERSION 108→110. Update the stale test stubs: - cli dashboard.test.ts: add getCliAgentRuntime() to the mock FnAgent class (returns undefined; runDashboard handles the no-runtime path). Fixes 52 failures all rooted at `cwdEngine?.getCliAgentRuntime is not a function`. - roadmap-store.test.ts: the roadmap store layers on core's Database, so its schema version tracks core; assert 110 instead of 108. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- TaskFieldsSection.css: restore the toggle knob fill to the themed
var(--card) token (was hardcoded to #fff on this branch), which the
AgentListModal theme-token guard scans all app CSS for and rejects.
- ListView.test.tsx: add fetchBoardWorkflows + api to the ../../api mock;
TaskDetailModal (rendered via the embedded detail) now calls both at mount
for board workflows and the cli-sessions lookup, so the object-literal mock
must provide them ("No X export is defined on the ../../api mock").
- auto-merge-toggle-blank.mobile.test.tsx / board-mobile-initial-render.test.tsx:
revert the runOnlyPendingTimers→runAllTimers change; with the installed rAF
shim that re-schedules itself, runAllTimers loops forever and trips vitest's
10000-timer infinite-loop abort.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
…fore merge) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rface # Conflicts: # packages/dashboard/app/components/__tests__/ListView.test.tsx
…rface # Conflicts: # CONCEPTS.md # packages/core/src/__tests__/db-migrate.test.ts # packages/core/src/__tests__/db.test.ts # packages/core/src/__tests__/goals-schema.test.ts # packages/core/src/__tests__/insight-store.test.ts # packages/core/src/__tests__/merge-request-record.test.ts # packages/core/src/__tests__/mission-store.test.ts # packages/core/src/__tests__/run-audit.test.ts # packages/core/src/__tests__/store-merge-queue.test.ts # packages/core/src/__tests__/task-documents.test.ts # packages/core/src/db.ts # packages/dashboard/src/__tests__/agent-onboarding.test.ts # packages/dashboard/src/__tests__/ai-refine.test.ts # packages/dashboard/src/__tests__/chat-attachment-routes.test.ts # packages/dashboard/src/__tests__/experiment-routes.finalize.test.ts # packages/dashboard/src/__tests__/milestone-slice-interview.test.ts # packages/dashboard/src/__tests__/mission-interview.test.ts # packages/dashboard/src/__tests__/pr-metadata-generator.test.ts # packages/dashboard/src/__tests__/project-pause-resume-routes.test.ts # packages/dashboard/src/__tests__/routes-approval-sandbox-provisioning.test.ts # packages/dashboard/src/__tests__/routes-approval-secrets.test.ts # packages/dashboard/src/__tests__/routes-approval.test.ts # packages/dashboard/src/__tests__/routes-worktrunk.test.ts # packages/dashboard/src/__tests__/session-error-recovery.test.ts # packages/dashboard/src/__tests__/session-persistence-roundtrip.test.ts # packages/dashboard/src/__tests__/session-reconnect.test.ts # packages/dashboard/src/__tests__/session-resume-history.test.ts # packages/dashboard/src/__tests__/setup-routes.test.ts # packages/dashboard/src/__tests__/subtask-breakdown.test.ts # packages/dashboard/src/routes/__tests__/agent-avatar-routes.test.ts # packages/dashboard/src/routes/__tests__/custom-providers.test.ts # packages/dashboard/src/routes/__tests__/docker-node-routes.test.ts # packages/i18n/locales/en/app.json # plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts
… gsxdsm/cli-agent-interface
…migration to 112 behind main's cli_sessions(110)/adapter(111), full-workspace literal sweep, i18n union
The .coderabbit.yaml added to PR Runfusion#1446 was a temporary review-scoping config (excludes tests/docs/locales to fit CodeRabbit's file cap). It was meant to be dropped before merge but leaked onto main when Runfusion#1446 merged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a third task executor type,
cli-agent, that runs Fusion agent sessions inside engine-owned PTYs running interactive CLI coding agents (Claude Code, Codex, Droid, Pi). The engine injects prompts, tracks agent state through each CLI's native telemetry (Orca-style hooks), captures native session IDs for resume, and drives the full task pipeline — while users co-drive through live terminals on the dashboard, mobile, and TUI. Chat gains a CLI-backed hybrid-transcript mode.Gated behind
experimentalFeatures.cliAgentExecutor(default OFF); the subsystem is inert until enabled.Origin:
docs/brainstorms/2026-06-04-cli-executor-requirements.md· Plan:docs/plans/2026-06-04-002-feat-cli-agent-executor-plan.mdWhat's included (Implementation Units U1–U17)
cli_sessionsschema v109 + store (U1),CliSessionManager+ adapter interface with injection FIFO / concurrency pool / scoped-SIGKILL teardown (U2), telemetry hub + session state machine with termination taxonomy and stall backstop (U3).claude2.1.165), Codex hybrid / Droid / Pi (U5, binaries probed), generic heuristic tier (U6).Testing
Known Residuals
Post-Deploy Monitoring & Validation
No additional operational monitoring required for merge — the feature ships flag-off (
experimentalFeatures.cliAgentExecutor). When enabling in an environment: watch engine logs forcli-agentsession spawn/resume errors andneedsAttentiontransitions, confirm no orphaned PTY processes after engine restart, and verify port 4040 is never targeted by session teardown.🤖 Generated with Claude Code
Before merge
.coderabbit.yaml(temporary review-scope config; must not land onmain).