@wrongstack/acp 0.273.1 → 0.275.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,644 @@
1
+ import { SubagentRunner } from '@wrongstack/core';
2
+ import { A as ACPClientTransport, b as ACPMessage, g as ToolCallUpdateNotification, P as PermissionOption, R as RequestPermissionOutcome, T as ToolKind, h as ToolCallStatus, f as PlanEntry, U as UsageCost, i as AnySessionUpdate, j as AgentCapabilities, k as AuthMethod, l as SessionId, M as McpServer, m as SessionInfo, C as ContentBlock, e as StopReason } from './acp-v1-BxskPsdo.js';
3
+
4
+ /**
5
+ * WebSocketClientTransport — remote ACP transport for `ACPSession`.
6
+ *
7
+ * Connects to a remote ACP agent over a WebSocket (cloud-hosted agents,
8
+ * separate-process agents reachable over the network). Each WebSocket
9
+ * message carries exactly one JSON-RPC 2.0 object — message boundaries
10
+ * are preserved by the WS framing, so (unlike stdio) no newline delimiter
11
+ * is needed.
12
+ *
13
+ * Uses the Node ≥ 22 built-in global `WebSocket` (undici), so there is no
14
+ * runtime dependency. Per-connection auth headers are not supported by the
15
+ * WHATWG WebSocket client; authenticate over the protocol instead
16
+ * (`ACPSession.authenticate`) or embed a token in the URL query string.
17
+ *
18
+ * Spec: https://agentclientprotocol.com/protocol/v1/overview (remote transport)
19
+ */
20
+
21
+ interface WebSocketClientTransportOptions {
22
+ /** ws:// or wss:// URL of the remote ACP agent. */
23
+ url: string;
24
+ /** Optional WebSocket subprotocols. */
25
+ protocols?: string | string[] | undefined;
26
+ /** How long to wait for the socket to open. Default 30s. */
27
+ handshakeTimeoutMs?: number | undefined;
28
+ }
29
+ declare class WebSocketClientTransport implements ACPClientTransport {
30
+ private ws;
31
+ private readonly handlers;
32
+ private closed;
33
+ private readonly opts;
34
+ constructor(opts: WebSocketClientTransportOptions);
35
+ start(): Promise<void>;
36
+ send(msg: ACPMessage): Promise<void>;
37
+ onMessage(handler: (msg: ACPMessage) => void): () => void;
38
+ stop(): void;
39
+ private onData;
40
+ private dispatch;
41
+ }
42
+
43
+ /**
44
+ * Permission policy for ACP v1 client sessions.
45
+ *
46
+ * ACP agents can call `session/request_permission` to ask the user
47
+ * before executing a tool call. The client is expected to surface
48
+ * the question, get a decision, and respond. This module is the seam
49
+ * where WrongStack-specific permission UI can plug in; for v1 we ship
50
+ * a minimal default that auto-approves the first `allow_once` option
51
+ * (or `allow_always` if present) and rejects on abort.
52
+ */
53
+
54
+ /** A single permission decision request. */
55
+ interface PermissionRequest {
56
+ toolCall: ToolCallUpdateNotification;
57
+ options: readonly PermissionOption[];
58
+ signal: AbortSignal;
59
+ }
60
+ /** A permission policy decides how to respond to a request. */
61
+ type PermissionPolicy = (req: PermissionRequest) => Promise<RequestPermissionOutcome>;
62
+ /**
63
+ * Default policy: auto-approve the least-standing allow option.
64
+ *
65
+ * ⚠️ This auto-approves EVERY tool call, including file writes and shell
66
+ * commands. It exists so non-interactive contexts (CLI `acp spawn`,
67
+ * the Director fan-out) work without a human in the loop. Interactive
68
+ * surfaces (TUI/WebUI) MUST inject a policy that surfaces the request to
69
+ * the user — pass `permissionPolicy` to `ACPSession` / the subagent runner.
70
+ * For untrusted agents prefer {@link readOnlyPermissionPolicy}.
71
+ */
72
+ declare const defaultPermissionPolicy: PermissionPolicy;
73
+ /**
74
+ * Safe-by-default policy: auto-approve only side-effect-free tool calls
75
+ * (read/search/fetch/think); reject anything that would write files or
76
+ * run commands. Use this when driving an untrusted external agent and no
77
+ * interactive surface is available to ask the user.
78
+ */
79
+ declare const readOnlyPermissionPolicy: PermissionPolicy;
80
+ /**
81
+ * Build a policy from a yes/no decision function. The decider receives the
82
+ * tool call (title + kind + rawInput) and returns whether to allow it.
83
+ * This is the seam an interactive host (TUI/WebUI confirm prompt, trust
84
+ * store, exec-allowlist) plugs into.
85
+ */
86
+ declare function makePermissionPolicy(decide: (req: PermissionRequest) => boolean | Promise<boolean>): PermissionPolicy;
87
+
88
+ /**
89
+ * ACPSession — v1-correct ACP client.
90
+ *
91
+ * Owns one child process running an ACP-supporting agent (Claude Code,
92
+ * Gemini CLI, Codex CLI, etc.) and translates the wire protocol into
93
+ * a `SubagentRunner`-shaped surface for the rest of WrongStack.
94
+ *
95
+ * Spec: https://agentclientprotocol.com/protocol/v1/overview
96
+ * Design: see ./acp-session.design.md in this directory.
97
+ */
98
+
99
+ interface ACPSessionOptions {
100
+ command: string;
101
+ args?: readonly string[] | undefined;
102
+ env?: Record<string, string> | undefined;
103
+ cwd?: string | undefined;
104
+ role?: string | undefined;
105
+ /** Sandbox root for fs/* and terminal/* methods. */
106
+ projectRoot: string;
107
+ /** Hard timeout for one prompt turn. Default 5 minutes. */
108
+ timeoutMs?: number | undefined;
109
+ /** Override the permission policy. */
110
+ permissionPolicy?: PermissionPolicy | undefined;
111
+ /** Per-fs-call timeout, default 30s. */
112
+ fsTimeoutMs?: number | undefined;
113
+ /** Per-terminal command timeout, default 5 minutes. */
114
+ terminalTimeoutMs?: number | undefined;
115
+ /** Per-terminal output byte cap, default 1 MiB. */
116
+ terminalOutputByteLimit?: number | undefined;
117
+ /**
118
+ * MCP server configs to include in session/new, session/load, and
119
+ * session/resume. The agent will connect to these servers to provide
120
+ * additional tools.
121
+ *
122
+ * Stdio servers are always sent. HTTP/SSE servers are only sent if
123
+ * the agent advertises the corresponding mcpCapabilities.
124
+ */
125
+ mcpServers?: McpServer[] | undefined;
126
+ }
127
+ /**
128
+ * A captured file diff emitted by the agent during a turn (via a tool
129
+ * call's `diff` content). `oldText: null` means the file was created.
130
+ */
131
+ interface ACPCapturedDiff {
132
+ path: string;
133
+ oldText: string | null;
134
+ newText: string;
135
+ }
136
+ /**
137
+ * A captured tool call the agent ran during a turn. We collapse the
138
+ * `tool_call` + subsequent `tool_call_update` notifications for the same
139
+ * `toolCallId` into one record carrying its latest status.
140
+ */
141
+ interface ACPCapturedToolCall {
142
+ toolCallId: string;
143
+ title: string;
144
+ kind?: ToolKind | undefined;
145
+ status: ToolCallStatus;
146
+ /** Terminal/command output or text content surfaced by the tool, if any. */
147
+ rawOutput?: Record<string, unknown> | undefined;
148
+ rawInput?: Record<string, unknown> | undefined;
149
+ }
150
+ interface ACPSessionRunResult {
151
+ text: string;
152
+ stopReason: StopReason;
153
+ hasText: boolean;
154
+ usage?: {
155
+ used: number;
156
+ size: number;
157
+ cost?: UsageCost | undefined;
158
+ } | undefined;
159
+ plan?: PlanEntry[] | undefined;
160
+ /** Tool calls the agent ran this turn (deduped by toolCallId). */
161
+ toolCalls: ACPCapturedToolCall[];
162
+ /** File diffs the agent produced this turn. */
163
+ diffs: ACPCapturedDiff[];
164
+ /** Agent "thinking" text emitted via thought_chunk, concatenated. */
165
+ thoughts: string;
166
+ }
167
+ /**
168
+ * Live progress callback. Invoked for every `session/update` notification
169
+ * the agent streams during a `prompt()` turn, in arrival order, BEFORE the
170
+ * turn resolves. Lets the host render tool activity / text deltas / diffs
171
+ * as they happen instead of waiting for the buffered final result.
172
+ *
173
+ * The raw `update` (the discriminated `session/update` payload) is passed
174
+ * through verbatim so callers can switch on `update.sessionUpdate`.
175
+ */
176
+ type ACPProgressHandler = (event: ACPProgressEvent) => void;
177
+ type ACPProgressEvent = {
178
+ type: 'message';
179
+ text: string;
180
+ } | {
181
+ type: 'thought';
182
+ text: string;
183
+ } | {
184
+ type: 'tool_call';
185
+ toolCall: ACPCapturedToolCall;
186
+ } | {
187
+ type: 'tool_call_update';
188
+ toolCall: ACPCapturedToolCall;
189
+ } | {
190
+ type: 'diff';
191
+ diff: ACPCapturedDiff;
192
+ } | {
193
+ type: 'plan';
194
+ entries: PlanEntry[];
195
+ } | {
196
+ type: 'usage';
197
+ usage: {
198
+ used: number;
199
+ size: number;
200
+ cost?: UsageCost | undefined;
201
+ };
202
+ } | {
203
+ type: 'raw';
204
+ update: AnySessionUpdate;
205
+ };
206
+ type ACPSessionErrorKind = 'spawn_failed' | 'init_failed' | 'protocol_error' | 'session_create_failed' | 'prompt_failed' | 'auth_failed' | 'logout_failed' | 'aborted' | 'closed' | 'agent_died' | 'unsupported_capability';
207
+ declare class ACPSessionError extends Error {
208
+ readonly kind: ACPSessionErrorKind;
209
+ readonly cause: unknown;
210
+ constructor(kind: ACPSessionErrorKind, message: string, cause?: unknown);
211
+ }
212
+ declare class ACPSession {
213
+ private readonly transport;
214
+ private readonly fileServer;
215
+ private readonly terminalServer;
216
+ private readonly permissionPolicy;
217
+ private readonly timeoutMs;
218
+ private readonly opts;
219
+ private state;
220
+ private sessionId;
221
+ /** Pending outbound requests (initialize, session/new, session/prompt, etc). */
222
+ private readonly pending;
223
+ private nextId;
224
+ /** True after close() has been called. */
225
+ private closed;
226
+ private agentCapabilities;
227
+ private agentInfo;
228
+ private authMethods;
229
+ /** Protocol version negotiated with the agent during initialize. */
230
+ private negotiatedVersion;
231
+ private constructor();
232
+ /** Agent capabilities advertised during initialize. */
233
+ getCapabilities(): AgentCapabilities;
234
+ /** Authentication methods advertised by the agent. */
235
+ getAuthMethods(): AuthMethod[];
236
+ /** Agent info (name, title, version) from initialize. */
237
+ getAgentInfo(): {
238
+ name: string;
239
+ title?: string | undefined;
240
+ version: string;
241
+ } | null;
242
+ /** Whether the agent requires authentication (has auth methods). */
243
+ requiresAuth(): boolean;
244
+ /** Current session id, if one exists. */
245
+ getSessionId(): SessionId | null;
246
+ /** Protocol version negotiated during initialize. */
247
+ getNegotiatedVersion(): number;
248
+ /**
249
+ * Spawn the child, run the initialize handshake, install the
250
+ * message dispatch, and return a ready session.
251
+ */
252
+ static start(opts: ACPSessionOptions): Promise<ACPSession>;
253
+ /**
254
+ * Connect to a REMOTE ACP agent over a WebSocket instead of spawning a
255
+ * local subprocess. `opts.command` is ignored for the wire (a label is
256
+ * still useful for `role`); everything else (projectRoot sandbox for
257
+ * fs/terminal, timeouts, permission policy, MCP servers) applies the same.
258
+ */
259
+ static connectWebSocket(wsOpts: WebSocketClientTransportOptions, opts: ACPSessionOptions): Promise<ACPSession>;
260
+ /**
261
+ * Connect using a caller-supplied transport. Lets advanced callers plug
262
+ * in their own wire (SDK streams, in-process pipes, test doubles).
263
+ */
264
+ static connect(transport: ACPClientTransport, opts: ACPSessionOptions): Promise<ACPSession>;
265
+ /** Shared connect path: start the transport, install dispatch, handshake. */
266
+ private static attach;
267
+ private initialize;
268
+ /**
269
+ * Authenticate with the agent using one of the advertised auth methods.
270
+ * Call this AFTER start() and BEFORE any session/new call.
271
+ *
272
+ * Throws ACPSessionError('auth_failed') if the agent rejects the
273
+ * authentication or if the methodId is not in the advertised list.
274
+ */
275
+ authenticate(methodId: string): Promise<void>;
276
+ /**
277
+ * Log out from the current authenticated session.
278
+ * Only callable if the agent advertises `auth.logout` capability.
279
+ */
280
+ logout(): Promise<void>;
281
+ /**
282
+ * Load an existing session. The agent replays the conversation history
283
+ * via session/update notifications before responding.
284
+ *
285
+ * Only works if the agent advertises `loadSession` capability.
286
+ *
287
+ * @param sessionId - The session to load
288
+ * @param mcpServers - Optional MCP servers (defaults to options.mcpServers)
289
+ * @param cwd - Optional working directory (defaults to options.cwd or projectRoot)
290
+ */
291
+ loadSession(sessionId: SessionId, mcpServers?: McpServer[], cwd?: string): Promise<void>;
292
+ /**
293
+ * Resume an existing session without replaying history.
294
+ *
295
+ * Only works if the agent advertises `sessionCapabilities.resume`.
296
+ *
297
+ * @param sessionId - The session to resume
298
+ * @param mcpServers - Optional MCP servers (defaults to options.mcpServers)
299
+ * @param cwd - Optional working directory (defaults to options.cwd or projectRoot)
300
+ */
301
+ resumeSession(sessionId: SessionId, mcpServers?: McpServer[], cwd?: string): Promise<void>;
302
+ /**
303
+ * List existing sessions known to the agent.
304
+ *
305
+ * Only works if the agent advertises `sessionCapabilities.list`.
306
+ */
307
+ listSessions(cursor?: string, cwd?: string): Promise<{
308
+ sessions: SessionInfo[];
309
+ nextCursor?: string | undefined;
310
+ }>;
311
+ /**
312
+ * Delete a session from the agent's session list.
313
+ *
314
+ * Only works if the agent advertises `sessionCapabilities.delete`.
315
+ */
316
+ deleteSession(sessionId: SessionId): Promise<void>;
317
+ /**
318
+ * Fork a session — create a new session from an existing one.
319
+ */
320
+ forkSession(sourceSessionId: SessionId, cwd?: string, mcpServers?: McpServer[]): Promise<SessionId>;
321
+ /**
322
+ * Set the active mode for a session.
323
+ */
324
+ setMode(sessionId: SessionId, modeId: string): Promise<void>;
325
+ /**
326
+ * Set a configuration option for a session.
327
+ */
328
+ setConfigOption(sessionId: SessionId, configId: string, value: string): Promise<void>;
329
+ /**
330
+ * List available providers and the current provider.
331
+ */
332
+ listProviders(): Promise<{
333
+ providers: unknown[];
334
+ currentProviderId: string | null;
335
+ }>;
336
+ /**
337
+ * Send an MCP message to the agent for routing.
338
+ */
339
+ mcpMessage(connectionId: string, message: Record<string, unknown>): Promise<unknown>;
340
+ /**
341
+ * Set the active provider for the agent.
342
+ */
343
+ setProvider(providerId: string, config?: Record<string, unknown>): Promise<void>;
344
+ /**
345
+ * Disable the current provider.
346
+ */
347
+ disableProvider(): Promise<void>;
348
+ /**
349
+ * Run one prompt turn. Creates a session if needed, sends the
350
+ * prompt, streams session/update notifications, and resolves with
351
+ * the agent's response.
352
+ *
353
+ * @param blocks - Content blocks to send. Use `textContent()` for plain
354
+ * text, or include ImageContent/AudioContent if the agent's
355
+ * `promptCapabilities` allow it.
356
+ * @param signal - AbortSignal for cancellation.
357
+ *
358
+ * Cancellation: if `signal` aborts mid-prompt, we send
359
+ * `session/cancel` (a notification per spec) and keep accepting
360
+ * updates until the agent returns with `stopReason: 'cancelled'`.
361
+ * The result is the same shape as a normal turn, with
362
+ * `stopReason === 'cancelled'`.
363
+ */
364
+ prompt(blocks: ContentBlock[], signal: AbortSignal, onProgress?: ACPProgressHandler): Promise<ACPSessionRunResult>;
365
+ private createSession;
366
+ /**
367
+ * Close the current session gracefully (if the agent supports it).
368
+ *
369
+ * Sends `session/close` JSON-RPC request, then clears the local
370
+ * session id. Best-effort — errors are swallowed so the caller can
371
+ * always proceed to transport teardown.
372
+ */
373
+ private closeSession;
374
+ /** Tear down the session and kill the child process. */
375
+ close(): Promise<void>;
376
+ /**
377
+ * Filter MCP servers according to agent capabilities.
378
+ * - Stdio servers are always included.
379
+ * - HTTP servers are only included if agent supports mcpCapabilities.http.
380
+ * - SSE servers are only included if agent supports mcpCapabilities.sse.
381
+ */
382
+ private filterMcpServers;
383
+ private allocId;
384
+ private sendRequest;
385
+ /**
386
+ * Send a JSON-RPC 2.0 success response to an agent-initiated request.
387
+ *
388
+ * Per JSON-RPC 2.0 (and the official ACP SDK's message router) a Response
389
+ * object MUST carry `jsonrpc: "2.0"` and MUST NOT carry a `method` field —
390
+ * the SDK classifies any object with a `method` key as a Request and drops
391
+ * it as a response, so an agent's `fs/*`, `terminal/*`, or
392
+ * `session/request_permission` callback would hang forever. The legacy
393
+ * `ACPMessage` type predates v1 (requires `method`, lacks `jsonrpc`), so we
394
+ * build the correct wire object and cast at the boundary.
395
+ */
396
+ private sendResult;
397
+ /** Send a JSON-RPC 2.0 error response (no `method` field, per spec). */
398
+ private sendErrorResponse;
399
+ private handleMessage;
400
+ private handleUpdate;
401
+ /**
402
+ * Fold a `tool_call` / `tool_call_update` notification into the scratch
403
+ * tool-call map (deduped by toolCallId), extract any `diff` content into
404
+ * the diffs list, and emit live progress.
405
+ */
406
+ private captureToolCall;
407
+ private emitProgress;
408
+ /** Live progress handler installed for the duration of a `prompt()` turn. */
409
+ private progressHandler;
410
+ private scratch;
411
+ private resetScratch;
412
+ private handlePermissionRequest;
413
+ private handleFsRequest;
414
+ private handleTerminalRequest;
415
+ }
416
+ /**
417
+ * Create a text ContentBlock. Convenience helper for callers of
418
+ * `session.prompt()`.
419
+ */
420
+ declare function textContent(text: string): ContentBlock;
421
+ /**
422
+ * Create an image ContentBlock. Only send this if the agent's
423
+ * `promptCapabilities.image` is `true` (check via
424
+ * `session.getCapabilities().promptCapabilities?.image`).
425
+ */
426
+ declare function imageContent(mimeType: string, data: string): ContentBlock;
427
+ /**
428
+ * Create an audio ContentBlock. Only send this if the agent's
429
+ * `promptCapabilities.audio` is `true` (check via
430
+ * `session.getCapabilities().promptCapabilities?.audio`).
431
+ */
432
+ declare function audioContent(mimeType: string, data: string): ContentBlock;
433
+
434
+ /**
435
+ * ACPSubagentRunner — `SubagentRunner` implementation for DIR-1.
436
+ *
437
+ * Wraps an external ACP-supporting agent (Claude Code, Gemini CLI, Codex
438
+ * CLI, Cline, Goose, OpenHands, etc.) as a WrongStack subagent. The
439
+ * external agent runs its own agent loop; we send it a task via the ACP
440
+ * v1 protocol and return the result.
441
+ *
442
+ * v1 spec: https://agentclientprotocol.com/protocol/v1/overview
443
+ *
444
+ * Connected to the Director / MultiAgentCoordinator via the
445
+ * `SubagentRunner` interface (same shape as `AgentSubagentRunner`).
446
+ */
447
+
448
+ interface ACPSubagentRunnerOptions {
449
+ /** How to spawn the external agent. */
450
+ command: string;
451
+ args?: string[] | undefined;
452
+ env?: Record<string, string> | undefined;
453
+ cwd?: string | undefined;
454
+ /** Subagent role label — surfaced in errors and used for logging. */
455
+ role?: string | undefined;
456
+ /**
457
+ * Hard wall-clock cap for one prompt turn. Defaults to 5 minutes.
458
+ * Overrides `SubagentRunContext.budget.limits.timeoutMs` if both are set.
459
+ */
460
+ timeoutMs?: number | undefined;
461
+ /**
462
+ * Filesystem sandbox root. Defaults to `options.cwd` (when set) or
463
+ * the process's current working directory. All `fs/read_text_file` /
464
+ * `fs/write_text_file` calls are bounded to this root.
465
+ */
466
+ projectRoot?: string | undefined;
467
+ /**
468
+ * Live progress callback. Forwarded to `ACPSession.prompt` so the host
469
+ * can render the external agent's tool calls / diffs / text as they
470
+ * stream, instead of waiting for the buffered final result.
471
+ */
472
+ onProgress?: ACPProgressHandler | undefined;
473
+ /**
474
+ * Permission policy for the external agent's `session/request_permission`
475
+ * calls. Defaults to the session's own default. Inject the host's
476
+ * confirm/trust UI here so an external agent's file writes / commands
477
+ * are surfaced to a human instead of silently auto-approved.
478
+ */
479
+ permissionPolicy?: PermissionPolicy | undefined;
480
+ /**
481
+ * MCP servers to expose to the external agent (passed through
482
+ * `session/new` / `session/load`). Stdio servers are always sent;
483
+ * HTTP/SSE are filtered by the agent's advertised capabilities.
484
+ */
485
+ mcpServers?: McpServer[] | undefined;
486
+ /**
487
+ * When true, the underlying `ACPSession` is kept open across multiple
488
+ * runner invocations (multi-turn conversation — the external agent
489
+ * keeps its context). The caller MUST call `stop()` to tear it down.
490
+ * Defaults to false (one process per task).
491
+ */
492
+ persistent?: boolean | undefined;
493
+ }
494
+ /**
495
+ * Static catalog of agent ids → spawn options.
496
+ *
497
+ * The CLI and the host's `buildACPRunner` look up entries by id. The
498
+ * canonical, multi-source catalog is `packages/acp/src/registry/agents.catalog.ts`
499
+ * (the 12-entry static catalog introduced in commit 4ad287b4). This
500
+ * map stays for backward compatibility with existing call sites that
501
+ * import it directly; new code should prefer the registry.
502
+ */
503
+ declare const ACP_AGENT_COMMANDS: Record<string, ACPSubagentRunnerOptions>;
504
+ /**
505
+ * Build a one-shot `SubagentRunner` for a single agent invocation. Each
506
+ * call to the returned function spawns a fresh child process, runs one
507
+ * prompt turn, and tears everything down. The cost is ~1 second of
508
+ * process-startup per call; for long-lived sessions (multi-turn
509
+ * conversations), use `makeACPSubagentRunnerWithStop` and call `stop()`
510
+ * explicitly.
511
+ */
512
+ declare function makeACPSubagentRunner(options: ACPSubagentRunnerOptions): Promise<SubagentRunner>;
513
+ /**
514
+ * Build a long-lived `SubagentRunner` plus an explicit `stop()` for
515
+ * teardown. The caller is responsible for calling `stop()` when done
516
+ * (or when the host's signal fires). Useful for the `wstack acp spawn`
517
+ * CLI command, which holds the child open for the duration of a user
518
+ * task and tears down on SIGINT.
519
+ */
520
+ declare function makeACPSubagentRunnerWithStop(options: ACPSubagentRunnerOptions): Promise<{
521
+ runner: SubagentRunner;
522
+ stop: () => void | Promise<void>;
523
+ }>;
524
+
525
+ /**
526
+ * Per-agent ACP invocation overrides, sourced from the user's
527
+ * `~/.wrongstack/config.json` (`config.acp.agents`). Lets a user point an
528
+ * agent id at the correct ACP entry — e.g. the Zed Claude-Code adapter —
529
+ * without a code change. NEVER honoured from in-project config (it is an
530
+ * arbitrary-command exec surface); see `config-loader.ts`.
531
+ */
532
+ type AcpAgentCommandOverrides = Record<string, {
533
+ command: string;
534
+ args?: string[];
535
+ env?: Record<string, string>;
536
+ }>;
537
+ /** A synced-registry catalog keyed by registry id (from `fetchAcpRegistry`). */
538
+ type AcpLiveCatalog = Record<string, {
539
+ command: string;
540
+ args?: readonly string[];
541
+ env?: Record<string, string>;
542
+ }>;
543
+ /**
544
+ * Map our stable, human-friendly catalog ids to the official registry's ids,
545
+ * so a live-synced registry (keyed by registry id) still resolves when the
546
+ * user types our id. Our id is preferred in the UI; the alias is the bridge.
547
+ */
548
+ declare const REGISTRY_ID_ALIASES: Readonly<Record<string, string>>;
549
+ /**
550
+ * Resolve an agent id to its spawn command. Precedence:
551
+ * 1. user override (`config.acp.agents[id]`)
552
+ * 2. the bundled static `AGENTS_CATALOG` (curated LOCAL-binary invocations)
553
+ * 3. live synced registry (`fetchAcpRegistry` → cache), by id or alias
554
+ * 4. legacy `ACP_AGENT_COMMANDS` map (last resort, kept for back-compat)
555
+ * Returns `null` for an id present in none of them.
556
+ *
557
+ * Why catalog BEFORE the live registry: our goal is to drive the user's
558
+ * already-installed, logged-in CLI. The catalog hand-curates the LOCAL-binary
559
+ * ACP entry for each popular agent (`gemini --acp`, `opencode acp`, …), which
560
+ * preserves the agent's own login and starts instantly. The official registry,
561
+ * by contrast, encodes "run a fresh copy" invocations — pinned `npx <pkg>@ver`
562
+ * downloads (no local login, slow first run) and platform binaries like
563
+ * `opencode.exe` that may not match a shim on PATH. So the registry is the
564
+ * source for the long tail of agents the catalog doesn't cover, NOT an
565
+ * override of the curated 12. Users force a specific command via the override.
566
+ */
567
+ declare function resolveAcpAgentCommand(id: string, overrides?: AcpAgentCommandOverrides, live?: AcpLiveCatalog): ACPSubagentRunnerOptions | null;
568
+ interface RunOneAcpTaskOptions {
569
+ command: string;
570
+ args?: string[] | undefined;
571
+ env?: Record<string, string> | undefined;
572
+ /** Agent id / role label, surfaced in errors + the synthetic task id. */
573
+ role?: string | undefined;
574
+ /** The task description forwarded verbatim to the agent. */
575
+ task: string;
576
+ cwd?: string | undefined;
577
+ projectRoot?: string | undefined;
578
+ timeoutMs?: number | undefined;
579
+ signal?: AbortSignal | undefined;
580
+ onProgress?: ACPProgressHandler | undefined;
581
+ permissionPolicy?: PermissionPolicy | undefined;
582
+ }
583
+ interface RunOneAcpTaskResult {
584
+ result: string;
585
+ iterations: number;
586
+ toolCalls: number;
587
+ }
588
+ /**
589
+ * Run a single task on one ACP agent and return its result. Spawns a fresh
590
+ * process, runs one prompt turn, and tears everything down. Throws a
591
+ * structured `SubagentError` on failure (spawn/init/prompt). This is the
592
+ * shared engine behind `wstack acp spawn` and `/acp <id> <task>`.
593
+ */
594
+ declare function runOneAcpTask(opts: RunOneAcpTaskOptions): Promise<RunOneAcpTaskResult>;
595
+ interface AcpProbeResult {
596
+ id: string;
597
+ ok: boolean;
598
+ ms: number;
599
+ agentInfo?: {
600
+ name: string;
601
+ title?: string | undefined;
602
+ version: string;
603
+ } | undefined;
604
+ error?: string | undefined;
605
+ }
606
+ /**
607
+ * Empirically test whether an agent actually speaks ACP on this machine:
608
+ * spawn it, run the `initialize` handshake, and close. `ok: true` means the
609
+ * agent answered `initialize` within `timeoutMs` (default 8s) — the truth,
610
+ * regardless of what the static catalog guesses. A bare CLI that drops into
611
+ * an interactive prompt fails here (init times out) instead of hanging a
612
+ * real turn.
613
+ */
614
+ declare function probeAcpAgent(idOrCmd: string | ACPSubagentRunnerOptions, opts?: {
615
+ timeoutMs?: number | undefined;
616
+ projectRoot?: string | undefined;
617
+ overrides?: AcpAgentCommandOverrides | undefined;
618
+ live?: AcpLiveCatalog | undefined;
619
+ }): Promise<AcpProbeResult>;
620
+ interface ProbeAcpAgentsOptions {
621
+ agentIds: string[];
622
+ resolveCmd: (id: string) => ACPSubagentRunnerOptions | null;
623
+ projectRoot?: string | undefined;
624
+ /** Max agents probed at once. Default 4. Keeps concurrent first-run `npx`
625
+ * downloads from starving local agents' stdout past their timeout. */
626
+ concurrency?: number | undefined;
627
+ /** Per-agent handshake timeout for LOCAL binary commands. Default 20s. */
628
+ timeoutMs?: number | undefined;
629
+ /** Per-agent timeout for `npx`/`uvx` commands (first run downloads the
630
+ * package, which is slow). Default 90s. */
631
+ packageTimeoutMs?: number | undefined;
632
+ signal?: AbortSignal | undefined;
633
+ onProgress?: ((id: string, result: AcpProbeResult) => void) | undefined;
634
+ }
635
+ /**
636
+ * Probe many agents with BOUNDED concurrency. Unbounded `Promise.all` over the
637
+ * full set spawns every agent at once — and a few concurrent `npx` downloads
638
+ * peg the machine hard enough that even already-installed local agents miss
639
+ * their handshake window. Bounding the fan-out (and giving npx/uvx a longer
640
+ * timeout) is what makes a mixed install probe reliably.
641
+ */
642
+ declare function probeAcpAgents(opts: ProbeAcpAgentsOptions): Promise<AcpProbeResult[]>;
643
+
644
+ export { type ACPCapturedDiff as A, type AcpLiveCatalog as B, type ProbeAcpAgentsOptions as C, REGISTRY_ID_ALIASES as D, probeAcpAgents as E, type PermissionPolicy as P, type RunOneAcpTaskOptions as R, WebSocketClientTransport as W, type ACPCapturedToolCall as a, type ACPProgressEvent as b, type ACPProgressHandler as c, ACPSession as d, ACPSessionError as e, type ACPSessionErrorKind as f, type ACPSessionOptions as g, type ACPSessionRunResult as h, type ACPSubagentRunnerOptions as i, ACP_AGENT_COMMANDS as j, type AcpAgentCommandOverrides as k, type AcpProbeResult as l, type PermissionRequest as m, type RunOneAcpTaskResult as n, type WebSocketClientTransportOptions as o, audioContent as p, defaultPermissionPolicy as q, imageContent as r, makeACPSubagentRunner as s, makeACPSubagentRunnerWithStop as t, makePermissionPolicy as u, probeAcpAgent as v, readOnlyPermissionPolicy as w, resolveAcpAgentCommand as x, runOneAcpTask as y, textContent as z };