@wrongstack/acp 1.0.19 → 1.0.24
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/dist/agent/protocol-contract.d.ts +13 -0
- package/dist/agent/protocol-handler.d.ts +2 -0
- package/dist/agent/protocol-session-management.d.ts +2 -1
- package/dist/agent/protocol-session-ops.d.ts +1 -4
- package/dist/agent.js +223 -101
- package/dist/client/acp-late-session.d.ts +4 -0
- package/dist/client/acp-request-state.d.ts +9 -0
- package/dist/client/file-server.d.ts +2 -0
- package/dist/client.js +130 -65
- package/dist/index.js +363 -184
- package/dist/types/acp-v1.d.ts +4 -3
- package/dist/wrongstack-acp-agent.js +213 -99
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -213,6 +213,7 @@ var ClientTransport = class {
|
|
|
213
213
|
this.messageQueue.length = 0;
|
|
214
214
|
this.queuedChars = 0;
|
|
215
215
|
this.handlers.clear();
|
|
216
|
+
this.claimHandlers.clear();
|
|
216
217
|
if (code !== 0 && code !== null) {
|
|
217
218
|
writeErr(`[acp-child exited with code ${code}]
|
|
218
219
|
`);
|
|
@@ -223,17 +224,28 @@ var ClientTransport = class {
|
|
|
223
224
|
const resolve3 = this.resolveRead;
|
|
224
225
|
this.resolveRead = null;
|
|
225
226
|
resolve3(msg);
|
|
226
|
-
} else
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
} else {
|
|
228
|
+
let claimed = false;
|
|
229
|
+
for (const handler of this.claimHandlers) {
|
|
230
|
+
try {
|
|
231
|
+
if (handler(msg)) claimed = true;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
writeErr(`[acp-child handler error] ${err}
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!claimed && this.handlers.size === 0) {
|
|
238
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
239
|
+
writeErr(
|
|
240
|
+
`[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
|
|
230
241
|
`
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
);
|
|
243
|
+
this.stop();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.messageQueue.push({ message: msg, chars });
|
|
247
|
+
this.queuedChars += chars;
|
|
234
248
|
}
|
|
235
|
-
this.messageQueue.push({ message: msg, chars });
|
|
236
|
-
this.queuedChars += chars;
|
|
237
249
|
}
|
|
238
250
|
for (const handler of this.handlers) {
|
|
239
251
|
try {
|
|
@@ -269,6 +281,55 @@ function readPackageVersion(load = () => require2("../package.json")) {
|
|
|
269
281
|
}
|
|
270
282
|
var ACP_PACKAGE_VERSION = readPackageVersion();
|
|
271
283
|
|
|
284
|
+
// src/client/acp-late-session.ts
|
|
285
|
+
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
286
|
+
function cancelLateAcpSession(transport, createPromise) {
|
|
287
|
+
createPromise.then(async (lateId) => {
|
|
288
|
+
try {
|
|
289
|
+
await new Promise((resolve3, reject) => {
|
|
290
|
+
const timer = setTimeout(() => {
|
|
291
|
+
reject(new Error("late session/cancel send timed out"));
|
|
292
|
+
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
293
|
+
Promise.resolve(
|
|
294
|
+
transport.send({
|
|
295
|
+
jsonrpc: "2.0",
|
|
296
|
+
method: "session/cancel",
|
|
297
|
+
params: { sessionId: lateId }
|
|
298
|
+
})
|
|
299
|
+
).then(
|
|
300
|
+
() => {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
resolve3();
|
|
303
|
+
},
|
|
304
|
+
(sendErr) => {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
} catch (err) {
|
|
311
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
312
|
+
console.warn(
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
level: "warn",
|
|
315
|
+
event: "acp_session.late_cancel_failed",
|
|
316
|
+
sessionId: lateId,
|
|
317
|
+
message
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}).catch((reason) => {
|
|
322
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
323
|
+
console.warn(
|
|
324
|
+
JSON.stringify({
|
|
325
|
+
level: "warn",
|
|
326
|
+
event: "acp_session.late_cancel_failed",
|
|
327
|
+
reason: message
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
272
333
|
// src/client/acp-message-routing.ts
|
|
273
334
|
function isBestEffortAckMethod(method) {
|
|
274
335
|
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
@@ -316,6 +377,13 @@ var FileServer = class {
|
|
|
316
377
|
}
|
|
317
378
|
/** Read a text file. Returns the content as a string. */
|
|
318
379
|
async readTextFile(params) {
|
|
380
|
+
if (params.line != null && (!Number.isSafeInteger(params.line) || params.line < 1) || params.limit != null && (!Number.isSafeInteger(params.limit) || params.limit < 0)) {
|
|
381
|
+
throw new FsError(
|
|
382
|
+
"INVALID_PATH",
|
|
383
|
+
params.path,
|
|
384
|
+
"line must be a positive integer and limit a non-negative integer"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
319
387
|
const safe = await this.resolveInside(params.path);
|
|
320
388
|
const controller = new AbortController();
|
|
321
389
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -334,7 +402,12 @@ var FileServer = class {
|
|
|
334
402
|
encoding: "utf8",
|
|
335
403
|
signal: controller.signal
|
|
336
404
|
});
|
|
337
|
-
return { content };
|
|
405
|
+
if (params.line == null && params.limit == null) return { content };
|
|
406
|
+
const lines = content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
407
|
+
const start = (params.line ?? 1) - 1;
|
|
408
|
+
return {
|
|
409
|
+
content: lines.slice(start, params.limit == null ? void 0 : start + params.limit).join("")
|
|
410
|
+
};
|
|
338
411
|
} catch (err) {
|
|
339
412
|
if (err instanceof FsError) throw err;
|
|
340
413
|
if (controller.signal.aborted) {
|
|
@@ -439,7 +512,8 @@ var FileServer = class {
|
|
|
439
512
|
throw mapFsError(err, resolvedPath);
|
|
440
513
|
}
|
|
441
514
|
const normReal = isWindows ? real.toLowerCase() : real;
|
|
442
|
-
|
|
515
|
+
const realRootWithSep = normRealRoot.endsWith(path.sep) ? normRealRoot : normRealRoot + path.sep;
|
|
516
|
+
if (normReal === normRealRoot || normReal.startsWith(realRootWithSep)) return;
|
|
443
517
|
throw new FsError(
|
|
444
518
|
"OUTSIDE_ROOT",
|
|
445
519
|
resolvedPath,
|
|
@@ -488,9 +562,12 @@ async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbac
|
|
|
488
562
|
);
|
|
489
563
|
await sender.sendResult(id, { outcome });
|
|
490
564
|
} catch (err) {
|
|
565
|
+
if (isAbortLikeError(err)) {
|
|
566
|
+
await sender.sendResult(id, { outcome: { outcome: "cancelled" } });
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
491
569
|
const message = err instanceof Error ? err.message : String(err);
|
|
492
|
-
|
|
493
|
-
await sender.sendErrorResponse(id, code, `permission policy failed: ${message}`);
|
|
570
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
494
571
|
}
|
|
495
572
|
}
|
|
496
573
|
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, callbackOptions = {}) {
|
|
@@ -526,7 +603,9 @@ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, cal
|
|
|
526
603
|
if (msg.method === "fs/read_text_file") {
|
|
527
604
|
const result = await fileServer.readTextFile({
|
|
528
605
|
sessionId: params.sessionId ?? "",
|
|
529
|
-
path: params.path
|
|
606
|
+
path: params.path,
|
|
607
|
+
line: params.line,
|
|
608
|
+
limit: params.limit
|
|
530
609
|
});
|
|
531
610
|
await sender.sendResult(id, result);
|
|
532
611
|
} else {
|
|
@@ -776,7 +855,7 @@ async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
776
855
|
"agent does not support session/load (loadSession capability not advertised)"
|
|
777
856
|
);
|
|
778
857
|
}
|
|
779
|
-
if (ctx.sessionId) {
|
|
858
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
780
859
|
await ctx.closeSession();
|
|
781
860
|
}
|
|
782
861
|
ctx.resetScratch();
|
|
@@ -800,7 +879,7 @@ async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
800
879
|
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
801
880
|
);
|
|
802
881
|
}
|
|
803
|
-
if (ctx.sessionId) {
|
|
882
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
804
883
|
await ctx.closeSession();
|
|
805
884
|
}
|
|
806
885
|
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
@@ -978,6 +1057,7 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
|
978
1057
|
}
|
|
979
1058
|
return;
|
|
980
1059
|
}
|
|
1060
|
+
case "agent_thought_chunk":
|
|
981
1061
|
case "thought_chunk": {
|
|
982
1062
|
const text = extractText(u.content);
|
|
983
1063
|
if (text) {
|
|
@@ -1643,7 +1723,6 @@ function finitePositiveLimit(value, fallback) {
|
|
|
1643
1723
|
}
|
|
1644
1724
|
|
|
1645
1725
|
// src/client/acp-session.ts
|
|
1646
|
-
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
1647
1726
|
var ACPSession = class _ACPSession {
|
|
1648
1727
|
transport;
|
|
1649
1728
|
fileServer;
|
|
@@ -1813,7 +1892,7 @@ var ACPSession = class _ACPSession {
|
|
|
1813
1892
|
throw new ACPSessionError("protocol_error", "initialize returned no protocolVersion");
|
|
1814
1893
|
}
|
|
1815
1894
|
const r = result;
|
|
1816
|
-
if (r.protocolVersion
|
|
1895
|
+
if (r.protocolVersion !== ACP_PROTOCOL_VERSION) {
|
|
1817
1896
|
throw new ACPSessionError(
|
|
1818
1897
|
"unsupported_capability",
|
|
1819
1898
|
`agent requires protocolVersion=${r.protocolVersion}, client supports up to ${ACP_PROTOCOL_VERSION}`
|
|
@@ -1849,6 +1928,12 @@ var ACPSession = class _ACPSession {
|
|
|
1849
1928
|
`auth method "${methodId}" not in advertised methods: ${this.authMethods.map((m) => m.id).join(", ")}`
|
|
1850
1929
|
);
|
|
1851
1930
|
}
|
|
1931
|
+
if (this.authMethods.find((m) => m.id === methodId)?.type === "terminal") {
|
|
1932
|
+
throw new ACPSessionError(
|
|
1933
|
+
"auth_failed",
|
|
1934
|
+
"Terminal authentication requires interactive login followed by reconnect; it cannot use authenticate"
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1852
1937
|
const id = this.allocId();
|
|
1853
1938
|
const result = await this.sendRequest(id, "authenticate", { methodId });
|
|
1854
1939
|
if (isJsonRpcError(result)) {
|
|
@@ -1944,6 +2029,16 @@ var ACPSession = class _ACPSession {
|
|
|
1944
2029
|
if (signal.aborted) {
|
|
1945
2030
|
return emptyRunResult("cancelled");
|
|
1946
2031
|
}
|
|
2032
|
+
const caps = this.agentCapabilities.promptCapabilities;
|
|
2033
|
+
for (const block of blocks) {
|
|
2034
|
+
const supported = block.type === "image" ? caps?.image : block.type === "audio" ? caps?.audio : block.type === "resource" ? caps?.embeddedContext : true;
|
|
2035
|
+
if (supported !== true) {
|
|
2036
|
+
throw new ACPSessionError(
|
|
2037
|
+
"unsupported_capability",
|
|
2038
|
+
`agent does not support ${block.type} prompt content`
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
1947
2042
|
let cancelled = false;
|
|
1948
2043
|
this.promptCallbackAbort = new AbortController();
|
|
1949
2044
|
const onAbort = () => {
|
|
@@ -2078,50 +2173,7 @@ var ACPSession = class _ACPSession {
|
|
|
2078
2173
|
* being silently swallowed.
|
|
2079
2174
|
*/
|
|
2080
2175
|
cancelLateSession(createPromise) {
|
|
2081
|
-
|
|
2082
|
-
try {
|
|
2083
|
-
await new Promise((resolve3, reject) => {
|
|
2084
|
-
const timer = setTimeout(() => {
|
|
2085
|
-
reject(new Error("late session/cancel send timed out"));
|
|
2086
|
-
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
2087
|
-
Promise.resolve(
|
|
2088
|
-
this.transport.send({
|
|
2089
|
-
jsonrpc: "2.0",
|
|
2090
|
-
method: "session/cancel",
|
|
2091
|
-
params: { sessionId: lateId }
|
|
2092
|
-
})
|
|
2093
|
-
).then(
|
|
2094
|
-
() => {
|
|
2095
|
-
clearTimeout(timer);
|
|
2096
|
-
resolve3();
|
|
2097
|
-
},
|
|
2098
|
-
(sendErr) => {
|
|
2099
|
-
clearTimeout(timer);
|
|
2100
|
-
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
2101
|
-
}
|
|
2102
|
-
);
|
|
2103
|
-
});
|
|
2104
|
-
} catch (err) {
|
|
2105
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2106
|
-
console.warn(
|
|
2107
|
-
JSON.stringify({
|
|
2108
|
-
level: "warn",
|
|
2109
|
-
event: "acp_session.late_cancel_failed",
|
|
2110
|
-
sessionId: lateId,
|
|
2111
|
-
message
|
|
2112
|
-
})
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
}).catch((reason) => {
|
|
2116
|
-
const message = reason instanceof Error ? reason.message : String(reason);
|
|
2117
|
-
console.warn(
|
|
2118
|
-
JSON.stringify({
|
|
2119
|
-
level: "warn",
|
|
2120
|
-
event: "acp_session.late_cancel_failed",
|
|
2121
|
-
reason: message
|
|
2122
|
-
})
|
|
2123
|
-
);
|
|
2124
|
-
});
|
|
2176
|
+
cancelLateAcpSession(this.transport, createPromise);
|
|
2125
2177
|
}
|
|
2126
2178
|
async closeSession() {
|
|
2127
2179
|
if (!this.sessionId) return;
|
|
@@ -2168,7 +2220,13 @@ var ACPSession = class _ACPSession {
|
|
|
2168
2220
|
// Wire layer
|
|
2169
2221
|
// ────────────────────────────────────────────────────────────────────
|
|
2170
2222
|
allocId() {
|
|
2171
|
-
|
|
2223
|
+
let candidate = Number.isSafeInteger(this.nextId) && this.nextId >= 1 ? this.nextId : 1;
|
|
2224
|
+
for (let probe = 0; probe <= this.pending.size; probe++) {
|
|
2225
|
+
this.nextId = candidate >= Number.MAX_SAFE_INTEGER ? 1 : candidate + 1;
|
|
2226
|
+
if (!this.pending.has(candidate)) return candidate;
|
|
2227
|
+
candidate = this.nextId;
|
|
2228
|
+
}
|
|
2229
|
+
throw new ACPSessionError("protocol_error", "no JSON-RPC request id is available");
|
|
2172
2230
|
}
|
|
2173
2231
|
async sendRequest(id, method, params, timeoutMs) {
|
|
2174
2232
|
return new Promise((resolve3, reject) => {
|
|
@@ -2236,7 +2294,7 @@ var ACPSession = class _ACPSession {
|
|
|
2236
2294
|
}
|
|
2237
2295
|
const terminal = this.authMethods.find((m) => m.type === "terminal");
|
|
2238
2296
|
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
2239
|
-
const setup = setupArgs !== void 0 ? `${this.opts.command} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
2297
|
+
const setup = setupArgs !== void 0 ? `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
2240
2298
|
throw new ACPSessionError(
|
|
2241
2299
|
"auth_failed",
|
|
2242
2300
|
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
@@ -2318,7 +2376,7 @@ var ACPSession = class _ACPSession {
|
|
|
2318
2376
|
}
|
|
2319
2377
|
if (isBestEffortAckMethod(msg.method)) {
|
|
2320
2378
|
if (msg.id !== void 0) {
|
|
2321
|
-
this.
|
|
2379
|
+
this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(() => {
|
|
2322
2380
|
});
|
|
2323
2381
|
}
|
|
2324
2382
|
return;
|
|
@@ -2327,6 +2385,13 @@ var ACPSession = class _ACPSession {
|
|
|
2327
2385
|
return;
|
|
2328
2386
|
}
|
|
2329
2387
|
if (msg.method) {
|
|
2388
|
+
if (msg.id !== void 0) {
|
|
2389
|
+
void this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(
|
|
2390
|
+
() => {
|
|
2391
|
+
}
|
|
2392
|
+
);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2330
2395
|
console.warn(
|
|
2331
2396
|
JSON.stringify({
|
|
2332
2397
|
level: "warn",
|