@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.
- package/README.md +376 -0
- package/dist/acp-subagent-runner-BAlo23L-.d.ts +644 -0
- package/dist/acp-v1-BxskPsdo.d.ts +520 -0
- package/dist/agent.d.ts +34 -61
- package/dist/agent.js +796 -32
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +3 -2
- package/dist/client.js +779 -112
- package/dist/client.js.map +1 -1
- package/dist/index-DEEYyEpu.d.ts +54 -0
- package/dist/index.d.ts +186 -227
- package/dist/index.js +1881 -286
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +12 -0
- package/dist/sdk.js +3350 -0
- package/dist/sdk.js.map +1 -0
- package/dist/server-agent-turn-C3U0lhA-.d.ts +163 -0
- package/dist/terminal-server-P9KpMZTT.d.ts +99 -0
- package/dist/{tools-registry-BCf8evEG.d.ts → tools-registry-D2xdbzN7.d.ts} +1 -1
- package/dist/wrongstack-acp-agent-nzrqmJnc.d.ts +341 -0
- package/dist/wrongstack-acp-agent.d.ts +2 -2
- package/dist/wrongstack-acp-agent.js +426 -26
- package/dist/wrongstack-acp-agent.js.map +1 -1
- package/package.json +7 -2
- package/dist/index-BvPqJHhm.d.ts +0 -119
- package/dist/stdio-transport-CsFr8JzC.d.ts +0 -205
- package/dist/wrongstack-acp-agent-Dv-A0bEm.d.ts +0 -310
|
@@ -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 };
|