/** * acp/host.ts, HOSTING third-party coding agents over the Agent Client * Protocol. * * The existing acp/ modules make GoodVibes an ACP *agent* (agent.ts) and spawn * short-lived ACP *subagents* (connection.ts/manager.ts). This module is the * daemon-side HOST: it discovers installed third-party coding agents (Claude * Code, Codex CLI, opencode), spawns one over stdio as a LONG-LIVED session, * and exposes the lifecycle a fleet row needs, prompt (steer), stop, and the * waiting-on-human attention states, so a hosted agent is visible, steerable, * and stoppable exactly like a native row. * * Honesty contract: * - Discovery is READ-ONLY (PATH + known install directories; no execution). * Absence is quiet, an empty list, never a nag. * - A binary that fails the ACP handshake yields a STRUCTURED error (which * binary, which stage, what happened) on a 'failed' record, never a hung * row. Spawn/initialize/session are bounded by a handshake timeout. * - Permission requests from the hosted agent flow through the injected * permission handler (the daemon wires its shared approval broker) and the * record reads 'awaiting-approval' while one is pending, so the fleet * attention classification (glyph/count/jump/push) is inherited for free. */ import type { PermissionRequestHandler } from '../permissions/prompt.js'; /** One known third-party ACP-capable coding agent and how to launch its ACP mode. */ export interface KnownAcpAgent { readonly id: string; readonly title: string; /** Launch candidates, first found wins: binary name + the args that start ACP stdio mode. */ readonly candidates: ReadonlyArray<{ readonly binary: string; readonly args: readonly string[]; }>; } /** * The known agents table. The ACP launch shape per agent: * - Claude Code speaks ACP through its dedicated adapter binary * (`claude-code-acp`, the officially published bridge). The bare `claude` * binary is deliberately NOT a candidate: it has no ACP mode (verified * live), and advertising it would offer a spawn that always fails. * - Codex CLI exposes `codex acp` (experimental) on recent builds. * - opencode serves ACP via `opencode acp` (verified live end-to-end). * A wrong/outdated launch shape is not a hazard: the handshake timeout turns * it into a structured 'failed' record, never a hung row. */ export declare const KNOWN_ACP_AGENTS: readonly KnownAcpAgent[]; /** A discovered, spawnable third-party agent: which binary resolved and how to launch it. */ export interface DiscoveredAcpAgent { readonly id: string; readonly title: string; readonly binaryPath: string; readonly args: readonly string[]; } /** Injectable probes so discovery is testable without touching the real filesystem. */ export interface DiscoveryIo { readonly fileExists: (path: string) => boolean; readonly envPath: () => string; readonly home: () => string; } /** * Discover installed third-party ACP-capable agents. READ-ONLY: existence * checks over $PATH entries and known install directories, no process is ever * executed. Returns only what is present; absence is a quiet empty list. */ export declare function discoverAcpAgents(io?: DiscoveryIo): DiscoveredAcpAgent[]; /** Lifecycle state of a hosted third-party agent session. */ export type HostedAcpState = 'starting' | 'idle' | 'prompting' | 'awaiting-approval' | 'failed' | 'stopped'; /** The structured, user-renderable handshake/spawn failure, never a bare string. */ export interface AcpHostError { /** The binary that was launched. */ readonly binary: string; /** Which stage failed: spawning the process, the ACP initialize, or session creation. */ readonly stage: 'spawn' | 'initialize' | 'session' | 'prompt'; readonly message: string; } /** One hosted third-party agent session, as the fleet adapter reads it. */ export interface HostedAcpAgent { readonly id: string; readonly agentId: string; readonly title: string; readonly binaryPath: string; readonly cwd: string; readonly state: HostedAcpState; readonly startedAt: number; readonly completedAt?: number | undefined; /** The daemon shared-session id this hosted agent is mapped onto. */ readonly sessionId?: string | undefined; /** Latest streamed output tail (bounded), for the row's activity line. */ readonly progress?: string | undefined; /** Present while a permission ask is pending, the attention detail. */ readonly pendingPermission?: string | undefined; /** Present when state === 'failed'. */ readonly error?: AcpHostError | undefined; readonly promptCount: number; } /** Registers/heartbeats the daemon shared session a hosted agent maps onto. */ export type AcpSessionRegistrar = (input: { readonly id: string; readonly title: string; readonly agentTitle: string; readonly cwd: string; }) => void; export interface AcpHostServiceDeps { /** Permission asks from hosted agents route here (the daemon wires its shared approval broker). */ readonly requestPermission?: PermissionRequestHandler | undefined; /** Maps the hosted agent onto a daemon shared session (kind 'acp'). Optional, narrower embeds skip it. */ readonly registerSession?: AcpSessionRegistrar | undefined; /** Injectable spawn seam for tests. Defaults to Bun.spawn. */ readonly spawn?: ((cmd: string[], opts: { cwd: string; }) => ReturnType) | undefined; /** Handshake bound (spawn→initialize→session). Default 15s, a bad binary becomes a structured failure, never a hung row. */ readonly handshakeTimeoutMs?: number | undefined; readonly now?: (() => number) | undefined; } export declare class AcpHostService { private readonly records; private readonly deps; private readonly now; constructor(deps?: AcpHostServiceDeps); list(): HostedAcpAgent[]; get(id: string): HostedAcpAgent | null; /** * Spawn a discovered agent into a working directory as a hosted session. * Resolves once the ACP handshake + session creation completed (state * 'idle', ready for prompts) or failed (state 'failed' with the structured * error). An initial prompt, when given, is fired after the handshake * without being awaited, the row streams like any live agent. */ spawnAgent(input: { readonly agent: DiscoveredAcpAgent; readonly cwd: string; readonly title?: string | undefined; readonly prompt?: string | undefined; }): Promise; /** * Send a prompt (a steer) to a hosted agent's live ACP session. Honest * refusal for a row that cannot take one. Resolves queued immediately; the * turn streams in the background and the state returns to 'idle' when the * agent's turn ends. */ prompt(id: string, text: string): { queued: true; } | { queued: false; reason: string; }; /** Stop a hosted agent: ACP cancel (best effort) then kill; state 'stopped'. */ stop(id: string): Promise; /** Drop terminal records (a panel dismiss); live rows are untouched. */ dismiss(id: string): boolean; private teardown; private buildClient; } //# sourceMappingURL=host.d.ts.map