@wrongstack/acp 1.0.28 → 1.0.32
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.js +54 -24
- package/dist/client/acp-session-messages.d.ts +24 -0
- package/dist/client/acp-session.d.ts +1 -0
- package/dist/client.js +258 -187
- package/dist/index.js +287 -191
- package/dist/wrongstack-acp-agent.js +24 -1
- package/package.json +4 -3
package/dist/client.js
CHANGED
|
@@ -318,8 +318,8 @@ function cancelLateAcpSession(transport, createPromise) {
|
|
|
318
318
|
const timer = setTimeout(() => {
|
|
319
319
|
reject(new Error("late session/cancel send timed out"));
|
|
320
320
|
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
321
|
-
Promise.resolve(
|
|
322
|
-
transport.send({
|
|
321
|
+
Promise.resolve().then(
|
|
322
|
+
() => transport.send({
|
|
323
323
|
jsonrpc: "2.0",
|
|
324
324
|
method: "session/cancel",
|
|
325
325
|
params: { sessionId: lateId }
|
|
@@ -358,11 +358,6 @@ function cancelLateAcpSession(transport, createPromise) {
|
|
|
358
358
|
});
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
// src/client/acp-message-routing.ts
|
|
362
|
-
function isBestEffortAckMethod(method) {
|
|
363
|
-
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
364
|
-
}
|
|
365
|
-
|
|
366
361
|
// src/client/acp-session-errors.ts
|
|
367
362
|
var ACPSessionError = class extends Error {
|
|
368
363
|
kind;
|
|
@@ -375,7 +370,7 @@ var ACPSessionError = class extends Error {
|
|
|
375
370
|
}
|
|
376
371
|
};
|
|
377
372
|
function isJsonRpcError(v) {
|
|
378
|
-
return typeof v === "object" && v !== null &&
|
|
373
|
+
return typeof v === "object" && v !== null && Number.isSafeInteger(v.code) && typeof v.message === "string";
|
|
379
374
|
}
|
|
380
375
|
function isAuthRequiredError(err) {
|
|
381
376
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -423,6 +418,7 @@ async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
423
418
|
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
424
419
|
await ctx.closeSession();
|
|
425
420
|
}
|
|
421
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
426
422
|
ctx.resetScratch();
|
|
427
423
|
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
428
424
|
const id = ctx.allocId();
|
|
@@ -434,6 +430,7 @@ async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
434
430
|
if (isJsonRpcError(result)) {
|
|
435
431
|
throw new ACPSessionError("prompt_failed", `session/load failed: ${result.message}`, result);
|
|
436
432
|
}
|
|
433
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
437
434
|
ctx.setSessionId(sessionId);
|
|
438
435
|
}
|
|
439
436
|
async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
@@ -447,6 +444,7 @@ async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
447
444
|
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
448
445
|
await ctx.closeSession();
|
|
449
446
|
}
|
|
447
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
450
448
|
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
451
449
|
const id = ctx.allocId();
|
|
452
450
|
const result = await ctx.sendRequest(id, "session/resume", {
|
|
@@ -457,6 +455,7 @@ async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
457
455
|
if (isJsonRpcError(result)) {
|
|
458
456
|
throw new ACPSessionError("prompt_failed", `session/resume failed: ${result.message}`, result);
|
|
459
457
|
}
|
|
458
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
460
459
|
ctx.setSessionId(sessionId);
|
|
461
460
|
}
|
|
462
461
|
async function executeListSessions(ctx, cursor, cwd) {
|
|
@@ -605,10 +604,11 @@ async function executeCreateSession(ctx) {
|
|
|
605
604
|
}
|
|
606
605
|
|
|
607
606
|
// src/client/acp-session-auth.ts
|
|
607
|
+
function ensureOpen(host) {
|
|
608
|
+
if (host.state === "closed") throw new ACPSessionError("closed", "session is closed");
|
|
609
|
+
}
|
|
608
610
|
async function authenticate(host, methodId) {
|
|
609
|
-
|
|
610
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
611
|
-
}
|
|
611
|
+
ensureOpen(host);
|
|
612
612
|
if (host.state !== "ready" && host.state !== "authenticated") {
|
|
613
613
|
throw new ACPSessionError(
|
|
614
614
|
"protocol_error",
|
|
@@ -633,12 +633,11 @@ async function authenticate(host, methodId) {
|
|
|
633
633
|
if (isJsonRpcError(result)) {
|
|
634
634
|
throw new ACPSessionError("auth_failed", `authenticate failed: ${result.message}`, result);
|
|
635
635
|
}
|
|
636
|
+
ensureOpen(host);
|
|
636
637
|
host.state = "authenticated";
|
|
637
638
|
}
|
|
638
639
|
async function logout(host) {
|
|
639
|
-
|
|
640
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
641
|
-
}
|
|
640
|
+
ensureOpen(host);
|
|
642
641
|
if (!host.agentCapabilities.auth?.logout) {
|
|
643
642
|
throw new ACPSessionError(
|
|
644
643
|
"unsupported_capability",
|
|
@@ -650,6 +649,7 @@ async function logout(host) {
|
|
|
650
649
|
if (isJsonRpcError(result)) {
|
|
651
650
|
throw new ACPSessionError("logout_failed", `logout failed: ${result.message}`, result);
|
|
652
651
|
}
|
|
652
|
+
ensureOpen(host);
|
|
653
653
|
host.state = "ready";
|
|
654
654
|
}
|
|
655
655
|
async function createSessionWithAuth(host) {
|
|
@@ -691,6 +691,44 @@ async function ensureAuthenticated(host) {
|
|
|
691
691
|
);
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
+
// src/client/acp-session-content.ts
|
|
695
|
+
function textContent(text) {
|
|
696
|
+
return { type: "text", text };
|
|
697
|
+
}
|
|
698
|
+
function imageContent(mimeType, data) {
|
|
699
|
+
return { type: "image", mimeType, data };
|
|
700
|
+
}
|
|
701
|
+
function audioContent(mimeType, data) {
|
|
702
|
+
return { type: "audio", mimeType, data };
|
|
703
|
+
}
|
|
704
|
+
function extractText(block) {
|
|
705
|
+
if (typeof block !== "object" || block === null) return "";
|
|
706
|
+
const b = block;
|
|
707
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
708
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
709
|
+
return b.resource.text;
|
|
710
|
+
}
|
|
711
|
+
return "";
|
|
712
|
+
}
|
|
713
|
+
function isRecord(v) {
|
|
714
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
715
|
+
}
|
|
716
|
+
function emptyRunResult(stopReason) {
|
|
717
|
+
return {
|
|
718
|
+
text: "",
|
|
719
|
+
stopReason,
|
|
720
|
+
hasText: false,
|
|
721
|
+
toolCalls: [],
|
|
722
|
+
diffs: [],
|
|
723
|
+
thoughts: ""
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/client/acp-message-routing.ts
|
|
728
|
+
function isBestEffortAckMethod(method) {
|
|
729
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
730
|
+
}
|
|
731
|
+
|
|
694
732
|
// src/client/file-server.ts
|
|
695
733
|
import { randomBytes } from "node:crypto";
|
|
696
734
|
import { realpathSync } from "node:fs";
|
|
@@ -747,6 +785,9 @@ var FileServer = class {
|
|
|
747
785
|
const stat2 = await this.operations.stat(safe).catch((err) => {
|
|
748
786
|
throw mapFsError(err, safe);
|
|
749
787
|
});
|
|
788
|
+
if (controller.signal.aborted) {
|
|
789
|
+
throw new FsError("TIMEOUT", safe, `readTextFile timed out after ${this.timeoutMs}ms`);
|
|
790
|
+
}
|
|
750
791
|
if (stat2.size > this.maxReadBytes) {
|
|
751
792
|
throw new FsError(
|
|
752
793
|
"TOO_LARGE",
|
|
@@ -758,6 +799,17 @@ var FileServer = class {
|
|
|
758
799
|
encoding: "utf8",
|
|
759
800
|
signal: controller.signal
|
|
760
801
|
});
|
|
802
|
+
if (controller.signal.aborted) {
|
|
803
|
+
throw new FsError("TIMEOUT", safe, `readTextFile timed out after ${this.timeoutMs}ms`);
|
|
804
|
+
}
|
|
805
|
+
const actualSize = Buffer.byteLength(content, "utf8");
|
|
806
|
+
if (actualSize > this.maxReadBytes) {
|
|
807
|
+
throw new FsError(
|
|
808
|
+
"TOO_LARGE",
|
|
809
|
+
safe,
|
|
810
|
+
`file is ${actualSize} bytes after read, max read is ${this.maxReadBytes} bytes`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
761
813
|
if (params.line == null && params.limit == null) return { content };
|
|
762
814
|
const lines = content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
763
815
|
const start = (params.line ?? 1) - 1;
|
|
@@ -796,9 +848,21 @@ var FileServer = class {
|
|
|
796
848
|
encoding: "utf8",
|
|
797
849
|
signal: controller.signal
|
|
798
850
|
});
|
|
851
|
+
if (controller.signal.aborted) {
|
|
852
|
+
throw new FsError("TIMEOUT", safe, `writeTextFile timed out after ${this.timeoutMs}ms`);
|
|
853
|
+
}
|
|
799
854
|
await this.assertRealInside(tmp);
|
|
855
|
+
if (controller.signal.aborted) {
|
|
856
|
+
throw new FsError("TIMEOUT", safe, `writeTextFile timed out after ${this.timeoutMs}ms`);
|
|
857
|
+
}
|
|
800
858
|
await this.assertRealInside(path.dirname(safe));
|
|
859
|
+
if (controller.signal.aborted) {
|
|
860
|
+
throw new FsError("TIMEOUT", safe, `writeTextFile timed out after ${this.timeoutMs}ms`);
|
|
861
|
+
}
|
|
801
862
|
await this.operations.rename(tmp, safe);
|
|
863
|
+
if (controller.signal.aborted) {
|
|
864
|
+
throw new FsError("TIMEOUT", safe, `writeTextFile timed out after ${this.timeoutMs}ms`);
|
|
865
|
+
}
|
|
802
866
|
} catch (err) {
|
|
803
867
|
if (err instanceof FsError) {
|
|
804
868
|
await this.operations.unlink(tmp).catch(() => void 0);
|
|
@@ -897,7 +961,7 @@ function safeRealpathSync(p) {
|
|
|
897
961
|
|
|
898
962
|
// src/client/acp-session-callbacks.ts
|
|
899
963
|
var DEFAULT_PERMISSION_TIMEOUT_MS = 6e4;
|
|
900
|
-
async function handleAcpPermissionRequest(msg, permissionPolicy, sender,
|
|
964
|
+
async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbackOptions2 = {}) {
|
|
901
965
|
const id = msg.id;
|
|
902
966
|
if (id === void 0) return;
|
|
903
967
|
const params = msg.params;
|
|
@@ -914,7 +978,7 @@ async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbac
|
|
|
914
978
|
toolCall,
|
|
915
979
|
options: permissionOptions
|
|
916
980
|
},
|
|
917
|
-
|
|
981
|
+
callbackOptions2
|
|
918
982
|
);
|
|
919
983
|
await sender.sendResult(id, { outcome });
|
|
920
984
|
} catch (err) {
|
|
@@ -926,7 +990,7 @@ async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbac
|
|
|
926
990
|
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
927
991
|
}
|
|
928
992
|
}
|
|
929
|
-
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender,
|
|
993
|
+
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, callbackOptions2 = {}) {
|
|
930
994
|
const id = msg.id;
|
|
931
995
|
if (id === void 0) return;
|
|
932
996
|
const params = msg.params;
|
|
@@ -943,7 +1007,7 @@ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, cal
|
|
|
943
1007
|
kind: "edit",
|
|
944
1008
|
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
945
1009
|
},
|
|
946
|
-
|
|
1010
|
+
callbackOptions2
|
|
947
1011
|
);
|
|
948
1012
|
if (authorization !== "allowed") {
|
|
949
1013
|
const isCancelled = authorization === "cancelled";
|
|
@@ -978,7 +1042,7 @@ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, cal
|
|
|
978
1042
|
await sender.sendErrorResponse(id, code, message);
|
|
979
1043
|
}
|
|
980
1044
|
}
|
|
981
|
-
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender,
|
|
1045
|
+
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender, callbackOptions2 = {}) {
|
|
982
1046
|
const id = msg.id;
|
|
983
1047
|
if (id === void 0) return;
|
|
984
1048
|
const params = msg.params ?? {};
|
|
@@ -998,7 +1062,7 @@ async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, s
|
|
|
998
1062
|
sessionId: params.sessionId
|
|
999
1063
|
}
|
|
1000
1064
|
},
|
|
1001
|
-
|
|
1065
|
+
callbackOptions2
|
|
1002
1066
|
);
|
|
1003
1067
|
if (authorization !== "allowed") {
|
|
1004
1068
|
const isCancelled = authorization === "cancelled";
|
|
@@ -1059,7 +1123,7 @@ async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, s
|
|
|
1059
1123
|
await sender.sendErrorResponse(id, -32603, message);
|
|
1060
1124
|
}
|
|
1061
1125
|
}
|
|
1062
|
-
async function authorizeAcpCallback(permissionPolicy, partial,
|
|
1126
|
+
async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions2) {
|
|
1063
1127
|
try {
|
|
1064
1128
|
const outcome = await runPermissionWithDeadline(
|
|
1065
1129
|
permissionPolicy,
|
|
@@ -1077,25 +1141,25 @@ async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions)
|
|
|
1077
1141
|
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1078
1142
|
]
|
|
1079
1143
|
},
|
|
1080
|
-
|
|
1144
|
+
callbackOptions2
|
|
1081
1145
|
);
|
|
1082
1146
|
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always" ? "allowed" : "denied";
|
|
1083
1147
|
} catch (err) {
|
|
1084
1148
|
return isAbortLikeError(err) ? "cancelled" : "denied";
|
|
1085
1149
|
}
|
|
1086
1150
|
}
|
|
1087
|
-
async function runPermissionWithDeadline(permissionPolicy, call,
|
|
1088
|
-
const timeoutMs = resolvePermissionDeadline(
|
|
1151
|
+
async function runPermissionWithDeadline(permissionPolicy, call, callbackOptions2) {
|
|
1152
|
+
const timeoutMs = resolvePermissionDeadline(callbackOptions2.permissionTimeoutMs);
|
|
1089
1153
|
const controller = new AbortController();
|
|
1090
1154
|
const abort = () => controller.abort();
|
|
1091
1155
|
const timer = timeoutMs === null ? null : setTimeout(abort, timeoutMs);
|
|
1092
1156
|
let removeAbort;
|
|
1093
|
-
if (
|
|
1094
|
-
if (
|
|
1157
|
+
if (callbackOptions2.signal) {
|
|
1158
|
+
if (callbackOptions2.signal.aborted) {
|
|
1095
1159
|
abort();
|
|
1096
1160
|
} else {
|
|
1097
|
-
|
|
1098
|
-
removeAbort = () =>
|
|
1161
|
+
callbackOptions2.signal.addEventListener("abort", abort, { once: true });
|
|
1162
|
+
removeAbort = () => callbackOptions2.signal?.removeEventListener("abort", abort);
|
|
1099
1163
|
}
|
|
1100
1164
|
}
|
|
1101
1165
|
try {
|
|
@@ -1131,54 +1195,21 @@ function resolvePermissionDeadline(value) {
|
|
|
1131
1195
|
return DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
1132
1196
|
}
|
|
1133
1197
|
|
|
1134
|
-
// src/client/acp-session-content.ts
|
|
1135
|
-
function textContent(text) {
|
|
1136
|
-
return { type: "text", text };
|
|
1137
|
-
}
|
|
1138
|
-
function imageContent(mimeType, data) {
|
|
1139
|
-
return { type: "image", mimeType, data };
|
|
1140
|
-
}
|
|
1141
|
-
function audioContent(mimeType, data) {
|
|
1142
|
-
return { type: "audio", mimeType, data };
|
|
1143
|
-
}
|
|
1144
|
-
function extractText(block) {
|
|
1145
|
-
if (typeof block !== "object" || block === null) return "";
|
|
1146
|
-
const b = block;
|
|
1147
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1148
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1149
|
-
return b.resource.text;
|
|
1150
|
-
}
|
|
1151
|
-
return "";
|
|
1152
|
-
}
|
|
1153
|
-
function isRecord(v) {
|
|
1154
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1155
|
-
}
|
|
1156
|
-
function emptyRunResult(stopReason) {
|
|
1157
|
-
return {
|
|
1158
|
-
text: "",
|
|
1159
|
-
stopReason,
|
|
1160
|
-
hasText: false,
|
|
1161
|
-
toolCalls: [],
|
|
1162
|
-
diffs: [],
|
|
1163
|
-
thoughts: ""
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
1198
|
// src/client/acp-session-updates.ts
|
|
1168
1199
|
function createSessionScratch() {
|
|
1169
1200
|
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
1170
1201
|
}
|
|
1171
|
-
function handleAcpSessionUpdate(msg, scratch,
|
|
1202
|
+
function handleAcpSessionUpdate(msg, scratch, emitProgress2) {
|
|
1172
1203
|
const update = msg.params?.update;
|
|
1173
1204
|
if (typeof update !== "object" || update === null) return;
|
|
1174
1205
|
const u = update;
|
|
1175
|
-
|
|
1206
|
+
emitProgress2({ type: "raw", update: u });
|
|
1176
1207
|
switch (u.sessionUpdate) {
|
|
1177
1208
|
case "agent_message_chunk": {
|
|
1178
1209
|
const text = extractText(u.content);
|
|
1179
1210
|
if (text) {
|
|
1180
1211
|
scratch.text += text;
|
|
1181
|
-
|
|
1212
|
+
emitProgress2({ type: "message", text });
|
|
1182
1213
|
}
|
|
1183
1214
|
return;
|
|
1184
1215
|
}
|
|
@@ -1187,29 +1218,29 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
|
1187
1218
|
const text = extractText(u.content);
|
|
1188
1219
|
if (text) {
|
|
1189
1220
|
scratch.thoughts += text;
|
|
1190
|
-
|
|
1221
|
+
emitProgress2({ type: "thought", text });
|
|
1191
1222
|
}
|
|
1192
1223
|
return;
|
|
1193
1224
|
}
|
|
1194
1225
|
case "tool_call":
|
|
1195
1226
|
case "tool_call_update":
|
|
1196
|
-
captureToolCall(u, u.sessionUpdate === "tool_call", scratch,
|
|
1227
|
+
captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress2);
|
|
1197
1228
|
return;
|
|
1198
1229
|
case "plan":
|
|
1199
1230
|
if (Array.isArray(u.entries)) {
|
|
1200
1231
|
scratch.plan = u.entries;
|
|
1201
|
-
|
|
1232
|
+
emitProgress2({ type: "plan", entries: u.entries });
|
|
1202
1233
|
}
|
|
1203
1234
|
return;
|
|
1204
1235
|
case "usage_update":
|
|
1205
|
-
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1236
|
+
if (typeof u.used === "number" && Number.isFinite(u.used) && u.used >= 0 && typeof u.size === "number" && Number.isFinite(u.size) && u.size >= 0) {
|
|
1206
1237
|
const usage = {
|
|
1207
1238
|
used: u.used,
|
|
1208
1239
|
size: u.size,
|
|
1209
1240
|
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1210
1241
|
};
|
|
1211
1242
|
scratch.usage = usage;
|
|
1212
|
-
|
|
1243
|
+
emitProgress2({ type: "usage", usage });
|
|
1213
1244
|
}
|
|
1214
1245
|
return;
|
|
1215
1246
|
case "available_commands_update":
|
|
@@ -1224,7 +1255,7 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
|
1224
1255
|
return;
|
|
1225
1256
|
}
|
|
1226
1257
|
}
|
|
1227
|
-
function captureToolCall(u, isNew, scratch,
|
|
1258
|
+
function captureToolCall(u, isNew, scratch, emitProgress2) {
|
|
1228
1259
|
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
1229
1260
|
if (!toolCallId) return;
|
|
1230
1261
|
const prev = scratch.toolCalls.get(toolCallId);
|
|
@@ -1246,16 +1277,119 @@ function captureToolCall(u, isNew, scratch, emitProgress) {
|
|
|
1246
1277
|
newText: c.newText
|
|
1247
1278
|
};
|
|
1248
1279
|
scratch.diffs.push(diff);
|
|
1249
|
-
|
|
1280
|
+
emitProgress2({ type: "diff", diff });
|
|
1250
1281
|
}
|
|
1251
1282
|
}
|
|
1252
1283
|
}
|
|
1253
|
-
|
|
1284
|
+
emitProgress2({
|
|
1254
1285
|
type: isNew ? "tool_call" : "tool_call_update",
|
|
1255
1286
|
toolCall: record
|
|
1256
1287
|
});
|
|
1257
1288
|
}
|
|
1258
1289
|
|
|
1290
|
+
// src/client/acp-session-messages.ts
|
|
1291
|
+
function responseSender() {
|
|
1292
|
+
return {
|
|
1293
|
+
sendResult: (id, result) => this.sendResult(id, result),
|
|
1294
|
+
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
function callbackOptions() {
|
|
1298
|
+
const promptSignal = this.promptCallbackAbort?.signal;
|
|
1299
|
+
const signal = promptSignal ? AbortSignal.any([this.callbackAbort.signal, promptSignal]) : this.callbackAbort.signal;
|
|
1300
|
+
return { signal, permissionTimeoutMs: Number.POSITIVE_INFINITY };
|
|
1301
|
+
}
|
|
1302
|
+
function handleMessage(msg) {
|
|
1303
|
+
if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
|
|
1304
|
+
const pending = this.pending.get(msg.id);
|
|
1305
|
+
if (!pending) return;
|
|
1306
|
+
clearTimeout(pending.timeoutHandle);
|
|
1307
|
+
this.pending.delete(msg.id);
|
|
1308
|
+
if (msg.error !== void 0) {
|
|
1309
|
+
const method = pending.method;
|
|
1310
|
+
const kind = method === "session/new" ? "session_create_failed" : method === "authenticate" ? "auth_failed" : "protocol_error";
|
|
1311
|
+
pending.reject(
|
|
1312
|
+
new ACPSessionError(kind, msg.error.message ?? "unknown JSON-RPC error", msg.error)
|
|
1313
|
+
);
|
|
1314
|
+
} else {
|
|
1315
|
+
pending.resolve(msg.result);
|
|
1316
|
+
}
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (msg.method === "session/update") {
|
|
1320
|
+
const updateSessionId = msg.params?.sessionId;
|
|
1321
|
+
if (typeof updateSessionId === "string" && updateSessionId !== this.sessionId) return;
|
|
1322
|
+
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (msg.method === "session/request_permission") {
|
|
1326
|
+
handleAcpPermissionRequest(
|
|
1327
|
+
msg,
|
|
1328
|
+
this.permissionPolicy,
|
|
1329
|
+
this.responseSender(),
|
|
1330
|
+
this.callbackOptions()
|
|
1331
|
+
).catch(() => {
|
|
1332
|
+
});
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
1336
|
+
handleAcpFsRequest(
|
|
1337
|
+
msg,
|
|
1338
|
+
this.fileServer,
|
|
1339
|
+
this.permissionPolicy,
|
|
1340
|
+
this.responseSender(),
|
|
1341
|
+
this.callbackOptions()
|
|
1342
|
+
).catch(() => {
|
|
1343
|
+
});
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (msg.method?.startsWith("terminal/")) {
|
|
1347
|
+
handleAcpTerminalRequest(
|
|
1348
|
+
msg,
|
|
1349
|
+
this.terminalServer,
|
|
1350
|
+
this.permissionPolicy,
|
|
1351
|
+
this.responseSender(),
|
|
1352
|
+
this.callbackOptions()
|
|
1353
|
+
).catch(() => {
|
|
1354
|
+
});
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
if (isBestEffortAckMethod(msg.method)) {
|
|
1358
|
+
if (msg.id !== void 0) {
|
|
1359
|
+
this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(() => {
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (msg.method === "$/cancel_request") {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (msg.method) {
|
|
1368
|
+
if (msg.id !== void 0) {
|
|
1369
|
+
void this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(
|
|
1370
|
+
() => {
|
|
1371
|
+
}
|
|
1372
|
+
);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
console.warn(
|
|
1376
|
+
JSON.stringify({
|
|
1377
|
+
level: "warn",
|
|
1378
|
+
event: "acp_session.unhandled_method",
|
|
1379
|
+
method: msg.method,
|
|
1380
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1381
|
+
})
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function emitProgress(event) {
|
|
1386
|
+
if (!this.progressHandler) return;
|
|
1387
|
+
try {
|
|
1388
|
+
this.progressHandler(event);
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1259
1393
|
// src/client/permission.ts
|
|
1260
1394
|
function pickAllow(options) {
|
|
1261
1395
|
const ranked = [...options].sort((a, b) => {
|
|
@@ -1294,6 +1428,7 @@ function makePermissionPolicy(decide) {
|
|
|
1294
1428
|
return async (req) => {
|
|
1295
1429
|
if (req.signal.aborted) return { outcome: "cancelled" };
|
|
1296
1430
|
const allow = await decide(req);
|
|
1431
|
+
if (req.signal.aborted) return { outcome: "cancelled" };
|
|
1297
1432
|
return allow ? pickAllow(req.options) : pickReject(req.options);
|
|
1298
1433
|
};
|
|
1299
1434
|
}
|
|
@@ -1346,8 +1481,8 @@ var TerminalServer = class {
|
|
|
1346
1481
|
constructor(opts) {
|
|
1347
1482
|
this.projectRoot = path2.resolve(opts.projectRoot);
|
|
1348
1483
|
this.commandTimeoutMs = opts.commandTimeoutMs ?? 5 * 6e4;
|
|
1349
|
-
this.outputByteLimit = opts.outputByteLimit
|
|
1350
|
-
this.maxOutputByteLimit = opts.maxOutputByteLimit
|
|
1484
|
+
this.outputByteLimit = this.clampFiniteInt(opts.outputByteLimit, 1024 * 1024);
|
|
1485
|
+
this.maxOutputByteLimit = this.clampFiniteInt(opts.maxOutputByteLimit, 16 * 1024 * 1024);
|
|
1351
1486
|
this.instanceId = `term_srv_${randomBytes2(4).toString("hex")}`;
|
|
1352
1487
|
this.maxTerminals = this.clampFiniteInt(opts.maxTerminals, 32);
|
|
1353
1488
|
this.abortSignal = opts.signal;
|
|
@@ -1408,7 +1543,7 @@ var TerminalServer = class {
|
|
|
1408
1543
|
clearTimeout(state.timeoutHandle);
|
|
1409
1544
|
state.timeoutHandle = null;
|
|
1410
1545
|
}
|
|
1411
|
-
const exitStatus = {
|
|
1546
|
+
const exitStatus = state.exitStatus ?? {
|
|
1412
1547
|
exitCode: typeof code === "number" ? code : null,
|
|
1413
1548
|
signal: typeof signalName === "string" ? signalName : null
|
|
1414
1549
|
};
|
|
@@ -1608,7 +1743,7 @@ var TerminalServer = class {
|
|
|
1608
1743
|
* unbounded memory growth.
|
|
1609
1744
|
*/
|
|
1610
1745
|
clampFiniteInt(value, defaultValue) {
|
|
1611
|
-
if (value === void 0 || !Number.isFinite(value) || value < 1) {
|
|
1746
|
+
if (value === void 0 || !Number.isFinite(value) || value < 1 || !Number.isSafeInteger(Math.trunc(value))) {
|
|
1612
1747
|
return defaultValue;
|
|
1613
1748
|
}
|
|
1614
1749
|
return Math.trunc(value);
|
|
@@ -1739,7 +1874,7 @@ var WebSocketClientTransport = class {
|
|
|
1739
1874
|
)
|
|
1740
1875
|
);
|
|
1741
1876
|
}
|
|
1742
|
-
const timeoutMs = this.opts.handshakeTimeoutMs
|
|
1877
|
+
const timeoutMs = finitePositiveLimit(this.opts.handshakeTimeoutMs, 3e4);
|
|
1743
1878
|
return new Promise((resolve3, reject) => {
|
|
1744
1879
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
1745
1880
|
ws.binaryType = "arraybuffer";
|
|
@@ -1885,7 +2020,9 @@ var WebSocketClientTransport = class {
|
|
|
1885
2020
|
}
|
|
1886
2021
|
};
|
|
1887
2022
|
function finitePositiveLimit(value, fallback) {
|
|
1888
|
-
|
|
2023
|
+
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
2024
|
+
const normalized = Math.floor(value);
|
|
2025
|
+
return Number.isSafeInteger(normalized) && normalized >= 1 ? normalized : fallback;
|
|
1889
2026
|
}
|
|
1890
2027
|
|
|
1891
2028
|
// src/client/acp-session.ts
|
|
@@ -2097,7 +2234,9 @@ var ACPSession = class _ACPSession {
|
|
|
2097
2234
|
opContext() {
|
|
2098
2235
|
const self = this;
|
|
2099
2236
|
return {
|
|
2100
|
-
closed
|
|
2237
|
+
get closed() {
|
|
2238
|
+
return self.closed;
|
|
2239
|
+
},
|
|
2101
2240
|
get sessionId() {
|
|
2102
2241
|
return self.sessionId;
|
|
2103
2242
|
},
|
|
@@ -2199,10 +2338,6 @@ var ACPSession = class _ACPSession {
|
|
|
2199
2338
|
createPromise,
|
|
2200
2339
|
new Promise((_, reject) => {
|
|
2201
2340
|
rejectCreate = reject;
|
|
2202
|
-
if (signal.aborted) {
|
|
2203
|
-
reject(new ACPSessionError("aborted", "prompt was aborted by the parent"));
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
2341
|
signal.addEventListener("abort", onCreateAbort, { once: true });
|
|
2207
2342
|
})
|
|
2208
2343
|
]);
|
|
@@ -2220,12 +2355,6 @@ var ACPSession = class _ACPSession {
|
|
|
2220
2355
|
}
|
|
2221
2356
|
signal.removeEventListener("abort", onCreateAbort);
|
|
2222
2357
|
rejectCreate = void 0;
|
|
2223
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
2224
|
-
signal.removeEventListener("abort", onAbort);
|
|
2225
|
-
this.promptCallbackAbort?.abort();
|
|
2226
|
-
this.promptCallbackAbort = null;
|
|
2227
|
-
throw new ACPSessionError("protocol_error", "session/new returned no session id");
|
|
2228
|
-
}
|
|
2229
2358
|
this.sessionId = sessionId;
|
|
2230
2359
|
}
|
|
2231
2360
|
if (signal.aborted) {
|
|
@@ -2408,103 +2537,16 @@ var ACPSession = class _ACPSession {
|
|
|
2408
2537
|
});
|
|
2409
2538
|
}
|
|
2410
2539
|
responseSender() {
|
|
2411
|
-
return
|
|
2412
|
-
sendResult: (id, result) => this.sendResult(id, result),
|
|
2413
|
-
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
2414
|
-
};
|
|
2540
|
+
return responseSender.call(this.acpSessionMessagesHost());
|
|
2415
2541
|
}
|
|
2416
2542
|
callbackOptions() {
|
|
2417
|
-
|
|
2418
|
-
const signal = promptSignal ? AbortSignal.any([this.callbackAbort.signal, promptSignal]) : this.callbackAbort.signal;
|
|
2419
|
-
return { signal, permissionTimeoutMs: Number.POSITIVE_INFINITY };
|
|
2543
|
+
return callbackOptions.call(this.acpSessionMessagesHost());
|
|
2420
2544
|
}
|
|
2421
2545
|
handleMessage(msg) {
|
|
2422
|
-
|
|
2423
|
-
const pending = this.pending.get(msg.id);
|
|
2424
|
-
if (!pending) return;
|
|
2425
|
-
clearTimeout(pending.timeoutHandle);
|
|
2426
|
-
this.pending.delete(msg.id);
|
|
2427
|
-
if (msg.error !== void 0) {
|
|
2428
|
-
const method = pending.method;
|
|
2429
|
-
const kind = method === "session/new" ? "session_create_failed" : method === "authenticate" ? "auth_failed" : "protocol_error";
|
|
2430
|
-
pending.reject(
|
|
2431
|
-
new ACPSessionError(kind, msg.error.message ?? "unknown JSON-RPC error", msg.error)
|
|
2432
|
-
);
|
|
2433
|
-
} else {
|
|
2434
|
-
pending.resolve(msg.result);
|
|
2435
|
-
}
|
|
2436
|
-
return;
|
|
2437
|
-
}
|
|
2438
|
-
if (msg.method === "session/update") {
|
|
2439
|
-
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
if (msg.method === "session/request_permission") {
|
|
2443
|
-
handleAcpPermissionRequest(
|
|
2444
|
-
msg,
|
|
2445
|
-
this.permissionPolicy,
|
|
2446
|
-
this.responseSender(),
|
|
2447
|
-
this.callbackOptions()
|
|
2448
|
-
).catch(() => {
|
|
2449
|
-
});
|
|
2450
|
-
return;
|
|
2451
|
-
}
|
|
2452
|
-
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
2453
|
-
handleAcpFsRequest(
|
|
2454
|
-
msg,
|
|
2455
|
-
this.fileServer,
|
|
2456
|
-
this.permissionPolicy,
|
|
2457
|
-
this.responseSender(),
|
|
2458
|
-
this.callbackOptions()
|
|
2459
|
-
).catch(() => {
|
|
2460
|
-
});
|
|
2461
|
-
return;
|
|
2462
|
-
}
|
|
2463
|
-
if (msg.method?.startsWith("terminal/")) {
|
|
2464
|
-
handleAcpTerminalRequest(
|
|
2465
|
-
msg,
|
|
2466
|
-
this.terminalServer,
|
|
2467
|
-
this.permissionPolicy,
|
|
2468
|
-
this.responseSender(),
|
|
2469
|
-
this.callbackOptions()
|
|
2470
|
-
).catch(() => {
|
|
2471
|
-
});
|
|
2472
|
-
return;
|
|
2473
|
-
}
|
|
2474
|
-
if (isBestEffortAckMethod(msg.method)) {
|
|
2475
|
-
if (msg.id !== void 0) {
|
|
2476
|
-
this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(() => {
|
|
2477
|
-
});
|
|
2478
|
-
}
|
|
2479
|
-
return;
|
|
2480
|
-
}
|
|
2481
|
-
if (msg.method === "$/cancel_request") {
|
|
2482
|
-
return;
|
|
2483
|
-
}
|
|
2484
|
-
if (msg.method) {
|
|
2485
|
-
if (msg.id !== void 0) {
|
|
2486
|
-
void this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(
|
|
2487
|
-
() => {
|
|
2488
|
-
}
|
|
2489
|
-
);
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
console.warn(
|
|
2493
|
-
JSON.stringify({
|
|
2494
|
-
level: "warn",
|
|
2495
|
-
event: "acp_session.unhandled_method",
|
|
2496
|
-
method: msg.method,
|
|
2497
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2498
|
-
})
|
|
2499
|
-
);
|
|
2500
|
-
}
|
|
2546
|
+
handleMessage.call(this.acpSessionMessagesHost(), msg);
|
|
2501
2547
|
}
|
|
2502
2548
|
emitProgress(event) {
|
|
2503
|
-
|
|
2504
|
-
try {
|
|
2505
|
-
this.progressHandler(event);
|
|
2506
|
-
} catch {
|
|
2507
|
-
}
|
|
2549
|
+
emitProgress.call(this.acpSessionMessagesHost(), event);
|
|
2508
2550
|
}
|
|
2509
2551
|
progressHandler = null;
|
|
2510
2552
|
scratch = createSessionScratch();
|
|
@@ -2534,6 +2576,23 @@ var ACPSession = class _ACPSession {
|
|
|
2534
2576
|
opts: this.opts
|
|
2535
2577
|
};
|
|
2536
2578
|
}
|
|
2579
|
+
acpSessionMessagesHost() {
|
|
2580
|
+
void this.sendResult;
|
|
2581
|
+
void this.sendErrorResponse;
|
|
2582
|
+
void this.promptCallbackAbort;
|
|
2583
|
+
void this.callbackAbort;
|
|
2584
|
+
void this.pending;
|
|
2585
|
+
void this.sessionId;
|
|
2586
|
+
void this.scratch;
|
|
2587
|
+
void this.emitProgress;
|
|
2588
|
+
void this.permissionPolicy;
|
|
2589
|
+
void this.responseSender;
|
|
2590
|
+
void this.callbackOptions;
|
|
2591
|
+
void this.fileServer;
|
|
2592
|
+
void this.terminalServer;
|
|
2593
|
+
void this.progressHandler;
|
|
2594
|
+
return this;
|
|
2595
|
+
}
|
|
2537
2596
|
};
|
|
2538
2597
|
|
|
2539
2598
|
// src/integration/acp-subagent-runner.ts
|
|
@@ -2751,18 +2810,30 @@ var ToolTranslator = class {
|
|
|
2751
2810
|
* response arrives.
|
|
2752
2811
|
*/
|
|
2753
2812
|
async callTool(transport, name, args, callId = crypto.randomUUID()) {
|
|
2754
|
-
|
|
2755
|
-
jsonrpc: "2.0",
|
|
2756
|
-
method: "tools/call",
|
|
2757
|
-
id: callId,
|
|
2758
|
-
params: { name, arguments: args }
|
|
2759
|
-
});
|
|
2813
|
+
if (this.pending.has(callId)) throw new Error(`Tool call id ${callId} is already pending`);
|
|
2760
2814
|
return new Promise((resolve3, reject) => {
|
|
2761
2815
|
const timeout = setTimeout(() => {
|
|
2762
2816
|
this.pending.delete(callId);
|
|
2763
2817
|
reject(new Error(`Tool call ${name} timed out after ${this.opts.totalTimeoutMs}ms`));
|
|
2764
2818
|
}, this.opts.totalTimeoutMs);
|
|
2765
|
-
|
|
2819
|
+
const pending = { resolve: resolve3, reject, timeout };
|
|
2820
|
+
this.pending.set(callId, pending);
|
|
2821
|
+
const failSend = (err) => {
|
|
2822
|
+
if (this.pending.get(callId) !== pending) return;
|
|
2823
|
+
clearTimeout(timeout);
|
|
2824
|
+
this.pending.delete(callId);
|
|
2825
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
2826
|
+
};
|
|
2827
|
+
try {
|
|
2828
|
+
transport.send({
|
|
2829
|
+
jsonrpc: "2.0",
|
|
2830
|
+
method: "tools/call",
|
|
2831
|
+
id: callId,
|
|
2832
|
+
params: { name, arguments: args }
|
|
2833
|
+
}).catch(failSend);
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
failSend(err);
|
|
2836
|
+
}
|
|
2766
2837
|
});
|
|
2767
2838
|
}
|
|
2768
2839
|
cancelAll() {
|