@wrongstack/acp 0.10.2 → 0.24.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.
Files changed (48) hide show
  1. package/dist/agent.d.ts +92 -0
  2. package/dist/agent.js +420 -0
  3. package/dist/agent.js.map +1 -0
  4. package/dist/client.d.ts +4 -0
  5. package/dist/client.js +315 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/{client/tool-translator.d.ts → index-CncECXiF.d.ts} +41 -17
  8. package/dist/index.d.ts +5 -24
  9. package/dist/index.js +841 -21
  10. package/dist/index.js.map +1 -1
  11. package/dist/stdio-transport-DqWq-2iP.d.ts +198 -0
  12. package/package.json +7 -7
  13. package/dist/agent/index.d.ts +0 -7
  14. package/dist/agent/index.d.ts.map +0 -1
  15. package/dist/agent/index.js +0 -5
  16. package/dist/agent/index.js.map +0 -1
  17. package/dist/agent/protocol-handler.d.ts +0 -39
  18. package/dist/agent/protocol-handler.d.ts.map +0 -1
  19. package/dist/agent/protocol-handler.js +0 -142
  20. package/dist/agent/protocol-handler.js.map +0 -1
  21. package/dist/agent/stdio-transport.d.ts +0 -64
  22. package/dist/agent/stdio-transport.d.ts.map +0 -1
  23. package/dist/agent/stdio-transport.js +0 -250
  24. package/dist/agent/stdio-transport.js.map +0 -1
  25. package/dist/agent/tools-registry.d.ts +0 -33
  26. package/dist/agent/tools-registry.d.ts.map +0 -1
  27. package/dist/agent/tools-registry.js +0 -131
  28. package/dist/agent/tools-registry.js.map +0 -1
  29. package/dist/agent/wrongstack-acp-agent.d.ts +0 -32
  30. package/dist/agent/wrongstack-acp-agent.d.ts.map +0 -1
  31. package/dist/agent/wrongstack-acp-agent.js +0 -90
  32. package/dist/agent/wrongstack-acp-agent.js.map +0 -1
  33. package/dist/client/index.d.ts +0 -6
  34. package/dist/client/index.d.ts.map +0 -1
  35. package/dist/client/index.js +0 -4
  36. package/dist/client/index.js.map +0 -1
  37. package/dist/client/tool-translator.d.ts.map +0 -1
  38. package/dist/client/tool-translator.js +0 -112
  39. package/dist/client/tool-translator.js.map +0 -1
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/integration/acp-subagent-runner.d.ts +0 -34
  42. package/dist/integration/acp-subagent-runner.d.ts.map +0 -1
  43. package/dist/integration/acp-subagent-runner.js +0 -218
  44. package/dist/integration/acp-subagent-runner.js.map +0 -1
  45. package/dist/types/acp-messages.d.ts +0 -133
  46. package/dist/types/acp-messages.d.ts.map +0 -1
  47. package/dist/types/acp-messages.js +0 -2
  48. package/dist/types/acp-messages.js.map +0 -1
