@wrongstack/acp 0.295.0 → 0.296.2
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-handler.d.ts +9 -0
- package/dist/agent/protocol-handler.d.ts.map +1 -1
- package/dist/agent/server-agent-turn.d.ts +83 -1
- package/dist/agent/server-agent-turn.d.ts.map +1 -1
- package/dist/agent/session-store.d.ts.map +1 -1
- package/dist/agent/stdio-transport.d.ts +18 -0
- package/dist/agent/stdio-transport.d.ts.map +1 -1
- package/dist/agent/wrongstack-acp-agent.d.ts +35 -0
- package/dist/agent/wrongstack-acp-agent.d.ts.map +1 -1
- package/dist/agent.js +30 -23
- package/dist/agent.js.map +3 -3
- package/dist/client/acp-message-routing.d.ts +2 -0
- package/dist/client/acp-message-routing.d.ts.map +1 -0
- package/dist/client/acp-session-callbacks.d.ts +12 -0
- package/dist/client/acp-session-callbacks.d.ts.map +1 -0
- package/dist/client/acp-session-content.d.ts +24 -0
- package/dist/client/acp-session-content.d.ts.map +1 -0
- package/dist/client/acp-session-errors.d.ts +13 -0
- package/dist/client/acp-session-errors.d.ts.map +1 -0
- package/dist/client/acp-session-types.d.ts +81 -0
- package/dist/client/acp-session-types.d.ts.map +1 -0
- package/dist/client/acp-session-updates.d.ts +18 -0
- package/dist/client/acp-session-updates.d.ts.map +1 -0
- package/dist/client/acp-session.d.ts +6 -171
- package/dist/client/acp-session.d.ts.map +1 -1
- package/dist/client/file-server.d.ts +19 -0
- package/dist/client/file-server.d.ts.map +1 -1
- package/dist/client/terminal-server.d.ts.map +1 -1
- package/dist/client/trust-boundary-permission.d.ts +16 -1
- package/dist/client/trust-boundary-permission.d.ts.map +1 -1
- package/dist/client/websocket-transport.d.ts.map +1 -1
- package/dist/client.js +358 -348
- package/dist/client.js.map +3 -3
- package/dist/index.js +409 -395
- package/dist/index.js.map +4 -4
- package/dist/integration/acp-bench.d.ts +32 -0
- package/dist/integration/acp-bench.d.ts.map +1 -1
- package/dist/integration/acp-subagent-runner.d.ts.map +1 -1
- package/dist/integration/ensemble-runner.d.ts +22 -1
- package/dist/integration/ensemble-runner.d.ts.map +1 -1
- package/dist/registry/acp-registry-fetch.d.ts +1 -1
- package/dist/registry/acp-registry-fetch.d.ts.map +1 -1
- package/dist/registry/ensemble-registry.d.ts +22 -0
- package/dist/registry/ensemble-registry.d.ts.map +1 -1
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/wrongstack-acp-agent.js +26 -20
- package/dist/wrongstack-acp-agent.js.map +2 -2
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/agent/stdio-transport.ts
|
|
2
2
|
import { expectDefined, writeErr } from "@wrongstack/core/utils";
|
|
3
|
+
import { treeKill } from "@wrongstack/core/utils/tree-kill";
|
|
3
4
|
|
|
4
5
|
// src/win32-cmd.ts
|
|
5
6
|
var WIN32_CMD_META = /[&|<>"\r\n\0]/;
|
|
@@ -29,7 +30,7 @@ function quoteWin32CmdArg(arg) {
|
|
|
29
30
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
30
31
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
31
32
|
function positiveLimit(value, fallback) {
|
|
32
|
-
return Number.isFinite(value) &&
|
|
33
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
33
34
|
}
|
|
34
35
|
var ClientTransport = class {
|
|
35
36
|
child = null;
|
|
@@ -47,10 +48,7 @@ var ClientTransport = class {
|
|
|
47
48
|
...options
|
|
48
49
|
};
|
|
49
50
|
this.maxFrameChars = positiveLimit(options.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
50
|
-
this.maxQueuedMessages = positiveLimit(
|
|
51
|
-
options.maxQueuedMessages,
|
|
52
|
-
DEFAULT_MAX_QUEUED_MESSAGES
|
|
53
|
-
);
|
|
51
|
+
this.maxQueuedMessages = positiveLimit(options.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
54
52
|
}
|
|
55
53
|
async start() {
|
|
56
54
|
if (this.child) return;
|
|
@@ -69,13 +67,13 @@ var ClientTransport = class {
|
|
|
69
67
|
const spawnCwd = isPkgLauncher ? os.homedir() : this.opts.cwd;
|
|
70
68
|
try {
|
|
71
69
|
const childArgs = this.opts.args ?? [];
|
|
72
|
-
const
|
|
73
|
-
this.child = spawn2(
|
|
70
|
+
const invocation = spawnInvocation(this.opts.command, childArgs, process.platform);
|
|
71
|
+
this.child = spawn2(invocation.command, invocation.args, {
|
|
74
72
|
env: { ...buildChildEnv2(), ...this.opts.env },
|
|
75
73
|
cwd: spawnCwd,
|
|
76
74
|
stdio: ["pipe", "pipe", "pipe"],
|
|
77
75
|
windowsHide: true,
|
|
78
|
-
...
|
|
76
|
+
...verbatimOptions(invocation)
|
|
79
77
|
});
|
|
80
78
|
} catch (err) {
|
|
81
79
|
clearTimeout(timeout);
|
|
@@ -119,6 +117,9 @@ var ClientTransport = class {
|
|
|
119
117
|
};
|
|
120
118
|
const waitForMarker = (chunk) => {
|
|
121
119
|
this.buffer += chunk;
|
|
120
|
+
if (this.buffer.length > this.maxFrameChars) {
|
|
121
|
+
this.buffer = this.buffer.slice(-this.maxFrameChars);
|
|
122
|
+
}
|
|
122
123
|
const idx = this.buffer.indexOf("[wstack-acp]\n");
|
|
123
124
|
if (idx !== -1) {
|
|
124
125
|
this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
|
|
@@ -140,7 +141,8 @@ var ClientTransport = class {
|
|
|
140
141
|
});
|
|
141
142
|
}
|
|
142
143
|
read() {
|
|
143
|
-
if (this.messageQueue.length > 0)
|
|
144
|
+
if (this.messageQueue.length > 0)
|
|
145
|
+
return Promise.resolve(expectDefined(this.messageQueue.shift()));
|
|
144
146
|
if (this.closed) return Promise.resolve(null);
|
|
145
147
|
return new Promise((resolve3) => {
|
|
146
148
|
this.resolveRead = resolve3;
|
|
@@ -159,10 +161,7 @@ var ClientTransport = class {
|
|
|
159
161
|
this.handlers.clear();
|
|
160
162
|
const child = this.child;
|
|
161
163
|
if (!child) return;
|
|
162
|
-
|
|
163
|
-
child.kill();
|
|
164
|
-
} catch {
|
|
165
|
-
}
|
|
164
|
+
treeKill(child);
|
|
166
165
|
this.child = null;
|
|
167
166
|
}
|
|
168
167
|
onChildData(chunk) {
|
|
@@ -226,15 +225,63 @@ var ClientTransport = class {
|
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
227
|
};
|
|
228
|
+
function spawnInvocation(command, args, platform) {
|
|
229
|
+
if (platform !== "win32") return { command, args };
|
|
230
|
+
return buildWin32CmdShimInvocation(command, args);
|
|
231
|
+
}
|
|
232
|
+
function verbatimOptions(invocation) {
|
|
233
|
+
return invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {};
|
|
234
|
+
}
|
|
229
235
|
|
|
230
236
|
// src/types/acp-v1.ts
|
|
231
237
|
var ACP_PROTOCOL_VERSION = 1;
|
|
232
238
|
|
|
239
|
+
// src/client/acp-session-content.ts
|
|
240
|
+
function textContent(text) {
|
|
241
|
+
return { type: "text", text };
|
|
242
|
+
}
|
|
243
|
+
function imageContent(mimeType, data) {
|
|
244
|
+
return { type: "image", mimeType, data };
|
|
245
|
+
}
|
|
246
|
+
function audioContent(mimeType, data) {
|
|
247
|
+
return { type: "audio", mimeType, data };
|
|
248
|
+
}
|
|
249
|
+
function extractText(block) {
|
|
250
|
+
if (typeof block !== "object" || block === null) return "";
|
|
251
|
+
const b = block;
|
|
252
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
253
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
254
|
+
return b.resource.text;
|
|
255
|
+
}
|
|
256
|
+
return "";
|
|
257
|
+
}
|
|
258
|
+
function isRecord(v) {
|
|
259
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
260
|
+
}
|
|
261
|
+
function emptyRunResult(stopReason) {
|
|
262
|
+
return {
|
|
263
|
+
text: "",
|
|
264
|
+
stopReason,
|
|
265
|
+
hasText: false,
|
|
266
|
+
toolCalls: [],
|
|
267
|
+
diffs: [],
|
|
268
|
+
thoughts: ""
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
233
272
|
// src/client/file-server.ts
|
|
234
273
|
import { randomBytes } from "node:crypto";
|
|
235
274
|
import { realpathSync } from "node:fs";
|
|
236
275
|
import * as fsp from "node:fs/promises";
|
|
237
276
|
import * as path from "node:path";
|
|
277
|
+
var DEFAULT_FILE_OPERATIONS = {
|
|
278
|
+
stat: fsp.stat,
|
|
279
|
+
readFile: fsp.readFile,
|
|
280
|
+
writeFile: fsp.writeFile,
|
|
281
|
+
realpath: fsp.realpath,
|
|
282
|
+
rename: fsp.rename,
|
|
283
|
+
unlink: fsp.unlink
|
|
284
|
+
};
|
|
238
285
|
var DEFAULT_MAX_READ_BYTES = 5 * 1024 * 1024;
|
|
239
286
|
var DEFAULT_MAX_WRITE_BYTES = 5 * 1024 * 1024;
|
|
240
287
|
var FsError = class extends Error {
|
|
@@ -253,12 +300,14 @@ var FileServer = class {
|
|
|
253
300
|
timeoutMs;
|
|
254
301
|
maxReadBytes;
|
|
255
302
|
maxWriteBytes;
|
|
303
|
+
operations;
|
|
256
304
|
constructor(opts) {
|
|
257
305
|
this.root = path.resolve(opts.projectRoot);
|
|
258
306
|
this.realRoot = safeRealpathSync(this.root);
|
|
259
307
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
260
308
|
this.maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
261
309
|
this.maxWriteBytes = opts.maxWriteBytes ?? DEFAULT_MAX_WRITE_BYTES;
|
|
310
|
+
this.operations = opts.operations ?? DEFAULT_FILE_OPERATIONS;
|
|
262
311
|
}
|
|
263
312
|
/** Read a text file. Returns the content as a string. */
|
|
264
313
|
async readTextFile(params) {
|
|
@@ -266,7 +315,7 @@ var FileServer = class {
|
|
|
266
315
|
const controller = new AbortController();
|
|
267
316
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
268
317
|
try {
|
|
269
|
-
const stat2 = await
|
|
318
|
+
const stat2 = await this.operations.stat(safe).catch((err) => {
|
|
270
319
|
throw mapFsError(err, safe);
|
|
271
320
|
});
|
|
272
321
|
if (stat2.size > this.maxReadBytes) {
|
|
@@ -276,7 +325,7 @@ var FileServer = class {
|
|
|
276
325
|
`file is ${stat2.size} bytes, max read is ${this.maxReadBytes} bytes`
|
|
277
326
|
);
|
|
278
327
|
}
|
|
279
|
-
const content = await
|
|
328
|
+
const content = await this.operations.readFile(safe, {
|
|
280
329
|
encoding: "utf8",
|
|
281
330
|
signal: controller.signal
|
|
282
331
|
});
|
|
@@ -306,20 +355,20 @@ var FileServer = class {
|
|
|
306
355
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
307
356
|
const tmp = `${safe}.${randomBytes(6).toString("hex")}.tmp`;
|
|
308
357
|
try {
|
|
309
|
-
await
|
|
358
|
+
await this.operations.writeFile(tmp, params.content, {
|
|
310
359
|
encoding: "utf8",
|
|
311
360
|
signal: controller.signal
|
|
312
361
|
});
|
|
313
362
|
await this.assertRealInside(tmp);
|
|
314
363
|
await this.assertRealInside(path.dirname(safe));
|
|
315
|
-
await
|
|
364
|
+
await this.operations.rename(tmp, safe);
|
|
316
365
|
} catch (err) {
|
|
317
366
|
if (err instanceof FsError) {
|
|
318
|
-
await
|
|
367
|
+
await this.operations.unlink(tmp).catch(() => void 0);
|
|
319
368
|
throw err;
|
|
320
369
|
}
|
|
321
370
|
try {
|
|
322
|
-
await
|
|
371
|
+
await this.operations.unlink(tmp);
|
|
323
372
|
} catch {
|
|
324
373
|
}
|
|
325
374
|
if (controller.signal.aborted) {
|
|
@@ -363,12 +412,14 @@ var FileServer = class {
|
|
|
363
412
|
for (; ; ) {
|
|
364
413
|
let real;
|
|
365
414
|
try {
|
|
366
|
-
real = await
|
|
415
|
+
real = await this.operations.realpath(probe);
|
|
367
416
|
} catch (err) {
|
|
368
417
|
const code = err.code;
|
|
369
418
|
if (code === "ENOENT") {
|
|
370
419
|
const parent = path.dirname(probe);
|
|
371
|
-
if (parent === probe)
|
|
420
|
+
if (parent === probe) {
|
|
421
|
+
throw new FsError("ENOENT", resolvedPath, `no existing ancestor: ${resolvedPath}`);
|
|
422
|
+
}
|
|
372
423
|
probe = parent;
|
|
373
424
|
continue;
|
|
374
425
|
}
|
|
@@ -466,7 +517,6 @@ var TerminalServer = class {
|
|
|
466
517
|
this.outputByteLimit = opts.outputByteLimit ?? 1024 * 1024;
|
|
467
518
|
this.maxOutputByteLimit = opts.maxOutputByteLimit ?? 16 * 1024 * 1024;
|
|
468
519
|
this.maxTerminals = this.clampFiniteInt(opts.maxTerminals, 32);
|
|
469
|
-
if (this.maxTerminals < 1) throw new RangeError("maxTerminals must be at least 1");
|
|
470
520
|
this.abortSignal = opts.signal;
|
|
471
521
|
if (opts.signal) {
|
|
472
522
|
opts.signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
@@ -797,7 +847,6 @@ var WebSocketClientTransport = class {
|
|
|
797
847
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
798
848
|
this.ws = ws;
|
|
799
849
|
const timer = setTimeout(() => {
|
|
800
|
-
if (settled) return;
|
|
801
850
|
settled = true;
|
|
802
851
|
try {
|
|
803
852
|
ws.close();
|
|
@@ -835,7 +884,7 @@ var WebSocketClientTransport = class {
|
|
|
835
884
|
}
|
|
836
885
|
try {
|
|
837
886
|
const serialized = JSON.stringify(msg);
|
|
838
|
-
const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount
|
|
887
|
+
const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount : 0;
|
|
839
888
|
if (buffered + Buffer.byteLength(serialized, "utf8") > this.maxBufferedBytes) {
|
|
840
889
|
this.stop();
|
|
841
890
|
return Promise.reject(new Error("WebSocket transport send buffer limit exceeded"));
|
|
@@ -892,10 +941,10 @@ var WebSocketClientTransport = class {
|
|
|
892
941
|
}
|
|
893
942
|
};
|
|
894
943
|
function finitePositiveLimit(value, fallback) {
|
|
895
|
-
return Number.isFinite(value) &&
|
|
944
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
896
945
|
}
|
|
897
946
|
|
|
898
|
-
// src/client/acp-session.ts
|
|
947
|
+
// src/client/acp-session-errors.ts
|
|
899
948
|
var ACPSessionError = class extends Error {
|
|
900
949
|
kind;
|
|
901
950
|
cause;
|
|
@@ -909,6 +958,264 @@ var ACPSessionError = class extends Error {
|
|
|
909
958
|
function isJsonRpcError(v) {
|
|
910
959
|
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
911
960
|
}
|
|
961
|
+
|
|
962
|
+
// src/client/acp-session-updates.ts
|
|
963
|
+
function createSessionScratch() {
|
|
964
|
+
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
965
|
+
}
|
|
966
|
+
function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
967
|
+
const update = msg.params?.update;
|
|
968
|
+
if (typeof update !== "object" || update === null) return;
|
|
969
|
+
const u = update;
|
|
970
|
+
emitProgress({ type: "raw", update: u });
|
|
971
|
+
switch (u.sessionUpdate) {
|
|
972
|
+
case "agent_message_chunk": {
|
|
973
|
+
const text = extractText(u.content);
|
|
974
|
+
if (text) {
|
|
975
|
+
scratch.text += text;
|
|
976
|
+
emitProgress({ type: "message", text });
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
case "thought_chunk": {
|
|
981
|
+
const text = extractText(u.content);
|
|
982
|
+
if (text) {
|
|
983
|
+
scratch.thoughts += text;
|
|
984
|
+
emitProgress({ type: "thought", text });
|
|
985
|
+
}
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
case "tool_call":
|
|
989
|
+
case "tool_call_update":
|
|
990
|
+
captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress);
|
|
991
|
+
return;
|
|
992
|
+
case "plan":
|
|
993
|
+
if (Array.isArray(u.entries)) {
|
|
994
|
+
scratch.plan = u.entries;
|
|
995
|
+
emitProgress({ type: "plan", entries: u.entries });
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
case "usage_update":
|
|
999
|
+
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1000
|
+
const usage = {
|
|
1001
|
+
used: u.used,
|
|
1002
|
+
size: u.size,
|
|
1003
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1004
|
+
};
|
|
1005
|
+
scratch.usage = usage;
|
|
1006
|
+
emitProgress({ type: "usage", usage });
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
case "available_commands_update":
|
|
1010
|
+
case "current_mode_update":
|
|
1011
|
+
case "config_option_update":
|
|
1012
|
+
case "session_info_update":
|
|
1013
|
+
case "user_message_chunk":
|
|
1014
|
+
case "next_edit_suggestions":
|
|
1015
|
+
case "elicitation":
|
|
1016
|
+
return;
|
|
1017
|
+
default:
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function captureToolCall(u, isNew, scratch, emitProgress) {
|
|
1022
|
+
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
1023
|
+
if (!toolCallId) return;
|
|
1024
|
+
const prev = scratch.toolCalls.get(toolCallId);
|
|
1025
|
+
const record = {
|
|
1026
|
+
toolCallId,
|
|
1027
|
+
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
1028
|
+
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
1029
|
+
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
1030
|
+
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
1031
|
+
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
1032
|
+
};
|
|
1033
|
+
scratch.toolCalls.set(toolCallId, record);
|
|
1034
|
+
if (Array.isArray(u.content)) {
|
|
1035
|
+
for (const c of u.content) {
|
|
1036
|
+
if (c && typeof c === "object" && c.type === "diff") {
|
|
1037
|
+
const diff = {
|
|
1038
|
+
path: c.path,
|
|
1039
|
+
oldText: c.oldText,
|
|
1040
|
+
newText: c.newText
|
|
1041
|
+
};
|
|
1042
|
+
scratch.diffs.push(diff);
|
|
1043
|
+
emitProgress({ type: "diff", diff });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
emitProgress({
|
|
1048
|
+
type: isNew ? "tool_call" : "tool_call_update",
|
|
1049
|
+
toolCall: record
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/client/acp-session-callbacks.ts
|
|
1054
|
+
async function handleAcpPermissionRequest(msg, permissionPolicy, sender) {
|
|
1055
|
+
const id = msg.id;
|
|
1056
|
+
if (id === void 0) return;
|
|
1057
|
+
const params = msg.params;
|
|
1058
|
+
const toolCall = params?.toolCall;
|
|
1059
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
1060
|
+
if (!toolCall) {
|
|
1061
|
+
await sender.sendErrorResponse(id, -32602, "toolCall is required");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const policyAbort = new AbortController();
|
|
1065
|
+
try {
|
|
1066
|
+
const outcome = await permissionPolicy({
|
|
1067
|
+
toolCall,
|
|
1068
|
+
options,
|
|
1069
|
+
signal: policyAbort.signal
|
|
1070
|
+
});
|
|
1071
|
+
await sender.sendResult(id, { outcome });
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1074
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender) {
|
|
1078
|
+
const id = msg.id;
|
|
1079
|
+
if (id === void 0) return;
|
|
1080
|
+
const params = msg.params;
|
|
1081
|
+
if (!params?.path) {
|
|
1082
|
+
await sender.sendErrorResponse(id, -32602, "path is required");
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (msg.method === "fs/write_text_file") {
|
|
1086
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
1087
|
+
toolCallId: `acp-fs-write-${id}`,
|
|
1088
|
+
title: `Write file: ${params.path}`,
|
|
1089
|
+
kind: "edit",
|
|
1090
|
+
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
1091
|
+
});
|
|
1092
|
+
if (!allowed) {
|
|
1093
|
+
await sender.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
if (msg.method === "fs/read_text_file") {
|
|
1099
|
+
const result = await fileServer.readTextFile({
|
|
1100
|
+
sessionId: params.sessionId ?? "",
|
|
1101
|
+
path: params.path
|
|
1102
|
+
});
|
|
1103
|
+
await sender.sendResult(id, result);
|
|
1104
|
+
} else {
|
|
1105
|
+
await fileServer.writeTextFile({
|
|
1106
|
+
sessionId: params.sessionId ?? "",
|
|
1107
|
+
path: params.path,
|
|
1108
|
+
content: params.content ?? ""
|
|
1109
|
+
});
|
|
1110
|
+
await sender.sendResult(id, {});
|
|
1111
|
+
}
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
const code = err instanceof FsError ? -32602 : -32603;
|
|
1114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1115
|
+
await sender.sendErrorResponse(id, code, message);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender) {
|
|
1119
|
+
const id = msg.id;
|
|
1120
|
+
if (id === void 0) return;
|
|
1121
|
+
const params = msg.params ?? {};
|
|
1122
|
+
try {
|
|
1123
|
+
switch (msg.method) {
|
|
1124
|
+
case "terminal/create": {
|
|
1125
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
1126
|
+
toolCallId: `acp-terminal-create-${id}`,
|
|
1127
|
+
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
1128
|
+
kind: "execute",
|
|
1129
|
+
rawInput: {
|
|
1130
|
+
command: params.command,
|
|
1131
|
+
args: params.args,
|
|
1132
|
+
cwd: params.cwd,
|
|
1133
|
+
sessionId: params.sessionId
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
if (!allowed) {
|
|
1137
|
+
await sender.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const createOpts = {
|
|
1141
|
+
sessionId: String(params.sessionId ?? ""),
|
|
1142
|
+
command: String(params.command ?? ""),
|
|
1143
|
+
args: Array.isArray(params.args) ? params.args : []
|
|
1144
|
+
};
|
|
1145
|
+
if (Array.isArray(params.env)) {
|
|
1146
|
+
createOpts.env = params.env;
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof params.cwd === "string") {
|
|
1149
|
+
createOpts.cwd = params.cwd;
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof params.outputByteLimit === "number") {
|
|
1152
|
+
createOpts.outputByteLimit = params.outputByteLimit;
|
|
1153
|
+
}
|
|
1154
|
+
const result = terminalServer.create(createOpts);
|
|
1155
|
+
await sender.sendResult(id, result);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
case "terminal/output": {
|
|
1159
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1160
|
+
const out = terminalServer.output(terminalId);
|
|
1161
|
+
await sender.sendResult(id, out);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
case "terminal/wait_for_exit": {
|
|
1165
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1166
|
+
const exit = await terminalServer.waitForExit(terminalId);
|
|
1167
|
+
await sender.sendResult(id, exit);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
case "terminal/kill": {
|
|
1171
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1172
|
+
terminalServer.kill(terminalId);
|
|
1173
|
+
await sender.sendResult(id, {});
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
case "terminal/release": {
|
|
1177
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1178
|
+
terminalServer.release(terminalId);
|
|
1179
|
+
await sender.sendResult(id, {});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
default:
|
|
1183
|
+
await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
1184
|
+
}
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1187
|
+
await sender.sendErrorResponse(id, -32603, message);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async function authorizeAcpCallback(permissionPolicy, partial) {
|
|
1191
|
+
try {
|
|
1192
|
+
const outcome = await permissionPolicy({
|
|
1193
|
+
toolCall: {
|
|
1194
|
+
sessionUpdate: "tool_call_update",
|
|
1195
|
+
toolCallId: partial.toolCallId,
|
|
1196
|
+
title: partial.title,
|
|
1197
|
+
kind: partial.kind,
|
|
1198
|
+
status: "pending",
|
|
1199
|
+
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
1200
|
+
},
|
|
1201
|
+
options: [
|
|
1202
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
1203
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1204
|
+
],
|
|
1205
|
+
signal: new AbortController().signal
|
|
1206
|
+
});
|
|
1207
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
1208
|
+
} catch {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/client/acp-message-routing.ts
|
|
1214
|
+
function isBestEffortAckMethod(method) {
|
|
1215
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/client/acp-session.ts
|
|
912
1219
|
var ACPSession = class _ACPSession {
|
|
913
1220
|
transport;
|
|
914
1221
|
fileServer;
|
|
@@ -1602,6 +1909,12 @@ var ACPSession = class _ACPSession {
|
|
|
1602
1909
|
error: { code, message }
|
|
1603
1910
|
});
|
|
1604
1911
|
}
|
|
1912
|
+
responseSender() {
|
|
1913
|
+
return {
|
|
1914
|
+
sendResult: (id, result) => this.sendResult(id, result),
|
|
1915
|
+
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1605
1918
|
handleMessage(msg) {
|
|
1606
1919
|
if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
|
|
1607
1920
|
const pending = this.pending.get(msg.id);
|
|
@@ -1616,29 +1929,32 @@ var ACPSession = class _ACPSession {
|
|
|
1616
1929
|
return;
|
|
1617
1930
|
}
|
|
1618
1931
|
if (msg.method === "session/update") {
|
|
1619
|
-
this.
|
|
1932
|
+
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
1620
1933
|
return;
|
|
1621
1934
|
}
|
|
1622
1935
|
if (msg.method === "session/request_permission") {
|
|
1623
|
-
void this.
|
|
1936
|
+
void handleAcpPermissionRequest(msg, this.permissionPolicy, this.responseSender());
|
|
1624
1937
|
return;
|
|
1625
1938
|
}
|
|
1626
1939
|
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
1627
|
-
void
|
|
1940
|
+
void handleAcpFsRequest(
|
|
1941
|
+
msg,
|
|
1942
|
+
this.fileServer,
|
|
1943
|
+
this.permissionPolicy,
|
|
1944
|
+
this.responseSender()
|
|
1945
|
+
);
|
|
1628
1946
|
return;
|
|
1629
1947
|
}
|
|
1630
1948
|
if (msg.method?.startsWith("terminal/")) {
|
|
1631
|
-
void
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1949
|
+
void handleAcpTerminalRequest(
|
|
1950
|
+
msg,
|
|
1951
|
+
this.terminalServer,
|
|
1952
|
+
this.permissionPolicy,
|
|
1953
|
+
this.responseSender()
|
|
1954
|
+
);
|
|
1639
1955
|
return;
|
|
1640
1956
|
}
|
|
1641
|
-
if (msg.method
|
|
1957
|
+
if (isBestEffortAckMethod(msg.method)) {
|
|
1642
1958
|
if (msg.id !== void 0) {
|
|
1643
1959
|
this.sendResult(msg.id, {}).catch(() => {
|
|
1644
1960
|
});
|
|
@@ -1659,98 +1975,6 @@ var ACPSession = class _ACPSession {
|
|
|
1659
1975
|
);
|
|
1660
1976
|
}
|
|
1661
1977
|
}
|
|
1662
|
-
handleUpdate(msg) {
|
|
1663
|
-
const update = msg.params?.update;
|
|
1664
|
-
if (typeof update !== "object" || update === null) return;
|
|
1665
|
-
const u = update;
|
|
1666
|
-
this.emitProgress({ type: "raw", update: u });
|
|
1667
|
-
switch (u.sessionUpdate) {
|
|
1668
|
-
case "agent_message_chunk": {
|
|
1669
|
-
const text = extractText(u.content);
|
|
1670
|
-
if (text) {
|
|
1671
|
-
this.scratch.text += text;
|
|
1672
|
-
this.emitProgress({ type: "message", text });
|
|
1673
|
-
}
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
case "thought_chunk": {
|
|
1677
|
-
const text = extractText(u.content);
|
|
1678
|
-
if (text) {
|
|
1679
|
-
this.scratch.thoughts += text;
|
|
1680
|
-
this.emitProgress({ type: "thought", text });
|
|
1681
|
-
}
|
|
1682
|
-
return;
|
|
1683
|
-
}
|
|
1684
|
-
case "tool_call":
|
|
1685
|
-
case "tool_call_update": {
|
|
1686
|
-
this.captureToolCall(u, u.sessionUpdate === "tool_call");
|
|
1687
|
-
return;
|
|
1688
|
-
}
|
|
1689
|
-
case "plan":
|
|
1690
|
-
if (Array.isArray(u.entries)) {
|
|
1691
|
-
this.scratch.plan = u.entries;
|
|
1692
|
-
this.emitProgress({ type: "plan", entries: u.entries });
|
|
1693
|
-
}
|
|
1694
|
-
return;
|
|
1695
|
-
case "usage_update":
|
|
1696
|
-
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1697
|
-
const usage = {
|
|
1698
|
-
used: u.used,
|
|
1699
|
-
size: u.size,
|
|
1700
|
-
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1701
|
-
};
|
|
1702
|
-
this.scratch.usage = usage;
|
|
1703
|
-
this.emitProgress({ type: "usage", usage });
|
|
1704
|
-
}
|
|
1705
|
-
return;
|
|
1706
|
-
case "available_commands_update":
|
|
1707
|
-
case "current_mode_update":
|
|
1708
|
-
case "config_option_update":
|
|
1709
|
-
case "session_info_update":
|
|
1710
|
-
case "user_message_chunk":
|
|
1711
|
-
case "next_edit_suggestions":
|
|
1712
|
-
case "elicitation":
|
|
1713
|
-
return;
|
|
1714
|
-
default:
|
|
1715
|
-
return;
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
/**
|
|
1719
|
-
* Fold a `tool_call` / `tool_call_update` notification into the scratch
|
|
1720
|
-
* tool-call map (deduped by toolCallId), extract any `diff` content into
|
|
1721
|
-
* the diffs list, and emit live progress.
|
|
1722
|
-
*/
|
|
1723
|
-
captureToolCall(u, isNew) {
|
|
1724
|
-
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
1725
|
-
if (!toolCallId) return;
|
|
1726
|
-
const prev = this.scratch.toolCalls.get(toolCallId);
|
|
1727
|
-
const record = {
|
|
1728
|
-
toolCallId,
|
|
1729
|
-
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
1730
|
-
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
1731
|
-
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
1732
|
-
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
1733
|
-
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
1734
|
-
};
|
|
1735
|
-
this.scratch.toolCalls.set(toolCallId, record);
|
|
1736
|
-
if (Array.isArray(u.content)) {
|
|
1737
|
-
for (const c of u.content) {
|
|
1738
|
-
if (c && typeof c === "object" && c.type === "diff") {
|
|
1739
|
-
const diff = {
|
|
1740
|
-
path: c.path,
|
|
1741
|
-
oldText: c.oldText,
|
|
1742
|
-
newText: c.newText
|
|
1743
|
-
};
|
|
1744
|
-
this.scratch.diffs.push(diff);
|
|
1745
|
-
this.emitProgress({ type: "diff", diff });
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
this.emitProgress({
|
|
1750
|
-
type: isNew ? "tool_call" : "tool_call_update",
|
|
1751
|
-
toolCall: record
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
1978
|
emitProgress(event) {
|
|
1755
1979
|
if (!this.progressHandler) return;
|
|
1756
1980
|
try {
|
|
@@ -1761,217 +1985,11 @@ var ACPSession = class _ACPSession {
|
|
|
1761
1985
|
/** Live progress handler installed for the duration of a `prompt()` turn. */
|
|
1762
1986
|
progressHandler = null;
|
|
1763
1987
|
// Per-prompt scratch state
|
|
1764
|
-
scratch =
|
|
1988
|
+
scratch = createSessionScratch();
|
|
1765
1989
|
resetScratch() {
|
|
1766
|
-
this.scratch =
|
|
1767
|
-
}
|
|
1768
|
-
async handlePermissionRequest(msg) {
|
|
1769
|
-
const id = msg.id;
|
|
1770
|
-
if (id === void 0) return;
|
|
1771
|
-
const params = msg.params;
|
|
1772
|
-
const toolCall = params?.toolCall;
|
|
1773
|
-
const options = Array.isArray(params?.options) ? params.options : [];
|
|
1774
|
-
if (!toolCall) {
|
|
1775
|
-
await this.sendErrorResponse(id, -32602, "toolCall is required");
|
|
1776
|
-
return;
|
|
1777
|
-
}
|
|
1778
|
-
const policyAbort = new AbortController();
|
|
1779
|
-
try {
|
|
1780
|
-
const outcome = await this.permissionPolicy({
|
|
1781
|
-
toolCall,
|
|
1782
|
-
options,
|
|
1783
|
-
signal: policyAbort.signal
|
|
1784
|
-
});
|
|
1785
|
-
await this.sendResult(id, { outcome });
|
|
1786
|
-
} catch (err) {
|
|
1787
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1788
|
-
await this.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* Enforce authorization at privileged callback sinks (fs/write,
|
|
1793
|
-
* terminal/create). Unlike `handlePermissionRequest` which responds to
|
|
1794
|
-
* agent-initiated `session/request_permission` messages, this method is
|
|
1795
|
-
* called by the handler BEFORE dispatching to FileServer/TerminalServer,
|
|
1796
|
-
* closing the gap where the agent simply skips the voluntary permission
|
|
1797
|
-
* request and sends the privileged callback directly.
|
|
1798
|
-
*
|
|
1799
|
-
* Uses the session's permission policy. The default
|
|
1800
|
-
* (`readOnlyPermissionPolicy`) auto-approves only side-effect-free tool
|
|
1801
|
-
* calls (read/search/fetch/think) and rejects everything else — this is
|
|
1802
|
-
* the safe-by-default posture. For trusted local agents (CLI `acp spawn`,
|
|
1803
|
-
* Director fan-out), inject `defaultPermissionPolicy` to grant
|
|
1804
|
-
* write/execute access.
|
|
1805
|
-
*
|
|
1806
|
-
* Returns true if the callback is authorized, false if denied.
|
|
1807
|
-
*/
|
|
1808
|
-
async authorizeCallback(partial) {
|
|
1809
|
-
try {
|
|
1810
|
-
const outcome = await this.permissionPolicy({
|
|
1811
|
-
toolCall: {
|
|
1812
|
-
sessionUpdate: "tool_call_update",
|
|
1813
|
-
toolCallId: partial.toolCallId,
|
|
1814
|
-
title: partial.title,
|
|
1815
|
-
kind: partial.kind,
|
|
1816
|
-
status: "pending",
|
|
1817
|
-
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
1818
|
-
},
|
|
1819
|
-
options: [
|
|
1820
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
1821
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1822
|
-
],
|
|
1823
|
-
signal: new AbortController().signal
|
|
1824
|
-
});
|
|
1825
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
1826
|
-
} catch {
|
|
1827
|
-
return false;
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
async handleFsRequest(msg) {
|
|
1831
|
-
const id = msg.id;
|
|
1832
|
-
if (id === void 0) return;
|
|
1833
|
-
const params = msg.params;
|
|
1834
|
-
if (!params?.path) {
|
|
1835
|
-
await this.sendErrorResponse(id, -32602, "path is required");
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
if (msg.method === "fs/write_text_file") {
|
|
1839
|
-
const allowed = await this.authorizeCallback({
|
|
1840
|
-
toolCallId: `acp-fs-write-${id}`,
|
|
1841
|
-
title: `Write file: ${params.path}`,
|
|
1842
|
-
kind: "edit",
|
|
1843
|
-
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
1844
|
-
});
|
|
1845
|
-
if (!allowed) {
|
|
1846
|
-
await this.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
try {
|
|
1851
|
-
if (msg.method === "fs/read_text_file") {
|
|
1852
|
-
const result = await this.fileServer.readTextFile({
|
|
1853
|
-
sessionId: params.sessionId ?? "",
|
|
1854
|
-
path: params.path
|
|
1855
|
-
});
|
|
1856
|
-
await this.sendResult(id, result);
|
|
1857
|
-
} else {
|
|
1858
|
-
await this.fileServer.writeTextFile({
|
|
1859
|
-
sessionId: params.sessionId ?? "",
|
|
1860
|
-
path: params.path,
|
|
1861
|
-
content: params.content ?? ""
|
|
1862
|
-
});
|
|
1863
|
-
await this.sendResult(id, {});
|
|
1864
|
-
}
|
|
1865
|
-
} catch (err) {
|
|
1866
|
-
const code = err instanceof FsError ? -32602 : -32603;
|
|
1867
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1868
|
-
await this.sendErrorResponse(id, code, message);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
async handleTerminalRequest(msg) {
|
|
1872
|
-
const id = msg.id;
|
|
1873
|
-
if (id === void 0) return;
|
|
1874
|
-
const params = msg.params ?? {};
|
|
1875
|
-
try {
|
|
1876
|
-
switch (msg.method) {
|
|
1877
|
-
case "terminal/create": {
|
|
1878
|
-
const allowed = await this.authorizeCallback({
|
|
1879
|
-
toolCallId: `acp-terminal-create-${id}`,
|
|
1880
|
-
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
1881
|
-
kind: "execute",
|
|
1882
|
-
rawInput: {
|
|
1883
|
-
command: params.command,
|
|
1884
|
-
args: params.args,
|
|
1885
|
-
cwd: params.cwd,
|
|
1886
|
-
sessionId: params.sessionId
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
|
-
if (!allowed) {
|
|
1890
|
-
await this.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
const createOpts = {
|
|
1894
|
-
sessionId: String(params.sessionId ?? ""),
|
|
1895
|
-
command: String(params.command ?? ""),
|
|
1896
|
-
args: Array.isArray(params.args) ? params.args : []
|
|
1897
|
-
};
|
|
1898
|
-
if (Array.isArray(params.env)) {
|
|
1899
|
-
createOpts.env = params.env;
|
|
1900
|
-
}
|
|
1901
|
-
if (typeof params.cwd === "string") {
|
|
1902
|
-
createOpts.cwd = params.cwd;
|
|
1903
|
-
}
|
|
1904
|
-
if (typeof params.outputByteLimit === "number") {
|
|
1905
|
-
createOpts.outputByteLimit = params.outputByteLimit;
|
|
1906
|
-
}
|
|
1907
|
-
const result = this.terminalServer.create(createOpts);
|
|
1908
|
-
await this.sendResult(id, result);
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
case "terminal/output": {
|
|
1912
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1913
|
-
const out = this.terminalServer.output(terminalId);
|
|
1914
|
-
await this.sendResult(id, out);
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
case "terminal/wait_for_exit": {
|
|
1918
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1919
|
-
const exit = await this.terminalServer.waitForExit(terminalId);
|
|
1920
|
-
await this.sendResult(id, exit);
|
|
1921
|
-
return;
|
|
1922
|
-
}
|
|
1923
|
-
case "terminal/kill": {
|
|
1924
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1925
|
-
this.terminalServer.kill(terminalId);
|
|
1926
|
-
await this.sendResult(id, {});
|
|
1927
|
-
return;
|
|
1928
|
-
}
|
|
1929
|
-
case "terminal/release": {
|
|
1930
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1931
|
-
this.terminalServer.release(terminalId);
|
|
1932
|
-
await this.sendResult(id, {});
|
|
1933
|
-
return;
|
|
1934
|
-
}
|
|
1935
|
-
default:
|
|
1936
|
-
await this.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
1937
|
-
}
|
|
1938
|
-
} catch (err) {
|
|
1939
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1940
|
-
await this.sendErrorResponse(id, -32603, message);
|
|
1941
|
-
}
|
|
1990
|
+
this.scratch = createSessionScratch();
|
|
1942
1991
|
}
|
|
1943
1992
|
};
|
|
1944
|
-
function textContent(text) {
|
|
1945
|
-
return { type: "text", text };
|
|
1946
|
-
}
|
|
1947
|
-
function imageContent(mimeType, data) {
|
|
1948
|
-
return { type: "image", mimeType, data };
|
|
1949
|
-
}
|
|
1950
|
-
function audioContent(mimeType, data) {
|
|
1951
|
-
return { type: "audio", mimeType, data };
|
|
1952
|
-
}
|
|
1953
|
-
function extractText(block) {
|
|
1954
|
-
if (typeof block !== "object" || block === null) return "";
|
|
1955
|
-
const b = block;
|
|
1956
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1957
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1958
|
-
return b.resource.text;
|
|
1959
|
-
}
|
|
1960
|
-
return "";
|
|
1961
|
-
}
|
|
1962
|
-
function isRecord(v) {
|
|
1963
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1964
|
-
}
|
|
1965
|
-
function emptyRunResult(stopReason) {
|
|
1966
|
-
return {
|
|
1967
|
-
text: "",
|
|
1968
|
-
stopReason,
|
|
1969
|
-
hasText: false,
|
|
1970
|
-
toolCalls: [],
|
|
1971
|
-
diffs: [],
|
|
1972
|
-
thoughts: ""
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
1993
|
|
|
1976
1994
|
// src/integration/acp-subagent-runner.ts
|
|
1977
1995
|
async function makeACPSubagentRunner(options) {
|
|
@@ -2100,16 +2118,8 @@ function mapACPKind(acpKind) {
|
|
|
2100
2118
|
}
|
|
2101
2119
|
}
|
|
2102
2120
|
function isRetryable(kind) {
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
case "provider_rate_limit":
|
|
2106
|
-
case "provider_timeout":
|
|
2107
|
-
case "tool_threw":
|
|
2108
|
-
case "budget_timeout":
|
|
2109
|
-
return true;
|
|
2110
|
-
default:
|
|
2111
|
-
return false;
|
|
2112
|
-
}
|
|
2121
|
+
void kind;
|
|
2122
|
+
return false;
|
|
2113
2123
|
}
|
|
2114
2124
|
|
|
2115
2125
|
// src/client/tool-translator.ts
|