package/dist/client.js ADDED
@@ -0,0 +1,315 @@
1
+ import { buildChildEnv } from '@wrongstack/core';
2
+ import { spawn } from 'child_process';
3
+
4
+ // src/agent/stdio-transport.ts
5
+ var ClientTransport = class {
6
+ child = null;
7
+ buffer = "";
8
+ handlers = /* @__PURE__ */ new Set();
9
+ closed = false;
10
+ resolveRead = null;
11
+ messageQueue = [];
12
+ opts;
13
+ constructor(options) {
14
+ this.opts = {
15
+ handshakeTimeoutMs: 3e4,
16
+ ...options
17
+ };
18
+ }
19
+ async start() {
20
+ if (this.child) return;
21
+ return new Promise((resolve, reject) => {
22
+ const timeout = setTimeout(() => {
23
+ reject(
24
+ new Error(`ACP child process failed to start within ${this.opts.handshakeTimeoutMs}ms`)
25
+ );
26
+ }, this.opts.handshakeTimeoutMs);
27
+ try {
28
+ this.child = spawn(this.opts.command, this.opts.args ?? [], {
29
+ env: { ...buildChildEnv(), ...this.opts.env },
30
+ cwd: this.opts.cwd,
31
+ stdio: ["pipe", "pipe", "pipe"]
32
+ });
33
+ } catch (err) {
34
+ clearTimeout(timeout);
35
+ reject(err);
36
+ return;
37
+ }
38
+ const child = this.child;
39
+ child.stdout.setEncoding("utf8");
40
+ const waitForMarker = (chunk) => {
41
+ this.buffer += chunk;
42
+ const idx = this.buffer.indexOf("[wstack-acp]\n");
43
+ if (idx !== -1) {
44
+ this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
45
+ child.stdout.removeListener("data", waitForMarker);
46
+ child.stdout.on("data", (c) => this.onChildData(c));
47
+ child.stderr.on("data", (c) => this.onChildError(c));
48
+ child.on("close", (code) => this.onChildClose(code));
49
+ clearTimeout(timeout);
50
+ resolve();
51
+ }
52
+ };
53
+ child.stdout.on("data", waitForMarker);
54
+ child.stdout.on("error", (err) => {
55
+ clearTimeout(timeout);
56
+ reject(err);
57
+ });
58
+ child.on("error", (err) => {
59
+ clearTimeout(timeout);
60
+ reject(err);
61
+ });
62
+ });
63
+ }
64
+ send(msg) {
65
+ if (!this.child) return Promise.reject(new Error("ClientTransport not started"));
66
+ return new Promise((resolve, reject) => {
67
+ const line = JSON.stringify(msg) + "\n";
68
+ this.child.stdin.write(line, "utf8", (err) => {
69
+ if (err) reject(err);
70
+ else resolve();
71
+ });
72
+ });
73
+ }
74
+ read() {
75
+ if (this.messageQueue.length > 0) return Promise.resolve(this.messageQueue.shift());
76
+ if (this.closed) return Promise.resolve(null);
77
+ return new Promise((resolve) => {
78
+ this.resolveRead = resolve;
79
+ });
80
+ }
81
+ onMessage(handler) {
82
+ this.handlers.add(handler);
83
+ return () => this.handlers.delete(handler);
84
+ }
85
+ stop() {
86
+ if (!this.child) return;
87
+ this.closed = true;
88
+ try {
89
+ this.child.kill();
90
+ } catch {
91
+ }
92
+ this.child = null;
93
+ }
94
+ onChildData(chunk) {
95
+ this.buffer += chunk;
96
+ const lines = this.buffer.split("\n");
97
+ this.buffer = lines.pop() ?? "";
98
+ for (const raw of lines) {
99
+ if (!raw.trim()) continue;
100
+ try {
101
+ this.dispatch(JSON.parse(raw));
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ onChildError(chunk) {
107
+ process.stderr.write(`[acp-child stderr] ${chunk}`, "utf8");
108
+ }
109
+ onChildClose(code) {
110
+ this.closed = true;
111
+ this.resolveRead?.(null);
112
+ this.resolveRead = null;
113
+ if (code !== 0 && code !== null) {
114
+ process.stderr.write(`[acp-child exited with code ${code}]
115
+ `, "utf8");
116
+ }
117
+ }
118
+ dispatch(msg) {
119
+ if (this.resolveRead) {
120
+ const resolve = this.resolveRead;
121
+ this.resolveRead = null;
122
+ resolve(msg);
123
+ } else {
124
+ this.messageQueue.push(msg);
125
+ }
126
+ for (const handler of this.handlers) {
127
+ try {
128
+ handler(msg);
129
+ } catch {
130
+ }
131
+ }
132
+ }
133
+ };
134
+
135
+ // src/client/tool-translator.ts
136
+ var DEFAULT_OPTIONS = {
137
+ asyncTools: true,
138
+ pollIntervalMs: 500,
139
+ totalTimeoutMs: 12e4
140
+ };
141
+ function extractTextFromContent(blocks) {
142
+ const parts = [];
143
+ for (const b of blocks) {
144
+ if (b.type === "text") parts.push(b.text);
145
+ else if (b.type === "resource") parts.push(`[resource: ${b.resource.uri}]`);
146
+ else if (b.type === "image") parts.push(`[image: ${b.data.slice(0, 20)}...]`);
147
+ else if (b.type === "progress") {
148
+ if (b.messages?.length) parts.push(b.messages.join("\n"));
149
+ }
150
+ }
151
+ return parts.join("\n");
152
+ }
153
+ function parseToolResponse(taskId, subagentId, response) {
154
+ const blocks = response.result.content;
155
+ const text = extractTextFromContent(blocks);
156
+ const isError = response.result.isError || text.toLowerCase().includes("error") || text.toLowerCase().includes("failed");
157
+ return {
158
+ taskId,
159
+ subagentId,
160
+ status: isError ? "failed" : "success",
161
+ result: text,
162
+ iterations: 1,
163
+ toolCalls: 1,
164
+ durationMs: 0
165
+ };
166
+ }
167
+ var ToolTranslator = class {
168
+ opts;
169
+ pending = /* @__PURE__ */ new Map();
170
+ constructor(opts = {}) {
171
+ this.opts = { ...DEFAULT_OPTIONS, ...opts };
172
+ }
173
+ /**
174
+ * Start listening to a transport for tool responses and cancellations.
175
+ * Call this once after constructing the translator and before sending tasks.
176
+ */
177
+ attachToTransport(transport) {
178
+ transport.onMessage((msg) => {
179
+ if (msg.method === "tools/call" && msg.id !== void 0) {
180
+ const pending = this.pending.get(msg.id);
181
+ if (pending) {
182
+ clearTimeout(pending.timeout);
183
+ this.pending.delete(msg.id);
184
+ pending.resolve(msg);
185
+ }
186
+ }
187
+ if (msg.method === "cancel" && msg.id !== void 0) {
188
+ const pending = this.pending.get(msg.id);
189
+ if (pending) {
190
+ clearTimeout(pending.timeout);
191
+ this.pending.delete(msg.id);
192
+ pending.reject(new Error("Call cancelled by client"));
193
+ }
194
+ }
195
+ });
196
+ }
197
+ /**
198
+ * Send a tool call over the transport and wait for a response.
199
+ * If asyncTools is true, polls for progress and resolves when the final
200
+ * response arrives.
201
+ */
202
+ async callTool(transport, name, args, callId = crypto.randomUUID()) {
203
+ await transport.send({
204
+ method: "tools/call",
205
+ id: callId,
206
+ params: { name, arguments: args }
207
+ });
208
+ return new Promise((resolve, reject) => {
209
+ const timeout = setTimeout(() => {
210
+ this.pending.delete(callId);
211
+ reject(new Error(`Tool call ${name} timed out after ${this.opts.totalTimeoutMs}ms`));
212
+ }, this.opts.totalTimeoutMs);
213
+ this.pending.set(callId, { resolve, reject, timeout });
214
+ });
215
+ }
216
+ cancelAll() {
217
+ for (const [, p] of this.pending) {
218
+ clearTimeout(p.timeout);
219
+ }
220
+ this.pending.clear();
221
+ }
222
+ };
223
+
224
+ // src/integration/acp-subagent-runner.ts
225
+ async function makeACPSubagentRunner(options) {
226
+ const transport = new ClientTransport({
227
+ command: options.command,
228
+ args: options.args,
229
+ env: options.env,
230
+ cwd: options.cwd,
231
+ handshakeTimeoutMs: 3e4
232
+ });
233
+ const translator = new ToolTranslator(options.toolTranslatorOpts);
234
+ const activeAbort = new AbortController();
235
+ let sessionStarted = false;
236
+ const startSession = async () => {
237
+ if (sessionStarted) return;
238
+ await transport.start();
239
+ await transport.send({
240
+ method: "initialize",
241
+ id: "1",
242
+ params: {
243
+ capabilities: ["code-generation", "async-tools", "streaming", "progress"],
244
+ protocolVersion: "2024-11",
245
+ sessionId: options.role ?? "wrongstack-subagent"
246
+ }
247
+ });
248
+ const initResp = await transport.read();
249
+ if (!initResp || initResp.error) {
250
+ throw new Error(`ACP initialize failed: ${initResp?.error?.message ?? "no response"}`);
251
+ }
252
+ translator.attachToTransport({
253
+ onMessage: (h) => transport.onMessage(h),
254
+ send: (m) => transport.send(m)
255
+ });
256
+ sessionStarted = true;
257
+ };
258
+ const runner = async (task, ctx) => {
259
+ ctx.signal.addEventListener("abort", () => {
260
+ activeAbort.abort();
261
+ transport.stop();
262
+ });
263
+ await startSession();
264
+ const callId = crypto.randomUUID();
265
+ let toolResult = null;
266
+ const resultPromise = new Promise((resolve, reject) => {
267
+ const budgetMs = ctx.budget.limits.timeoutMs ?? 3e5;
268
+ const timeout = setTimeout(() => {
269
+ reject(new Error(`ACP task timed out for subagent ${ctx.subagentId} (${budgetMs}ms budget)`));
270
+ }, budgetMs);
271
+ transport.onMessage((msg) => {
272
+ if (msg.method === "tools/call" && msg.id !== void 0) {
273
+ clearTimeout(timeout);
274
+ resolve(msg);
275
+ }
276
+ });
277
+ ctx.signal.addEventListener("abort", () => {
278
+ clearTimeout(timeout);
279
+ reject(new Error("Task aborted by parent"));
280
+ });
281
+ });
282
+ try {
283
+ await transport.send({
284
+ method: "agent/run",
285
+ id: callId,
286
+ params: {
287
+ task: task.description,
288
+ sessionId: ctx.subagentId
289
+ }
290
+ });
291
+ toolResult = await resultPromise;
292
+ } catch (err) {
293
+ const msg = err instanceof Error ? err.message : String(err);
294
+ return {
295
+ result: `ACP subagent error: ${msg}`,
296
+ iterations: 0,
297
+ toolCalls: 0
298
+ };
299
+ }
300
+ if (!toolResult) {
301
+ return { result: "ACP subagent returned no result", iterations: 1, toolCalls: 1 };
302
+ }
303
+ const parsed = parseToolResponse(task.id, ctx.subagentId, toolResult);
304
+ return {
305
+ result: parsed.result ?? parsed.error,
306
+ iterations: parsed.iterations,
307
+ toolCalls: parsed.toolCalls
308
+ };
309
+ };
310
+ return runner;
311
+ }
312
+
313
+ export { ClientTransport, ToolTranslator, makeACPSubagentRunner };
314
+ //# sourceMappingURL=client.js.map
315
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agent/stdio-transport.ts","../src/client/tool-translator.ts","../src/integration/acp-subagent-runner.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["/**\n * StdioTransport — bidirectional stdin/stdout communication for ACP.\n *\n * ACP uses newline-delimited JSON-RPC 2.0 messages over stdio:\n * client → agent: JSON-RPC request/notification on stdin\n * agent → client: JSON-RPC response/notification on stdout\n *\n * Start message: clients look for the `[wstack-acp]` marker on stdout before\n * treating subsequent lines as protocol messages.\n */\nimport { buildChildEnv } from '@wrongstack/core';\nimport type { ACPMessage } from '../types/acp-messages.js';\n\nexport interface AgentServerTransport {\n send(msg: ACPMessage): Promise<void>;\n sendRaw(chunk: string): void;\n read(): Promise<ACPMessage | null>;\n close(): void;\n onMessage(handler: (msg: ACPMessage) => void): () => void;\n}\n\nexport class StdioTransport implements AgentServerTransport {\n private readonly stdin = process.stdin;\n private readonly stdout = process.stdout;\n private readonly stderr = process.stderr;\n\n private buffer = '';\n private readonly handlers = new Set<(msg: ACPMessage) => void>();\n private closed = false;\n private resolveRead: ((msg: ACPMessage | null) => void) | null = null;\n private messageQueue: ACPMessage[] = [];\n\n constructor() {\n this.stdin.resume();\n this.stdin.setEncoding('utf8');\n this.stdin.on('data', (chunk: string) => this.onData(chunk));\n this.stdin.on('end', () => this.handleClose());\n this.stdin.on('error', (err: Error) => this.failAll(err));\n }\n\n sendStartupMarker(): void {\n this.stdout.write('[wstack-acp]\\n', 'utf8');\n }\n\n send(msg: ACPMessage): Promise<void> {\n if (this.closed) return Promise.resolve();\n return new Promise((resolve) => {\n const line = JSON.stringify(msg) + '\\n';\n this.stdout.write(line, 'utf8', () => resolve());\n });\n }\n\n sendRaw(chunk: string): void {\n this.stdout.write(chunk, 'utf8');\n }\n\n read(): Promise<ACPMessage | null> {\n if (this.messageQueue.length > 0) return Promise.resolve(this.messageQueue.shift()!);\n if (this.closed) return Promise.resolve(null);\n return new Promise((resolve) => {\n this.resolveRead = resolve;\n });\n }\n\n onMessage(handler: (msg: ACPMessage) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n\n close(): void {\n this.closed = true;\n this.stdin.pause();\n this.resolveRead?.(null);\n this.resolveRead = null;\n }\n\n private onData(chunk: string): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() ?? '';\n\n for (const raw of lines) {\n if (!raw.trim()) continue;\n try {\n this.dispatch(JSON.parse(raw) as ACPMessage);\n } catch (err) {\n this.stderr.write(`[wstack-acp parse error] ${err}\\n`, 'utf8');\n }\n }\n }\n\n private dispatch(msg: ACPMessage): void {\n if (this.resolveRead) {\n const resolve = this.resolveRead;\n this.resolveRead = null;\n resolve(msg);\n } else {\n this.messageQueue.push(msg);\n }\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch (err) {\n this.stderr.write(`[wstack-acp handler error] ${err}\\n`, 'utf8');\n }\n }\n }\n\n private handleClose(): void {\n this.closed = true;\n this.resolveRead?.(null);\n this.resolveRead = null;\n }\n\n private failAll(err: Error): void {\n this.stderr.write(`[wstack-acp stdin error] ${err.message}\\n`, 'utf8');\n this.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// ClientTransport — spawns a child ACP agent process (DIR-1)\n// ---------------------------------------------------------------------------\n\nimport { spawn } from 'node:child_process';\nimport type { EventEmitter } from 'node:events';\n\nexport interface ClientTransportOptions {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n handshakeTimeoutMs?: number;\n}\n\nexport interface ACPChildProcess extends EventEmitter {\n stdout: NodeJS.ReadableStream;\n stdin: NodeJS.WritableStream;\n stderr: NodeJS.ReadableStream;\n pid: number | undefined;\n kill(): void;\n}\n\nexport class ClientTransport {\n private child: ACPChildProcess | null = null;\n private buffer = '';\n private readonly handlers = new Set<(msg: ACPMessage) => void>();\n private closed = false;\n private resolveRead: ((msg: ACPMessage | null) => void) | null = null;\n private messageQueue: ACPMessage[] = [];\n private readonly opts: Required<Pick<ClientTransportOptions, 'handshakeTimeoutMs'>> &\n ClientTransportOptions;\n\n constructor(options: ClientTransportOptions) {\n this.opts = {\n handshakeTimeoutMs: 30_000,\n ...options,\n };\n }\n\n async start(): Promise<void> {\n if (this.child) return;\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n new Error(`ACP child process failed to start within ${this.opts.handshakeTimeoutMs}ms`),\n );\n }, this.opts.handshakeTimeoutMs);\n\n try {\n this.child = spawn(this.opts.command, this.opts.args ?? [], {\n env: { ...buildChildEnv(), ...this.opts.env },\n cwd: this.opts.cwd,\n stdio: ['pipe', 'pipe', 'pipe'],\n }) as unknown as ACPChildProcess;\n } catch (err) {\n clearTimeout(timeout);\n reject(err);\n return;\n }\n\n const child = this.child;\n\n child.stdout.setEncoding('utf8');\n\n const waitForMarker = (chunk: string) => {\n this.buffer += chunk;\n const idx = this.buffer.indexOf('[wstack-acp]\\n');\n if (idx !== -1) {\n this.buffer = this.buffer.slice(idx + '[wstack-acp]\\n'.length);\n child.stdout.removeListener('data', waitForMarker);\n child.stdout.on('data', (c: string) => this.onChildData(c));\n child.stderr.on('data', (c: string) => this.onChildError(c));\n child.on('close', (code: number | null) => this.onChildClose(code));\n clearTimeout(timeout);\n resolve();\n }\n };\n\n child.stdout.on('data', waitForMarker);\n child.stdout.on('error', (err: Error) => {\n clearTimeout(timeout);\n reject(err);\n });\n child.on('error', (err: Error) => {\n clearTimeout(timeout);\n reject(err);\n });\n });\n }\n\n send(msg: ACPMessage): Promise<void> {\n if (!this.child) return Promise.reject(new Error('ClientTransport not started'));\n return new Promise((resolve, reject) => {\n const line = JSON.stringify(msg) + '\\n';\n this.child!.stdin.write(line, 'utf8', (err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n }\n\n read(): Promise<ACPMessage | null> {\n if (this.messageQueue.length > 0) return Promise.resolve(this.messageQueue.shift()!);\n if (this.closed) return Promise.resolve(null);\n return new Promise((resolve) => {\n this.resolveRead = resolve;\n });\n }\n\n onMessage(handler: (msg: ACPMessage) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n\n stop(): void {\n if (!this.child) return;\n this.closed = true;\n try {\n this.child.kill();\n } catch {\n // already dead\n }\n this.child = null;\n }\n\n private onChildData(chunk: string): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() ?? '';\n\n for (const raw of lines) {\n if (!raw.trim()) continue;\n try {\n this.dispatch(JSON.parse(raw) as ACPMessage);\n } catch {\n // skip malformed\n }\n }\n }\n\n private onChildError(chunk: string): void {\n process.stderr.write(`[acp-child stderr] ${chunk}`, 'utf8');\n }\n\n private onChildClose(code: number | null): void {\n this.closed = true;\n this.resolveRead?.(null);\n this.resolveRead = null;\n if (code !== 0 && code !== null) {\n process.stderr.write(`[acp-child exited with code ${code}]\\n`, 'utf8');\n }\n }\n\n private dispatch(msg: ACPMessage): void {\n if (this.resolveRead) {\n const resolve = this.resolveRead;\n this.resolveRead = null;\n resolve(msg);\n } else {\n this.messageQueue.push(msg);\n }\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch {\n // non-fatal\n }\n }\n }\n}\n","/**\n * ToolTranslator — bidirectional translation between WrongStack tools and\n * ACP tool representations.\n *\n * Used by DIR-1 (WrongStack as ACP client) to:\n * - Map WrongStack TaskSpec → ACP task payload\n * - Map ACP tool responses → TaskResult\n *\n * Used by DIR-2 (WrongStack as ACP server) to:\n * - Convert the WrongStack Tool.inputSchema → ACPToolDefinition.inputSchema\n * - (handled by tools-registry.ts — same logic lives there)\n *\n * For DIR-1 async tool calls: ACP agents send progress notifications while\n * a tool is running, then send a final result. The translator handles this\n * by polling for the final [result] notification on the transport.\n */\nimport type {ACPMessage, ACPToolDefinition, ACPToolCallResponse, ContentBlock} from '../types/acp-messages.js';\nimport type {TaskSpec, TaskResult} from '@wrongstack/core';\n\nexport interface ToolTranslatorOptions {\n /**\n * If true (default), wrap tool calls in an async poll loop that waits\n * for progress notifications until a final result arrives.\n */\n asyncTools?: boolean;\n pollIntervalMs?: number;\n totalTimeoutMs?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<ToolTranslatorOptions> = {\n asyncTools: true,\n pollIntervalMs: 500,\n totalTimeoutMs: 120_000,\n};\n\n/** Convert an ACP ACPToolDefinition → a JSON schema object recognisable by WrongStack */\nexport function acpToolToSchema(def: ACPToolDefinition): Record<string, unknown> {\n if (!def.inputSchema) return {type: 'object', properties: {}};\n return def.inputSchema as Record<string, unknown>;\n}\n\n/** Extract tool result text from ACP ContentBlock[] */\nexport function extractTextFromContent(blocks: ContentBlock[]): string {\n const parts: string[] = [];\n for (const b of blocks) {\n if (b.type === 'text') parts.push(b.text);\n else if (b.type === 'resource') parts.push(`[resource: ${b.resource.uri}]`);\n else if (b.type === 'image') parts.push(`[image: ${b.data.slice(0, 20)}...]`);\n else if (b.type === 'progress') {\n if (b.messages?.length) parts.push(b.messages.join('\\n'));\n }\n }\n return parts.join('\\n');\n}\n\n/** Build a TaskSpec from an ACP task payload */\nexport function buildTaskSpec(payload: {\n taskId: string;\n task: string;\n subagentId?: string;\n}): TaskSpec {\n return {\n id: payload.taskId,\n description: payload.task,\n subagentId: payload.subagentId,\n };\n}\n\n/** Parse an ACP tools/call response → TaskResult */\nexport function parseToolResponse(\n taskId: string,\n subagentId: string,\n response: ACPToolCallResponse,\n): TaskResult {\n const blocks = response.result.content;\n const text = extractTextFromContent(blocks);\n\n // Detect error state from isError flag or error-like text\n const isError =\n response.result.isError || text.toLowerCase().includes('error') ||\n text.toLowerCase().includes('failed');\n\n return {\n taskId,\n subagentId,\n status: isError ? 'failed' : 'success',\n result: text,\n iterations: 1,\n toolCalls: 1,\n durationMs: 0,\n };\n}\n\n/** ToolTranslator for DIR-1 — wraps ACP client transport, adds task semantics */\nexport class ToolTranslator {\n private readonly opts: Required<ToolTranslatorOptions>;\n private readonly pending = new Map<string | number, {\n resolve: (v: ACPToolCallResponse) => void;\n reject: (e: Error) => void;\n timeout: ReturnType<typeof setTimeout>;\n }>();\n\n constructor(opts: ToolTranslatorOptions = {}) {\n this.opts = {...DEFAULT_OPTIONS, ...opts};\n }\n\n /**\n * Start listening to a transport for tool responses and cancellations.\n * Call this once after constructing the translator and before sending tasks.\n */\n attachToTransport(\n transport: {onMessage: (h: (msg: ACPMessage) => void) => () => void; send: (msg: ACPMessage) => Promise<void>},\n ): void {\n transport.onMessage((msg) => {\n if (msg.method === 'tools/call' && msg.id !== undefined) {\n const pending = this.pending.get(msg.id);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pending.delete(msg.id!);\n pending.resolve(msg as unknown as ACPToolCallResponse);\n }\n }\n\n // Handle cancellation notifications\n if (msg.method === 'cancel' && msg.id !== undefined) {\n const pending = this.pending.get(msg.id);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pending.delete(msg.id!);\n pending.reject(new Error('Call cancelled by client'));\n }\n }\n });\n }\n\n /**\n * Send a tool call over the transport and wait for a response.\n * If asyncTools is true, polls for progress and resolves when the final\n * response arrives.\n */\n async callTool(\n transport: {send: (msg: ACPMessage) => Promise<void>},\n name: string,\n args: Record<string, unknown>,\n callId: string | number = crypto.randomUUID(),\n ): Promise<ACPToolCallResponse> {\n await transport.send({\n method: 'tools/call',\n id: callId,\n params: {name, arguments: args},\n });\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pending.delete(callId);\n reject(new Error(`Tool call ${name} timed out after ${this.opts.totalTimeoutMs}ms`));\n }, this.opts.totalTimeoutMs);\n\n this.pending.set(callId, {resolve, reject, timeout});\n });\n }\n\n cancelAll(): void {\n for (const [, p] of this.pending) {\n clearTimeout(p.timeout);\n }\n this.pending.clear();\n }\n}\n","/**\n * ACPSubagentRunner — SubagentRunner implementation for DIR-1.\n *\n * Wraps an external ACP agent (Cline, Gemini CLI, Codex CLI, Copilot, etc.)\n * as a WrongStack subagent. The external agent runs its own agent loop;\n * we send it a task via ACP and return the result.\n *\n * Connected to Director / MultiAgentCoordinator via the SubagentRunner\n * interface (same as AgentSubagentRunner).\n */\nimport type {SubagentRunContext, SubagentRunner, TaskSpec} from '@wrongstack/core';\nimport {ClientTransport} from '../agent/stdio-transport.js';\nimport type {ACPToolCallResponse} from '../types/acp-messages.js';\nimport {ToolTranslator, parseToolResponse} from '../client/tool-translator.js';\nimport type {ToolTranslatorOptions} from '../client/tool-translator.js';\n\nexport interface ACPSubagentRunnerOptions {\n /** ACP agent command or npm package (e.g. 'npx', 'gemini', 'gh') */\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n /** Subagent role — used for protocol negotiation and prompt overrides */\n role?: string;\n toolTranslatorOpts?: ToolTranslatorOptions;\n}\n\n/** Map WrongStack ACP agent role → how to spawn it. */\nexport const ACP_AGENT_COMMANDS: Record<string, ACPSubagentRunnerOptions> = {\n cline: {\n command: 'npx',\n args: ['-y', '@agentify/cline'],\n role: 'cline',\n },\n 'gemini-cli': {\n command: 'gemini',\n role: 'gemini-cli',\n },\n copilot: {\n command: 'gh',\n args: ['copilot', 'agent'],\n role: 'copilot',\n },\n openhands: {\n command: 'openhands',\n role: 'openhands',\n },\n goose: {\n command: 'goose',\n role: 'goose',\n },\n};\n\n/**\n * Build an ACPSubagentRunner for a given role, or a generic one from explicit options.\n */\nexport async function makeACPSubagentRunner(\n options: ACPSubagentRunnerOptions,\n): Promise<SubagentRunner> {\n const transport = new ClientTransport({\n command: options.command,\n args: options.args,\n env: options.env,\n cwd: options.cwd,\n handshakeTimeoutMs: 30_000,\n });\n\n const translator = new ToolTranslator(options.toolTranslatorOpts);\n const activeAbort = new AbortController();\n\n let sessionStarted = false;\n\n const startSession = async (): Promise<void> => {\n if (sessionStarted) return;\n await transport.start();\n\n await transport.send({\n method: 'initialize',\n id: '1',\n params: {\n capabilities: ['code-generation', 'async-tools', 'streaming', 'progress'],\n protocolVersion: '2024-11',\n sessionId: options.role ?? 'wrongstack-subagent',\n },\n });\n\n const initResp = await transport.read();\n if (!initResp || initResp.error) {\n throw new Error(`ACP initialize failed: ${initResp?.error?.message ?? 'no response'}`);\n }\n\n translator.attachToTransport({\n onMessage: (h) => transport.onMessage(h),\n send: (m) => transport.send(m),\n });\n\n sessionStarted = true;\n };\n\n const runner: SubagentRunner = async (\n task: TaskSpec,\n ctx: SubagentRunContext,\n ): Promise<{result?: unknown; iterations: number; toolCalls: number}> => {\n ctx.signal.addEventListener('abort', () => {\n activeAbort.abort();\n transport.stop();\n });\n\n await startSession();\n\n const callId = crypto.randomUUID();\n let toolResult: ACPToolCallResponse | null = null;\n\n const resultPromise = new Promise<ACPToolCallResponse>((resolve, reject) => {\n const budgetMs = ctx.budget.limits.timeoutMs ?? 300_000;\n\n const timeout = setTimeout(() => {\n reject(new Error(`ACP task timed out for subagent ${ctx.subagentId} (${budgetMs}ms budget)`));\n }, budgetMs);\n\n transport.onMessage((msg) => {\n if (msg.method === 'tools/call' && msg.id !== undefined) {\n clearTimeout(timeout);\n resolve(msg as unknown as ACPToolCallResponse);\n }\n });\n\n ctx.signal.addEventListener('abort', () => {\n clearTimeout(timeout);\n reject(new Error('Task aborted by parent'));\n });\n });\n\n try {\n // Most ACP agents accept a free-form task string as their primary input.\n // Use the tools/call protocol with a special 'task' pseudo-tool if the\n // agent advertises it; otherwise send it as an initialize session detail\n // or a custom agent/run message. The agent will respond on stdout.\n await transport.send({\n method: 'agent/run',\n id: callId,\n params: {\n task: task.description,\n sessionId: ctx.subagentId,\n },\n });\n\n toolResult = await resultPromise;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n result: `ACP subagent error: ${msg}`,\n iterations: 0,\n toolCalls: 0,\n };\n }\n\n if (!toolResult) {\n return {result: 'ACP subagent returned no result', iterations: 1, toolCalls: 1};\n }\n\n const parsed = parseToolResponse(task.id, ctx.subagentId, toolResult);\n return {\n result: parsed.result ?? parsed.error,\n iterations: parsed.iterations,\n toolCalls: parsed.toolCalls,\n };\n };\n\n return runner;\n}\n\n/** Returns the runner and a stop function to clean up the transport. */\nexport async function makeACPSubagentRunnerWithStop(\n options: ACPSubagentRunnerOptions,\n): Promise<{runner: SubagentRunner; stop: () => void}> {\n const transport = new ClientTransport({\n command: options.command,\n args: options.args,\n env: options.env,\n cwd: options.cwd,\n handshakeTimeoutMs: 30_000,\n });\n\n const translator = new ToolTranslator(options.toolTranslatorOpts);\n const activeAbort = new AbortController();\n\n let sessionStarted = false;\n\n const startSession = async (): Promise<void> => {\n if (sessionStarted) return;\n await transport.start();\n\n await transport.send({\n method: 'initialize',\n id: '1',\n params: {\n capabilities: ['code-generation', 'async-tools', 'streaming', 'progress'],\n protocolVersion: '2024-11',\n sessionId: options.role ?? 'wrongstack-subagent',\n },\n });\n\n const initResp = await transport.read();\n if (!initResp || initResp.error) {\n throw new Error(`ACP initialize failed: ${initResp?.error?.message ?? 'no response'}`);\n }\n\n translator.attachToTransport({\n onMessage: (h) => transport.onMessage(h),\n send: (m) => transport.send(m),\n });\n\n sessionStarted = true;\n };\n\n const stop = () => {\n activeAbort.abort();\n transport.stop();\n };\n\n const runner: SubagentRunner = async (\n task: TaskSpec,\n ctx: SubagentRunContext,\n ): Promise<{result?: unknown; iterations: number; toolCalls: number}> => {\n ctx.signal.addEventListener('abort', () => {\n activeAbort.abort();\n transport.stop();\n });\n\n await startSession();\n\n const callId = crypto.randomUUID();\n let toolResult: ACPToolCallResponse | null = null;\n\n const resultPromise = new Promise<ACPToolCallResponse>((resolve, reject) => {\n const budgetMs = ctx.budget.limits.timeoutMs ?? 300_000;\n\n const timeout = setTimeout(() => {\n reject(new Error(`ACP task timed out for subagent ${ctx.subagentId} (${budgetMs}ms budget)`));\n }, budgetMs);\n\n transport.onMessage((msg) => {\n if (msg.method === 'tools/call' && msg.id !== undefined) {\n clearTimeout(timeout);\n resolve(msg as unknown as ACPToolCallResponse);\n }\n });\n\n ctx.signal.addEventListener('abort', () => {\n clearTimeout(timeout);\n reject(new Error('Task aborted by parent'));\n });\n });\n\n try {\n await transport.send({\n method: 'agent/run',\n id: callId,\n params: {\n task: task.description,\n sessionId: ctx.subagentId,\n },\n });\n\n toolResult = await resultPromise;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n result: `ACP subagent error: ${msg}`,\n iterations: 0,\n toolCalls: 0,\n };\n }\n\n if (!toolResult) {\n return {result: 'ACP subagent returned no result', iterations: 1, toolCalls: 1};\n }\n\n const parsed = parseToolResponse(task.id, ctx.subagentId, toolResult);\n return {\n result: parsed.result ?? parsed.error,\n iterations: parsed.iterations,\n toolCalls: parsed.toolCalls,\n };\n };\n\n return {runner, stop};\n}\n"]}
@@ -1,3 +1,6 @@
1
+ import { g as ACPMessage, s as ACPToolCallResponse } from './stdio-transport-DqWq-2iP.js';
2
+ import { SubagentRunner } from '@wrongstack/core';
3
+
1
4
  /**
2
5
  * ToolTranslator — bidirectional translation between WrongStack tools and
3
6
  * ACP tool representations.
@@ -14,9 +17,8 @@
14
17
  * a tool is running, then send a final result. The translator handles this
15
18
  * by polling for the final [result] notification on the transport.
16
19
  */
17
- import type { ACPMessage, ACPToolDefinition, ACPToolCallResponse, ContentBlock } from '../types/acp-messages.js';
18
- import type { TaskSpec, TaskResult } from '@wrongstack/core';
19
- export interface ToolTranslatorOptions {
20
+
21
+ interface ToolTranslatorOptions {
20
22
  /**
21
23
  * If true (default), wrap tool calls in an async poll loop that waits
22
24
  * for progress notifications until a final result arrives.
@@ -25,20 +27,8 @@ export interface ToolTranslatorOptions {
25
27
  pollIntervalMs?: number;
26
28
  totalTimeoutMs?: number;
27
29
  }
28
- /** Convert an ACP ACPToolDefinition → a JSON schema object recognisable by WrongStack */
29
- export declare function acpToolToSchema(def: ACPToolDefinition): Record<string, unknown>;
30
- /** Extract tool result text from ACP ContentBlock[] */
31
- export declare function extractTextFromContent(blocks: ContentBlock[]): string;
32
- /** Build a TaskSpec from an ACP task payload */
33
- export declare function buildTaskSpec(payload: {
34
- taskId: string;
35
- task: string;
36
- subagentId?: string;
37
- }): TaskSpec;
38
- /** Parse an ACP tools/call response → TaskResult */
39
- export declare function parseToolResponse(taskId: string, subagentId: string, response: ACPToolCallResponse): TaskResult;
40
30
  /** ToolTranslator for DIR-1 — wraps ACP client transport, adds task semantics */
41
- export declare class ToolTranslator {
31
+ declare class ToolTranslator {
42
32
  private readonly opts;
43
33
  private readonly pending;
44
34
  constructor(opts?: ToolTranslatorOptions);
@@ -60,4 +50,38 @@ export declare class ToolTranslator {
60
50
  }, name: string, args: Record<string, unknown>, callId?: string | number): Promise<ACPToolCallResponse>;
61
51
  cancelAll(): void;
62
52
  }
63
- //# sourceMappingURL=tool-translator.d.ts.map
53
+
54
+ /**
55
+ * ACPSubagentRunner — SubagentRunner implementation for DIR-1.
56
+ *
57
+ * Wraps an external ACP agent (Cline, Gemini CLI, Codex CLI, Copilot, etc.)
58
+ * as a WrongStack subagent. The external agent runs its own agent loop;
59
+ * we send it a task via ACP and return the result.
60
+ *
61
+ * Connected to Director / MultiAgentCoordinator via the SubagentRunner
62
+ * interface (same as AgentSubagentRunner).
63
+ */
64
+
65
+ interface ACPSubagentRunnerOptions {
66
+ /** ACP agent command or npm package (e.g. 'npx', 'gemini', 'gh') */
67
+ command: string;
68
+ args?: string[];
69
+ env?: Record<string, string>;
70
+ cwd?: string;
71
+ /** Subagent role — used for protocol negotiation and prompt overrides */
72
+ role?: string;
73
+ toolTranslatorOpts?: ToolTranslatorOptions;
74
+ }
75
+ /** Map WrongStack ACP agent role → how to spawn it. */
76
+ declare const ACP_AGENT_COMMANDS: Record<string, ACPSubagentRunnerOptions>;
77
+ /**
78
+ * Build an ACPSubagentRunner for a given role, or a generic one from explicit options.
79
+ */
80
+ declare function makeACPSubagentRunner(options: ACPSubagentRunnerOptions): Promise<SubagentRunner>;
81
+ /** Returns the runner and a stop function to clean up the transport. */
82
+ declare function makeACPSubagentRunnerWithStop(options: ACPSubagentRunnerOptions): Promise<{
83
+ runner: SubagentRunner;
84
+ stop: () => void;
85
+ }>;
86
+
87
+ export { type ACPSubagentRunnerOptions as A, ToolTranslator as T, ACP_AGENT_COMMANDS as a, type ToolTranslatorOptions as b, makeACPSubagentRunnerWithStop as c, makeACPSubagentRunner as m };
package/dist/index.d.ts CHANGED
@@ -1,24 +1,5 @@
1
- /**
2
- * @wrongstack/acp ACP integration public surface.
3
- *
4
- * DIR-2: WrongStack as ACP Server
5
- * import { WrongStackACPServer } from '@wrongstack/acp/agent';
6
- *
7
- * DIR-1: WrongStack as ACP Client
8
- * import { makeACPSubagentRunner } from '@wrongstack/acp/client';
9
- */
10
- export { StdioTransport } from './agent/stdio-transport.js';
11
- export type { AgentServerTransport } from './agent/stdio-transport.js';
12
- export { ACPToolsRegistry } from './agent/tools-registry.js';
13
- export { ACPProtocolHandler } from './agent/protocol-handler.js';
14
- export { WrongStackACPServer } from './agent/wrongstack-acp-agent.js';
15
- export type { WrongStackACPServerOptions } from './agent/wrongstack-acp-agent.js';
16
- export { ClientTransport } from './agent/stdio-transport.js';
17
- export type { ClientTransportOptions, ACPChildProcess } from './agent/stdio-transport.js';
18
- export { ToolTranslator } from './client/tool-translator.js';
19
- export type { ToolTranslatorOptions } from './client/tool-translator.js';
20
- export { makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from './integration/acp-subagent-runner.js';
21
- export type { ACPSubagentRunnerOptions } from './integration/acp-subagent-runner.js';
22
- export { ACP_AGENT_COMMANDS } from './integration/acp-subagent-runner.js';
23
- export * from './types/acp-messages.js';
24
- //# sourceMappingURL=index.d.ts.map
1
+ export { A as ACPCancelParams, a as ACPCapabilities, b as ACPChildProcess, c as ACPError, d as ACPImageContent, e as ACPInitializeParams, f as ACPInputSchema, g as ACPMessage, h as ACPNotification, i as ACPPlanContent, j as ACPPlanStep, k as ACPProgressContent, l as ACPRequest, m as ACPResourceContent, n as ACPResponse, o as ACPSessionInfo, p as ACPSessionMode, q as ACPTextContent, r as ACPToolCallRequest, s as ACPToolCallResponse, t as ACPToolDefinition, u as ACPToolList, v as ACPToolResult, w as AgentServerTransport, C as ClientTransport, x as ClientTransportOptions, y as ContentBlock, S as StdioTransport } from './stdio-transport-DqWq-2iP.js';
2
+ export { ACPProtocolHandler, ACPToolsRegistry, WrongStackACPServer, WrongStackACPServerOptions } from './agent.js';
3
+ export { A as ACPSubagentRunnerOptions, a as ACP_AGENT_COMMANDS, T as ToolTranslator, b as ToolTranslatorOptions, m as makeACPSubagentRunner, c as makeACPSubagentRunnerWithStop } from './index-CncECXiF.js';
4
+ import 'node:events';
5
+ import '@wrongstack/core';