@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/index.js
CHANGED
|
@@ -9,9 +9,9 @@ function assertNeverSessionUpdate(x) {
|
|
|
9
9
|
// src/version.ts
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
var require2 = createRequire(import.meta.url);
|
|
12
|
-
function readPackageVersion() {
|
|
12
|
+
function readPackageVersion(load = () => require2("../package.json")) {
|
|
13
13
|
try {
|
|
14
|
-
const packageJson =
|
|
14
|
+
const packageJson = load();
|
|
15
15
|
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
16
16
|
return packageJson.version;
|
|
17
17
|
}
|
|
@@ -79,7 +79,7 @@ var ACPProtocolHandler = class {
|
|
|
79
79
|
this.replayFor = opts.replayFor;
|
|
80
80
|
this.seedFor = opts.seedFor;
|
|
81
81
|
this.disposeFor = opts.disposeFor;
|
|
82
|
-
this.maxSessions = Number.isFinite(opts.maxSessions) &&
|
|
82
|
+
this.maxSessions = Number.isFinite(opts.maxSessions) && opts.maxSessions > 0 ? Math.floor(opts.maxSessions) : DEFAULT_MAX_SESSIONS;
|
|
83
83
|
this.store = opts.store;
|
|
84
84
|
if (typeof this.transport.onMessage === "function") {
|
|
85
85
|
this.transport.onMessage((m) => this.maybeResolvePending(m));
|
|
@@ -361,7 +361,7 @@ var ACPProtocolHandler = class {
|
|
|
361
361
|
}
|
|
362
362
|
if (existing) {
|
|
363
363
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
364
|
-
const replay =
|
|
364
|
+
const replay = this.replayFor?.(sessionId);
|
|
365
365
|
if (replay) {
|
|
366
366
|
for (const update of replay) {
|
|
367
367
|
await this.sendNotification({ sessionId, update });
|
|
@@ -426,10 +426,8 @@ var ACPProtocolHandler = class {
|
|
|
426
426
|
return false;
|
|
427
427
|
}
|
|
428
428
|
session.abort.abort();
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.disposeSession(sessionId);
|
|
432
|
-
}
|
|
429
|
+
this.sessions.delete(sessionId);
|
|
430
|
+
this.disposeSession(sessionId);
|
|
433
431
|
await this.transport.send(toWire({
|
|
434
432
|
jsonrpc: "2.0",
|
|
435
433
|
id,
|
|
@@ -738,6 +736,7 @@ function errorToJsonRpc(err) {
|
|
|
738
736
|
|
|
739
737
|
// src/agent/stdio-transport.ts
|
|
740
738
|
import { expectDefined, writeErr } from "@wrongstack/core/utils";
|
|
739
|
+
import { treeKill } from "@wrongstack/core/utils/tree-kill";
|
|
741
740
|
|
|
742
741
|
// src/win32-cmd.ts
|
|
743
742
|
var WIN32_CMD_META = /[&|<>"\r\n\0]/;
|
|
@@ -767,7 +766,7 @@ function quoteWin32CmdArg(arg) {
|
|
|
767
766
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
768
767
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
769
768
|
function positiveLimit(value, fallback) {
|
|
770
|
-
return Number.isFinite(value) &&
|
|
769
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
771
770
|
}
|
|
772
771
|
var StdioTransport = class {
|
|
773
772
|
stdin = process.stdin;
|
|
@@ -782,10 +781,7 @@ var StdioTransport = class {
|
|
|
782
781
|
maxQueuedMessages;
|
|
783
782
|
constructor(opts = {}) {
|
|
784
783
|
this.maxFrameChars = positiveLimit(opts.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
785
|
-
this.maxQueuedMessages = positiveLimit(
|
|
786
|
-
opts.maxQueuedMessages,
|
|
787
|
-
DEFAULT_MAX_QUEUED_MESSAGES
|
|
788
|
-
);
|
|
784
|
+
this.maxQueuedMessages = positiveLimit(opts.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
789
785
|
this.stdin.resume();
|
|
790
786
|
this.stdin.setEncoding("utf8");
|
|
791
787
|
this.stdin.on("data", (chunk) => this.onData(chunk));
|
|
@@ -806,7 +802,8 @@ var StdioTransport = class {
|
|
|
806
802
|
this.stdout.write(chunk, "utf8");
|
|
807
803
|
}
|
|
808
804
|
read() {
|
|
809
|
-
if (this.messageQueue.length > 0)
|
|
805
|
+
if (this.messageQueue.length > 0)
|
|
806
|
+
return Promise.resolve(expectDefined(this.messageQueue.shift()));
|
|
810
807
|
if (this.closed) return Promise.resolve(null);
|
|
811
808
|
return new Promise((resolve3) => {
|
|
812
809
|
this.resolveRead = resolve3;
|
|
@@ -908,10 +905,7 @@ var ClientTransport = class {
|
|
|
908
905
|
...options
|
|
909
906
|
};
|
|
910
907
|
this.maxFrameChars = positiveLimit(options.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
911
|
-
this.maxQueuedMessages = positiveLimit(
|
|
912
|
-
options.maxQueuedMessages,
|
|
913
|
-
DEFAULT_MAX_QUEUED_MESSAGES
|
|
914
|
-
);
|
|
908
|
+
this.maxQueuedMessages = positiveLimit(options.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
915
909
|
}
|
|
916
910
|
async start() {
|
|
917
911
|
if (this.child) return;
|
|
@@ -930,13 +924,13 @@ var ClientTransport = class {
|
|
|
930
924
|
const spawnCwd = isPkgLauncher ? os.homedir() : this.opts.cwd;
|
|
931
925
|
try {
|
|
932
926
|
const childArgs = this.opts.args ?? [];
|
|
933
|
-
const
|
|
934
|
-
this.child = spawn3(
|
|
927
|
+
const invocation = spawnInvocation(this.opts.command, childArgs, process.platform);
|
|
928
|
+
this.child = spawn3(invocation.command, invocation.args, {
|
|
935
929
|
env: { ...buildChildEnv2(), ...this.opts.env },
|
|
936
930
|
cwd: spawnCwd,
|
|
937
931
|
stdio: ["pipe", "pipe", "pipe"],
|
|
938
932
|
windowsHide: true,
|
|
939
|
-
...
|
|
933
|
+
...verbatimOptions(invocation)
|
|
940
934
|
});
|
|
941
935
|
} catch (err) {
|
|
942
936
|
clearTimeout(timeout);
|
|
@@ -980,6 +974,9 @@ var ClientTransport = class {
|
|
|
980
974
|
};
|
|
981
975
|
const waitForMarker = (chunk) => {
|
|
982
976
|
this.buffer += chunk;
|
|
977
|
+
if (this.buffer.length > this.maxFrameChars) {
|
|
978
|
+
this.buffer = this.buffer.slice(-this.maxFrameChars);
|
|
979
|
+
}
|
|
983
980
|
const idx = this.buffer.indexOf("[wstack-acp]\n");
|
|
984
981
|
if (idx !== -1) {
|
|
985
982
|
this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
|
|
@@ -1001,7 +998,8 @@ var ClientTransport = class {
|
|
|
1001
998
|
});
|
|
1002
999
|
}
|
|
1003
1000
|
read() {
|
|
1004
|
-
if (this.messageQueue.length > 0)
|
|
1001
|
+
if (this.messageQueue.length > 0)
|
|
1002
|
+
return Promise.resolve(expectDefined(this.messageQueue.shift()));
|
|
1005
1003
|
if (this.closed) return Promise.resolve(null);
|
|
1006
1004
|
return new Promise((resolve3) => {
|
|
1007
1005
|
this.resolveRead = resolve3;
|
|
@@ -1020,10 +1018,7 @@ var ClientTransport = class {
|
|
|
1020
1018
|
this.handlers.clear();
|
|
1021
1019
|
const child = this.child;
|
|
1022
1020
|
if (!child) return;
|
|
1023
|
-
|
|
1024
|
-
child.kill();
|
|
1025
|
-
} catch {
|
|
1026
|
-
}
|
|
1021
|
+
treeKill(child);
|
|
1027
1022
|
this.child = null;
|
|
1028
1023
|
}
|
|
1029
1024
|
onChildData(chunk) {
|
|
@@ -1087,6 +1082,13 @@ var ClientTransport = class {
|
|
|
1087
1082
|
}
|
|
1088
1083
|
}
|
|
1089
1084
|
};
|
|
1085
|
+
function spawnInvocation(command, args, platform) {
|
|
1086
|
+
if (platform !== "win32") return { command, args };
|
|
1087
|
+
return buildWin32CmdShimInvocation(command, args);
|
|
1088
|
+
}
|
|
1089
|
+
function verbatimOptions(invocation) {
|
|
1090
|
+
return invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {};
|
|
1091
|
+
}
|
|
1090
1092
|
|
|
1091
1093
|
// src/agent/tools-registry.ts
|
|
1092
1094
|
var ACPToolsRegistry = class {
|
|
@@ -1203,8 +1205,8 @@ function toolToPriority(tool) {
|
|
|
1203
1205
|
}
|
|
1204
1206
|
|
|
1205
1207
|
// src/agent/wrongstack-acp-agent.ts
|
|
1206
|
-
import { fileURLToPath } from "node:url";
|
|
1207
1208
|
import { createServer } from "node:http";
|
|
1209
|
+
import { fileURLToPath } from "node:url";
|
|
1208
1210
|
import { writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
1209
1211
|
var WrongStackACPServer = class {
|
|
1210
1212
|
transport;
|
|
@@ -1265,10 +1267,9 @@ var WrongStackACPServer = class {
|
|
|
1265
1267
|
let httpChain = Promise.resolve();
|
|
1266
1268
|
this.httpServer = createServer(async (req, res) => {
|
|
1267
1269
|
if (authToken) {
|
|
1268
|
-
const url = new URL(req.url
|
|
1270
|
+
const url = new URL(requestPath(req.url), `http://${host}:${port}`);
|
|
1269
1271
|
const queryToken = url.searchParams.get("token");
|
|
1270
|
-
const
|
|
1271
|
-
const bearerToken = Array.isArray(authHeader) ? authHeader[0]?.replace(/^Bearer\s+/i, "") : authHeader?.replace(/^Bearer\s+/i, "");
|
|
1272
|
+
const bearerToken = headerValue(req.headers.authorization)?.replace(/^Bearer\s+/i, "");
|
|
1272
1273
|
const supplied = queryToken ?? bearerToken ?? "";
|
|
1273
1274
|
if (supplied !== authToken) {
|
|
1274
1275
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -1277,7 +1278,7 @@ var WrongStackACPServer = class {
|
|
|
1277
1278
|
}
|
|
1278
1279
|
}
|
|
1279
1280
|
const selfOrigin = `http://${host}:${port}`;
|
|
1280
|
-
const reqOrigin =
|
|
1281
|
+
const reqOrigin = headerValue(req.headers.origin);
|
|
1281
1282
|
if (reqOrigin && reqOrigin !== selfOrigin) {
|
|
1282
1283
|
res.writeHead(403);
|
|
1283
1284
|
res.end(JSON.stringify({ error: "cross-origin request forbidden" }));
|
|
@@ -1357,6 +1358,8 @@ var WrongStackACPServer = class {
|
|
|
1357
1358
|
try {
|
|
1358
1359
|
await requestPromise;
|
|
1359
1360
|
} catch {
|
|
1361
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1362
|
+
res.end(JSON.stringify({ error: { code: -32603, message: "Internal error" } }));
|
|
1360
1363
|
}
|
|
1361
1364
|
});
|
|
1362
1365
|
return new Promise((resolve3) => {
|
|
@@ -1382,6 +1385,12 @@ var WrongStackACPServer = class {
|
|
|
1382
1385
|
var defaultEchoRunTurn = async (_input, _emit) => {
|
|
1383
1386
|
return { stopReason: "end_turn" };
|
|
1384
1387
|
};
|
|
1388
|
+
function headerValue(value) {
|
|
1389
|
+
return Array.isArray(value) ? value[0] : value;
|
|
1390
|
+
}
|
|
1391
|
+
function requestPath(value) {
|
|
1392
|
+
return value ?? "/";
|
|
1393
|
+
}
|
|
1385
1394
|
async function main() {
|
|
1386
1395
|
const server = new WrongStackACPServer();
|
|
1387
1396
|
await server.start();
|
|
@@ -1395,11 +1404,52 @@ if (isEntrypoint) {
|
|
|
1395
1404
|
});
|
|
1396
1405
|
}
|
|
1397
1406
|
|
|
1407
|
+
// src/client/acp-session-content.ts
|
|
1408
|
+
function textContent(text) {
|
|
1409
|
+
return { type: "text", text };
|
|
1410
|
+
}
|
|
1411
|
+
function imageContent(mimeType, data) {
|
|
1412
|
+
return { type: "image", mimeType, data };
|
|
1413
|
+
}
|
|
1414
|
+
function audioContent(mimeType, data) {
|
|
1415
|
+
return { type: "audio", mimeType, data };
|
|
1416
|
+
}
|
|
1417
|
+
function extractText(block) {
|
|
1418
|
+
if (typeof block !== "object" || block === null) return "";
|
|
1419
|
+
const b = block;
|
|
1420
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1421
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1422
|
+
return b.resource.text;
|
|
1423
|
+
}
|
|
1424
|
+
return "";
|
|
1425
|
+
}
|
|
1426
|
+
function isRecord(v) {
|
|
1427
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1428
|
+
}
|
|
1429
|
+
function emptyRunResult(stopReason) {
|
|
1430
|
+
return {
|
|
1431
|
+
text: "",
|
|
1432
|
+
stopReason,
|
|
1433
|
+
hasText: false,
|
|
1434
|
+
toolCalls: [],
|
|
1435
|
+
diffs: [],
|
|
1436
|
+
thoughts: ""
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1398
1440
|
// src/client/file-server.ts
|
|
1399
1441
|
import { randomBytes } from "node:crypto";
|
|
1400
1442
|
import { realpathSync } from "node:fs";
|
|
1401
1443
|
import * as fsp from "node:fs/promises";
|
|
1402
1444
|
import * as path from "node:path";
|
|
1445
|
+
var DEFAULT_FILE_OPERATIONS = {
|
|
1446
|
+
stat: fsp.stat,
|
|
1447
|
+
readFile: fsp.readFile,
|
|
1448
|
+
writeFile: fsp.writeFile,
|
|
1449
|
+
realpath: fsp.realpath,
|
|
1450
|
+
rename: fsp.rename,
|
|
1451
|
+
unlink: fsp.unlink
|
|
1452
|
+
};
|
|
1403
1453
|
var DEFAULT_MAX_READ_BYTES = 5 * 1024 * 1024;
|
|
1404
1454
|
var DEFAULT_MAX_WRITE_BYTES = 5 * 1024 * 1024;
|
|
1405
1455
|
var FsError = class extends Error {
|
|
@@ -1418,12 +1468,14 @@ var FileServer = class {
|
|
|
1418
1468
|
timeoutMs;
|
|
1419
1469
|
maxReadBytes;
|
|
1420
1470
|
maxWriteBytes;
|
|
1471
|
+
operations;
|
|
1421
1472
|
constructor(opts) {
|
|
1422
1473
|
this.root = path.resolve(opts.projectRoot);
|
|
1423
1474
|
this.realRoot = safeRealpathSync(this.root);
|
|
1424
1475
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
1425
1476
|
this.maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
1426
1477
|
this.maxWriteBytes = opts.maxWriteBytes ?? DEFAULT_MAX_WRITE_BYTES;
|
|
1478
|
+
this.operations = opts.operations ?? DEFAULT_FILE_OPERATIONS;
|
|
1427
1479
|
}
|
|
1428
1480
|
/** Read a text file. Returns the content as a string. */
|
|
1429
1481
|
async readTextFile(params) {
|
|
@@ -1431,7 +1483,7 @@ var FileServer = class {
|
|
|
1431
1483
|
const controller = new AbortController();
|
|
1432
1484
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1433
1485
|
try {
|
|
1434
|
-
const stat2 = await
|
|
1486
|
+
const stat2 = await this.operations.stat(safe).catch((err) => {
|
|
1435
1487
|
throw mapFsError(err, safe);
|
|
1436
1488
|
});
|
|
1437
1489
|
if (stat2.size > this.maxReadBytes) {
|
|
@@ -1441,7 +1493,7 @@ var FileServer = class {
|
|
|
1441
1493
|
`file is ${stat2.size} bytes, max read is ${this.maxReadBytes} bytes`
|
|
1442
1494
|
);
|
|
1443
1495
|
}
|
|
1444
|
-
const content = await
|
|
1496
|
+
const content = await this.operations.readFile(safe, {
|
|
1445
1497
|
encoding: "utf8",
|
|
1446
1498
|
signal: controller.signal
|
|
1447
1499
|
});
|
|
@@ -1471,20 +1523,20 @@ var FileServer = class {
|
|
|
1471
1523
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1472
1524
|
const tmp = `${safe}.${randomBytes(6).toString("hex")}.tmp`;
|
|
1473
1525
|
try {
|
|
1474
|
-
await
|
|
1526
|
+
await this.operations.writeFile(tmp, params.content, {
|
|
1475
1527
|
encoding: "utf8",
|
|
1476
1528
|
signal: controller.signal
|
|
1477
1529
|
});
|
|
1478
1530
|
await this.assertRealInside(tmp);
|
|
1479
1531
|
await this.assertRealInside(path.dirname(safe));
|
|
1480
|
-
await
|
|
1532
|
+
await this.operations.rename(tmp, safe);
|
|
1481
1533
|
} catch (err) {
|
|
1482
1534
|
if (err instanceof FsError) {
|
|
1483
|
-
await
|
|
1535
|
+
await this.operations.unlink(tmp).catch(() => void 0);
|
|
1484
1536
|
throw err;
|
|
1485
1537
|
}
|
|
1486
1538
|
try {
|
|
1487
|
-
await
|
|
1539
|
+
await this.operations.unlink(tmp);
|
|
1488
1540
|
} catch {
|
|
1489
1541
|
}
|
|
1490
1542
|
if (controller.signal.aborted) {
|
|
@@ -1528,12 +1580,14 @@ var FileServer = class {
|
|
|
1528
1580
|
for (; ; ) {
|
|
1529
1581
|
let real;
|
|
1530
1582
|
try {
|
|
1531
|
-
real = await
|
|
1583
|
+
real = await this.operations.realpath(probe);
|
|
1532
1584
|
} catch (err) {
|
|
1533
1585
|
const code = err.code;
|
|
1534
1586
|
if (code === "ENOENT") {
|
|
1535
1587
|
const parent = path.dirname(probe);
|
|
1536
|
-
if (parent === probe)
|
|
1588
|
+
if (parent === probe) {
|
|
1589
|
+
throw new FsError("ENOENT", resolvedPath, `no existing ancestor: ${resolvedPath}`);
|
|
1590
|
+
}
|
|
1537
1591
|
probe = parent;
|
|
1538
1592
|
continue;
|
|
1539
1593
|
}
|
|
@@ -1631,7 +1685,6 @@ var TerminalServer = class {
|
|
|
1631
1685
|
this.outputByteLimit = opts.outputByteLimit ?? 1024 * 1024;
|
|
1632
1686
|
this.maxOutputByteLimit = opts.maxOutputByteLimit ?? 16 * 1024 * 1024;
|
|
1633
1687
|
this.maxTerminals = this.clampFiniteInt(opts.maxTerminals, 32);
|
|
1634
|
-
if (this.maxTerminals < 1) throw new RangeError("maxTerminals must be at least 1");
|
|
1635
1688
|
this.abortSignal = opts.signal;
|
|
1636
1689
|
if (opts.signal) {
|
|
1637
1690
|
opts.signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
@@ -1962,7 +2015,6 @@ var WebSocketClientTransport = class {
|
|
|
1962
2015
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
1963
2016
|
this.ws = ws;
|
|
1964
2017
|
const timer = setTimeout(() => {
|
|
1965
|
-
if (settled) return;
|
|
1966
2018
|
settled = true;
|
|
1967
2019
|
try {
|
|
1968
2020
|
ws.close();
|
|
@@ -2000,7 +2052,7 @@ var WebSocketClientTransport = class {
|
|
|
2000
2052
|
}
|
|
2001
2053
|
try {
|
|
2002
2054
|
const serialized = JSON.stringify(msg);
|
|
2003
|
-
const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount
|
|
2055
|
+
const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount : 0;
|
|
2004
2056
|
if (buffered + Buffer.byteLength(serialized, "utf8") > this.maxBufferedBytes) {
|
|
2005
2057
|
this.stop();
|
|
2006
2058
|
return Promise.reject(new Error("WebSocket transport send buffer limit exceeded"));
|
|
@@ -2057,10 +2109,10 @@ var WebSocketClientTransport = class {
|
|
|
2057
2109
|
}
|
|
2058
2110
|
};
|
|
2059
2111
|
function finitePositiveLimit(value, fallback) {
|
|
2060
|
-
return Number.isFinite(value) &&
|
|
2112
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
2061
2113
|
}
|
|
2062
2114
|
|
|
2063
|
-
// src/client/acp-session.ts
|
|
2115
|
+
// src/client/acp-session-errors.ts
|
|
2064
2116
|
var ACPSessionError = class extends Error {
|
|
2065
2117
|
kind;
|
|
2066
2118
|
cause;
|
|
@@ -2074,6 +2126,264 @@ var ACPSessionError = class extends Error {
|
|
|
2074
2126
|
function isJsonRpcError(v) {
|
|
2075
2127
|
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
2076
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
// src/client/acp-session-updates.ts
|
|
2131
|
+
function createSessionScratch() {
|
|
2132
|
+
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
2133
|
+
}
|
|
2134
|
+
function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
2135
|
+
const update = msg.params?.update;
|
|
2136
|
+
if (typeof update !== "object" || update === null) return;
|
|
2137
|
+
const u = update;
|
|
2138
|
+
emitProgress({ type: "raw", update: u });
|
|
2139
|
+
switch (u.sessionUpdate) {
|
|
2140
|
+
case "agent_message_chunk": {
|
|
2141
|
+
const text = extractText(u.content);
|
|
2142
|
+
if (text) {
|
|
2143
|
+
scratch.text += text;
|
|
2144
|
+
emitProgress({ type: "message", text });
|
|
2145
|
+
}
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
case "thought_chunk": {
|
|
2149
|
+
const text = extractText(u.content);
|
|
2150
|
+
if (text) {
|
|
2151
|
+
scratch.thoughts += text;
|
|
2152
|
+
emitProgress({ type: "thought", text });
|
|
2153
|
+
}
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
case "tool_call":
|
|
2157
|
+
case "tool_call_update":
|
|
2158
|
+
captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress);
|
|
2159
|
+
return;
|
|
2160
|
+
case "plan":
|
|
2161
|
+
if (Array.isArray(u.entries)) {
|
|
2162
|
+
scratch.plan = u.entries;
|
|
2163
|
+
emitProgress({ type: "plan", entries: u.entries });
|
|
2164
|
+
}
|
|
2165
|
+
return;
|
|
2166
|
+
case "usage_update":
|
|
2167
|
+
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
2168
|
+
const usage = {
|
|
2169
|
+
used: u.used,
|
|
2170
|
+
size: u.size,
|
|
2171
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
2172
|
+
};
|
|
2173
|
+
scratch.usage = usage;
|
|
2174
|
+
emitProgress({ type: "usage", usage });
|
|
2175
|
+
}
|
|
2176
|
+
return;
|
|
2177
|
+
case "available_commands_update":
|
|
2178
|
+
case "current_mode_update":
|
|
2179
|
+
case "config_option_update":
|
|
2180
|
+
case "session_info_update":
|
|
2181
|
+
case "user_message_chunk":
|
|
2182
|
+
case "next_edit_suggestions":
|
|
2183
|
+
case "elicitation":
|
|
2184
|
+
return;
|
|
2185
|
+
default:
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function captureToolCall(u, isNew, scratch, emitProgress) {
|
|
2190
|
+
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
2191
|
+
if (!toolCallId) return;
|
|
2192
|
+
const prev = scratch.toolCalls.get(toolCallId);
|
|
2193
|
+
const record = {
|
|
2194
|
+
toolCallId,
|
|
2195
|
+
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
2196
|
+
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
2197
|
+
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
2198
|
+
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
2199
|
+
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
2200
|
+
};
|
|
2201
|
+
scratch.toolCalls.set(toolCallId, record);
|
|
2202
|
+
if (Array.isArray(u.content)) {
|
|
2203
|
+
for (const c of u.content) {
|
|
2204
|
+
if (c && typeof c === "object" && c.type === "diff") {
|
|
2205
|
+
const diff = {
|
|
2206
|
+
path: c.path,
|
|
2207
|
+
oldText: c.oldText,
|
|
2208
|
+
newText: c.newText
|
|
2209
|
+
};
|
|
2210
|
+
scratch.diffs.push(diff);
|
|
2211
|
+
emitProgress({ type: "diff", diff });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
emitProgress({
|
|
2216
|
+
type: isNew ? "tool_call" : "tool_call_update",
|
|
2217
|
+
toolCall: record
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// src/client/acp-session-callbacks.ts
|
|
2222
|
+
async function handleAcpPermissionRequest(msg, permissionPolicy, sender) {
|
|
2223
|
+
const id = msg.id;
|
|
2224
|
+
if (id === void 0) return;
|
|
2225
|
+
const params = msg.params;
|
|
2226
|
+
const toolCall = params?.toolCall;
|
|
2227
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
2228
|
+
if (!toolCall) {
|
|
2229
|
+
await sender.sendErrorResponse(id, -32602, "toolCall is required");
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const policyAbort = new AbortController();
|
|
2233
|
+
try {
|
|
2234
|
+
const outcome = await permissionPolicy({
|
|
2235
|
+
toolCall,
|
|
2236
|
+
options,
|
|
2237
|
+
signal: policyAbort.signal
|
|
2238
|
+
});
|
|
2239
|
+
await sender.sendResult(id, { outcome });
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2242
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender) {
|
|
2246
|
+
const id = msg.id;
|
|
2247
|
+
if (id === void 0) return;
|
|
2248
|
+
const params = msg.params;
|
|
2249
|
+
if (!params?.path) {
|
|
2250
|
+
await sender.sendErrorResponse(id, -32602, "path is required");
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
if (msg.method === "fs/write_text_file") {
|
|
2254
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
2255
|
+
toolCallId: `acp-fs-write-${id}`,
|
|
2256
|
+
title: `Write file: ${params.path}`,
|
|
2257
|
+
kind: "edit",
|
|
2258
|
+
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
2259
|
+
});
|
|
2260
|
+
if (!allowed) {
|
|
2261
|
+
await sender.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
try {
|
|
2266
|
+
if (msg.method === "fs/read_text_file") {
|
|
2267
|
+
const result = await fileServer.readTextFile({
|
|
2268
|
+
sessionId: params.sessionId ?? "",
|
|
2269
|
+
path: params.path
|
|
2270
|
+
});
|
|
2271
|
+
await sender.sendResult(id, result);
|
|
2272
|
+
} else {
|
|
2273
|
+
await fileServer.writeTextFile({
|
|
2274
|
+
sessionId: params.sessionId ?? "",
|
|
2275
|
+
path: params.path,
|
|
2276
|
+
content: params.content ?? ""
|
|
2277
|
+
});
|
|
2278
|
+
await sender.sendResult(id, {});
|
|
2279
|
+
}
|
|
2280
|
+
} catch (err) {
|
|
2281
|
+
const code = err instanceof FsError ? -32602 : -32603;
|
|
2282
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2283
|
+
await sender.sendErrorResponse(id, code, message);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender) {
|
|
2287
|
+
const id = msg.id;
|
|
2288
|
+
if (id === void 0) return;
|
|
2289
|
+
const params = msg.params ?? {};
|
|
2290
|
+
try {
|
|
2291
|
+
switch (msg.method) {
|
|
2292
|
+
case "terminal/create": {
|
|
2293
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
2294
|
+
toolCallId: `acp-terminal-create-${id}`,
|
|
2295
|
+
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
2296
|
+
kind: "execute",
|
|
2297
|
+
rawInput: {
|
|
2298
|
+
command: params.command,
|
|
2299
|
+
args: params.args,
|
|
2300
|
+
cwd: params.cwd,
|
|
2301
|
+
sessionId: params.sessionId
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
2304
|
+
if (!allowed) {
|
|
2305
|
+
await sender.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
const createOpts = {
|
|
2309
|
+
sessionId: String(params.sessionId ?? ""),
|
|
2310
|
+
command: String(params.command ?? ""),
|
|
2311
|
+
args: Array.isArray(params.args) ? params.args : []
|
|
2312
|
+
};
|
|
2313
|
+
if (Array.isArray(params.env)) {
|
|
2314
|
+
createOpts.env = params.env;
|
|
2315
|
+
}
|
|
2316
|
+
if (typeof params.cwd === "string") {
|
|
2317
|
+
createOpts.cwd = params.cwd;
|
|
2318
|
+
}
|
|
2319
|
+
if (typeof params.outputByteLimit === "number") {
|
|
2320
|
+
createOpts.outputByteLimit = params.outputByteLimit;
|
|
2321
|
+
}
|
|
2322
|
+
const result = terminalServer.create(createOpts);
|
|
2323
|
+
await sender.sendResult(id, result);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
case "terminal/output": {
|
|
2327
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2328
|
+
const out = terminalServer.output(terminalId);
|
|
2329
|
+
await sender.sendResult(id, out);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
case "terminal/wait_for_exit": {
|
|
2333
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2334
|
+
const exit = await terminalServer.waitForExit(terminalId);
|
|
2335
|
+
await sender.sendResult(id, exit);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
case "terminal/kill": {
|
|
2339
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2340
|
+
terminalServer.kill(terminalId);
|
|
2341
|
+
await sender.sendResult(id, {});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
case "terminal/release": {
|
|
2345
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2346
|
+
terminalServer.release(terminalId);
|
|
2347
|
+
await sender.sendResult(id, {});
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
default:
|
|
2351
|
+
await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
2352
|
+
}
|
|
2353
|
+
} catch (err) {
|
|
2354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2355
|
+
await sender.sendErrorResponse(id, -32603, message);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
async function authorizeAcpCallback(permissionPolicy, partial) {
|
|
2359
|
+
try {
|
|
2360
|
+
const outcome = await permissionPolicy({
|
|
2361
|
+
toolCall: {
|
|
2362
|
+
sessionUpdate: "tool_call_update",
|
|
2363
|
+
toolCallId: partial.toolCallId,
|
|
2364
|
+
title: partial.title,
|
|
2365
|
+
kind: partial.kind,
|
|
2366
|
+
status: "pending",
|
|
2367
|
+
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
2368
|
+
},
|
|
2369
|
+
options: [
|
|
2370
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
2371
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
2372
|
+
],
|
|
2373
|
+
signal: new AbortController().signal
|
|
2374
|
+
});
|
|
2375
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
2376
|
+
} catch {
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// src/client/acp-message-routing.ts
|
|
2382
|
+
function isBestEffortAckMethod(method) {
|
|
2383
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/client/acp-session.ts
|
|
2077
2387
|
var ACPSession = class _ACPSession {
|
|
2078
2388
|
transport;
|
|
2079
2389
|
fileServer;
|
|
@@ -2767,6 +3077,12 @@ var ACPSession = class _ACPSession {
|
|
|
2767
3077
|
error: { code, message }
|
|
2768
3078
|
});
|
|
2769
3079
|
}
|
|
3080
|
+
responseSender() {
|
|
3081
|
+
return {
|
|
3082
|
+
sendResult: (id, result) => this.sendResult(id, result),
|
|
3083
|
+
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
2770
3086
|
handleMessage(msg) {
|
|
2771
3087
|
if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
|
|
2772
3088
|
const pending = this.pending.get(msg.id);
|
|
@@ -2781,29 +3097,32 @@ var ACPSession = class _ACPSession {
|
|
|
2781
3097
|
return;
|
|
2782
3098
|
}
|
|
2783
3099
|
if (msg.method === "session/update") {
|
|
2784
|
-
this.
|
|
3100
|
+
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
2785
3101
|
return;
|
|
2786
3102
|
}
|
|
2787
3103
|
if (msg.method === "session/request_permission") {
|
|
2788
|
-
void this.
|
|
3104
|
+
void handleAcpPermissionRequest(msg, this.permissionPolicy, this.responseSender());
|
|
2789
3105
|
return;
|
|
2790
3106
|
}
|
|
2791
3107
|
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
2792
|
-
void
|
|
3108
|
+
void handleAcpFsRequest(
|
|
3109
|
+
msg,
|
|
3110
|
+
this.fileServer,
|
|
3111
|
+
this.permissionPolicy,
|
|
3112
|
+
this.responseSender()
|
|
3113
|
+
);
|
|
2793
3114
|
return;
|
|
2794
3115
|
}
|
|
2795
3116
|
if (msg.method?.startsWith("terminal/")) {
|
|
2796
|
-
void
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
});
|
|
2803
|
-
}
|
|
3117
|
+
void handleAcpTerminalRequest(
|
|
3118
|
+
msg,
|
|
3119
|
+
this.terminalServer,
|
|
3120
|
+
this.permissionPolicy,
|
|
3121
|
+
this.responseSender()
|
|
3122
|
+
);
|
|
2804
3123
|
return;
|
|
2805
3124
|
}
|
|
2806
|
-
if (msg.method
|
|
3125
|
+
if (isBestEffortAckMethod(msg.method)) {
|
|
2807
3126
|
if (msg.id !== void 0) {
|
|
2808
3127
|
this.sendResult(msg.id, {}).catch(() => {
|
|
2809
3128
|
});
|
|
@@ -2824,98 +3143,6 @@ var ACPSession = class _ACPSession {
|
|
|
2824
3143
|
);
|
|
2825
3144
|
}
|
|
2826
3145
|
}
|
|
2827
|
-
handleUpdate(msg) {
|
|
2828
|
-
const update = msg.params?.update;
|
|
2829
|
-
if (typeof update !== "object" || update === null) return;
|
|
2830
|
-
const u = update;
|
|
2831
|
-
this.emitProgress({ type: "raw", update: u });
|
|
2832
|
-
switch (u.sessionUpdate) {
|
|
2833
|
-
case "agent_message_chunk": {
|
|
2834
|
-
const text = extractText(u.content);
|
|
2835
|
-
if (text) {
|
|
2836
|
-
this.scratch.text += text;
|
|
2837
|
-
this.emitProgress({ type: "message", text });
|
|
2838
|
-
}
|
|
2839
|
-
return;
|
|
2840
|
-
}
|
|
2841
|
-
case "thought_chunk": {
|
|
2842
|
-
const text = extractText(u.content);
|
|
2843
|
-
if (text) {
|
|
2844
|
-
this.scratch.thoughts += text;
|
|
2845
|
-
this.emitProgress({ type: "thought", text });
|
|
2846
|
-
}
|
|
2847
|
-
return;
|
|
2848
|
-
}
|
|
2849
|
-
case "tool_call":
|
|
2850
|
-
case "tool_call_update": {
|
|
2851
|
-
this.captureToolCall(u, u.sessionUpdate === "tool_call");
|
|
2852
|
-
return;
|
|
2853
|
-
}
|
|
2854
|
-
case "plan":
|
|
2855
|
-
if (Array.isArray(u.entries)) {
|
|
2856
|
-
this.scratch.plan = u.entries;
|
|
2857
|
-
this.emitProgress({ type: "plan", entries: u.entries });
|
|
2858
|
-
}
|
|
2859
|
-
return;
|
|
2860
|
-
case "usage_update":
|
|
2861
|
-
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
2862
|
-
const usage = {
|
|
2863
|
-
used: u.used,
|
|
2864
|
-
size: u.size,
|
|
2865
|
-
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
2866
|
-
};
|
|
2867
|
-
this.scratch.usage = usage;
|
|
2868
|
-
this.emitProgress({ type: "usage", usage });
|
|
2869
|
-
}
|
|
2870
|
-
return;
|
|
2871
|
-
case "available_commands_update":
|
|
2872
|
-
case "current_mode_update":
|
|
2873
|
-
case "config_option_update":
|
|
2874
|
-
case "session_info_update":
|
|
2875
|
-
case "user_message_chunk":
|
|
2876
|
-
case "next_edit_suggestions":
|
|
2877
|
-
case "elicitation":
|
|
2878
|
-
return;
|
|
2879
|
-
default:
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
/**
|
|
2884
|
-
* Fold a `tool_call` / `tool_call_update` notification into the scratch
|
|
2885
|
-
* tool-call map (deduped by toolCallId), extract any `diff` content into
|
|
2886
|
-
* the diffs list, and emit live progress.
|
|
2887
|
-
*/
|
|
2888
|
-
captureToolCall(u, isNew) {
|
|
2889
|
-
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
2890
|
-
if (!toolCallId) return;
|
|
2891
|
-
const prev = this.scratch.toolCalls.get(toolCallId);
|
|
2892
|
-
const record = {
|
|
2893
|
-
toolCallId,
|
|
2894
|
-
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
2895
|
-
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
2896
|
-
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
2897
|
-
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
2898
|
-
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
2899
|
-
};
|
|
2900
|
-
this.scratch.toolCalls.set(toolCallId, record);
|
|
2901
|
-
if (Array.isArray(u.content)) {
|
|
2902
|
-
for (const c of u.content) {
|
|
2903
|
-
if (c && typeof c === "object" && c.type === "diff") {
|
|
2904
|
-
const diff = {
|
|
2905
|
-
path: c.path,
|
|
2906
|
-
oldText: c.oldText,
|
|
2907
|
-
newText: c.newText
|
|
2908
|
-
};
|
|
2909
|
-
this.scratch.diffs.push(diff);
|
|
2910
|
-
this.emitProgress({ type: "diff", diff });
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
this.emitProgress({
|
|
2915
|
-
type: isNew ? "tool_call" : "tool_call_update",
|
|
2916
|
-
toolCall: record
|
|
2917
|
-
});
|
|
2918
|
-
}
|
|
2919
3146
|
emitProgress(event) {
|
|
2920
3147
|
if (!this.progressHandler) return;
|
|
2921
3148
|
try {
|
|
@@ -2926,217 +3153,11 @@ var ACPSession = class _ACPSession {
|
|
|
2926
3153
|
/** Live progress handler installed for the duration of a `prompt()` turn. */
|
|
2927
3154
|
progressHandler = null;
|
|
2928
3155
|
// Per-prompt scratch state
|
|
2929
|
-
scratch =
|
|
3156
|
+
scratch = createSessionScratch();
|
|
2930
3157
|
resetScratch() {
|
|
2931
|
-
this.scratch =
|
|
2932
|
-
}
|
|
2933
|
-
async handlePermissionRequest(msg) {
|
|
2934
|
-
const id = msg.id;
|
|
2935
|
-
if (id === void 0) return;
|
|
2936
|
-
const params = msg.params;
|
|
2937
|
-
const toolCall = params?.toolCall;
|
|
2938
|
-
const options = Array.isArray(params?.options) ? params.options : [];
|
|
2939
|
-
if (!toolCall) {
|
|
2940
|
-
await this.sendErrorResponse(id, -32602, "toolCall is required");
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
const policyAbort = new AbortController();
|
|
2944
|
-
try {
|
|
2945
|
-
const outcome = await this.permissionPolicy({
|
|
2946
|
-
toolCall,
|
|
2947
|
-
options,
|
|
2948
|
-
signal: policyAbort.signal
|
|
2949
|
-
});
|
|
2950
|
-
await this.sendResult(id, { outcome });
|
|
2951
|
-
} catch (err) {
|
|
2952
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2953
|
-
await this.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
/**
|
|
2957
|
-
* Enforce authorization at privileged callback sinks (fs/write,
|
|
2958
|
-
* terminal/create). Unlike `handlePermissionRequest` which responds to
|
|
2959
|
-
* agent-initiated `session/request_permission` messages, this method is
|
|
2960
|
-
* called by the handler BEFORE dispatching to FileServer/TerminalServer,
|
|
2961
|
-
* closing the gap where the agent simply skips the voluntary permission
|
|
2962
|
-
* request and sends the privileged callback directly.
|
|
2963
|
-
*
|
|
2964
|
-
* Uses the session's permission policy. The default
|
|
2965
|
-
* (`readOnlyPermissionPolicy`) auto-approves only side-effect-free tool
|
|
2966
|
-
* calls (read/search/fetch/think) and rejects everything else — this is
|
|
2967
|
-
* the safe-by-default posture. For trusted local agents (CLI `acp spawn`,
|
|
2968
|
-
* Director fan-out), inject `defaultPermissionPolicy` to grant
|
|
2969
|
-
* write/execute access.
|
|
2970
|
-
*
|
|
2971
|
-
* Returns true if the callback is authorized, false if denied.
|
|
2972
|
-
*/
|
|
2973
|
-
async authorizeCallback(partial) {
|
|
2974
|
-
try {
|
|
2975
|
-
const outcome = await this.permissionPolicy({
|
|
2976
|
-
toolCall: {
|
|
2977
|
-
sessionUpdate: "tool_call_update",
|
|
2978
|
-
toolCallId: partial.toolCallId,
|
|
2979
|
-
title: partial.title,
|
|
2980
|
-
kind: partial.kind,
|
|
2981
|
-
status: "pending",
|
|
2982
|
-
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
2983
|
-
},
|
|
2984
|
-
options: [
|
|
2985
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
2986
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
2987
|
-
],
|
|
2988
|
-
signal: new AbortController().signal
|
|
2989
|
-
});
|
|
2990
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
2991
|
-
} catch {
|
|
2992
|
-
return false;
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
async handleFsRequest(msg) {
|
|
2996
|
-
const id = msg.id;
|
|
2997
|
-
if (id === void 0) return;
|
|
2998
|
-
const params = msg.params;
|
|
2999
|
-
if (!params?.path) {
|
|
3000
|
-
await this.sendErrorResponse(id, -32602, "path is required");
|
|
3001
|
-
return;
|
|
3002
|
-
}
|
|
3003
|
-
if (msg.method === "fs/write_text_file") {
|
|
3004
|
-
const allowed = await this.authorizeCallback({
|
|
3005
|
-
toolCallId: `acp-fs-write-${id}`,
|
|
3006
|
-
title: `Write file: ${params.path}`,
|
|
3007
|
-
kind: "edit",
|
|
3008
|
-
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
3009
|
-
});
|
|
3010
|
-
if (!allowed) {
|
|
3011
|
-
await this.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
3012
|
-
return;
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
try {
|
|
3016
|
-
if (msg.method === "fs/read_text_file") {
|
|
3017
|
-
const result = await this.fileServer.readTextFile({
|
|
3018
|
-
sessionId: params.sessionId ?? "",
|
|
3019
|
-
path: params.path
|
|
3020
|
-
});
|
|
3021
|
-
await this.sendResult(id, result);
|
|
3022
|
-
} else {
|
|
3023
|
-
await this.fileServer.writeTextFile({
|
|
3024
|
-
sessionId: params.sessionId ?? "",
|
|
3025
|
-
path: params.path,
|
|
3026
|
-
content: params.content ?? ""
|
|
3027
|
-
});
|
|
3028
|
-
await this.sendResult(id, {});
|
|
3029
|
-
}
|
|
3030
|
-
} catch (err) {
|
|
3031
|
-
const code = err instanceof FsError ? -32602 : -32603;
|
|
3032
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3033
|
-
await this.sendErrorResponse(id, code, message);
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
async handleTerminalRequest(msg) {
|
|
3037
|
-
const id = msg.id;
|
|
3038
|
-
if (id === void 0) return;
|
|
3039
|
-
const params = msg.params ?? {};
|
|
3040
|
-
try {
|
|
3041
|
-
switch (msg.method) {
|
|
3042
|
-
case "terminal/create": {
|
|
3043
|
-
const allowed = await this.authorizeCallback({
|
|
3044
|
-
toolCallId: `acp-terminal-create-${id}`,
|
|
3045
|
-
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
3046
|
-
kind: "execute",
|
|
3047
|
-
rawInput: {
|
|
3048
|
-
command: params.command,
|
|
3049
|
-
args: params.args,
|
|
3050
|
-
cwd: params.cwd,
|
|
3051
|
-
sessionId: params.sessionId
|
|
3052
|
-
}
|
|
3053
|
-
});
|
|
3054
|
-
if (!allowed) {
|
|
3055
|
-
await this.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
3056
|
-
return;
|
|
3057
|
-
}
|
|
3058
|
-
const createOpts = {
|
|
3059
|
-
sessionId: String(params.sessionId ?? ""),
|
|
3060
|
-
command: String(params.command ?? ""),
|
|
3061
|
-
args: Array.isArray(params.args) ? params.args : []
|
|
3062
|
-
};
|
|
3063
|
-
if (Array.isArray(params.env)) {
|
|
3064
|
-
createOpts.env = params.env;
|
|
3065
|
-
}
|
|
3066
|
-
if (typeof params.cwd === "string") {
|
|
3067
|
-
createOpts.cwd = params.cwd;
|
|
3068
|
-
}
|
|
3069
|
-
if (typeof params.outputByteLimit === "number") {
|
|
3070
|
-
createOpts.outputByteLimit = params.outputByteLimit;
|
|
3071
|
-
}
|
|
3072
|
-
const result = this.terminalServer.create(createOpts);
|
|
3073
|
-
await this.sendResult(id, result);
|
|
3074
|
-
return;
|
|
3075
|
-
}
|
|
3076
|
-
case "terminal/output": {
|
|
3077
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3078
|
-
const out = this.terminalServer.output(terminalId);
|
|
3079
|
-
await this.sendResult(id, out);
|
|
3080
|
-
return;
|
|
3081
|
-
}
|
|
3082
|
-
case "terminal/wait_for_exit": {
|
|
3083
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3084
|
-
const exit = await this.terminalServer.waitForExit(terminalId);
|
|
3085
|
-
await this.sendResult(id, exit);
|
|
3086
|
-
return;
|
|
3087
|
-
}
|
|
3088
|
-
case "terminal/kill": {
|
|
3089
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3090
|
-
this.terminalServer.kill(terminalId);
|
|
3091
|
-
await this.sendResult(id, {});
|
|
3092
|
-
return;
|
|
3093
|
-
}
|
|
3094
|
-
case "terminal/release": {
|
|
3095
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3096
|
-
this.terminalServer.release(terminalId);
|
|
3097
|
-
await this.sendResult(id, {});
|
|
3098
|
-
return;
|
|
3099
|
-
}
|
|
3100
|
-
default:
|
|
3101
|
-
await this.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
3102
|
-
}
|
|
3103
|
-
} catch (err) {
|
|
3104
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3105
|
-
await this.sendErrorResponse(id, -32603, message);
|
|
3106
|
-
}
|
|
3158
|
+
this.scratch = createSessionScratch();
|
|
3107
3159
|
}
|
|
3108
3160
|
};
|
|
3109
|
-
function textContent(text) {
|
|
3110
|
-
return { type: "text", text };
|
|
3111
|
-
}
|
|
3112
|
-
function imageContent(mimeType, data) {
|
|
3113
|
-
return { type: "image", mimeType, data };
|
|
3114
|
-
}
|
|
3115
|
-
function audioContent(mimeType, data) {
|
|
3116
|
-
return { type: "audio", mimeType, data };
|
|
3117
|
-
}
|
|
3118
|
-
function extractText(block) {
|
|
3119
|
-
if (typeof block !== "object" || block === null) return "";
|
|
3120
|
-
const b = block;
|
|
3121
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
3122
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
3123
|
-
return b.resource.text;
|
|
3124
|
-
}
|
|
3125
|
-
return "";
|
|
3126
|
-
}
|
|
3127
|
-
function isRecord(v) {
|
|
3128
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3129
|
-
}
|
|
3130
|
-
function emptyRunResult(stopReason) {
|
|
3131
|
-
return {
|
|
3132
|
-
text: "",
|
|
3133
|
-
stopReason,
|
|
3134
|
-
hasText: false,
|
|
3135
|
-
toolCalls: [],
|
|
3136
|
-
diffs: [],
|
|
3137
|
-
thoughts: ""
|
|
3138
|
-
};
|
|
3139
|
-
}
|
|
3140
3161
|
|
|
3141
3162
|
// src/client/tool-translator.ts
|
|
3142
3163
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
@@ -3286,12 +3307,12 @@ async function benchOne(agentId, cmd, opts) {
|
|
|
3286
3307
|
signal
|
|
3287
3308
|
);
|
|
3288
3309
|
fsOk = fsRes.text.includes(fileToken);
|
|
3289
|
-
if (!fsOk)
|
|
3310
|
+
if (!fsOk)
|
|
3311
|
+
fsDetail = "agent did not return the file contents (may not have used a read tool)";
|
|
3290
3312
|
} catch (err) {
|
|
3291
3313
|
fsDetail = err instanceof Error ? err.message : String(err);
|
|
3292
3314
|
} finally {
|
|
3293
|
-
await
|
|
3294
|
-
});
|
|
3315
|
+
await removeBenchFile(filePath);
|
|
3295
3316
|
}
|
|
3296
3317
|
checks.push({ name: "fs", ok: fsOk, detail: fsDetail });
|
|
3297
3318
|
}
|
|
@@ -3306,8 +3327,7 @@ async function benchOne(agentId, cmd, opts) {
|
|
|
3306
3327
|
}
|
|
3307
3328
|
const required = checks.filter((c) => c.name !== "fs" || opts.checkFs);
|
|
3308
3329
|
const allReq = required.every((c) => c.ok);
|
|
3309
|
-
const
|
|
3310
|
-
const status = allReq ? "pass" : handshakeOk ? "partial" : "fail";
|
|
3330
|
+
const status = allReq ? "pass" : "partial";
|
|
3311
3331
|
return {
|
|
3312
3332
|
agentId,
|
|
3313
3333
|
status,
|
|
@@ -3399,7 +3419,9 @@ function renderAcpBenchText(result) {
|
|
|
3399
3419
|
for (const r of result.results) {
|
|
3400
3420
|
const checks = r.checks.map((c) => `${c.ok ? "\u2713" : "\u2717"}${c.name}`).join(" ");
|
|
3401
3421
|
const timing = r.handshakeMs !== void 0 ? ` hs=${r.handshakeMs}ms${r.promptMs !== void 0 ? ` prompt=${r.promptMs}ms` : ""}` : "";
|
|
3402
|
-
lines.push(
|
|
3422
|
+
lines.push(
|
|
3423
|
+
` ${icon(r.status)} ${r.agentId.padEnd(16)} ${r.status.toUpperCase().padEnd(7)} ${checks}${timing}`
|
|
3424
|
+
);
|
|
3403
3425
|
if (r.agentInfo) lines.push(` agent: ${r.agentInfo.name} ${r.agentInfo.version}`);
|
|
3404
3426
|
if (r.sample) lines.push(` reply: ${r.sample}`);
|
|
3405
3427
|
if (r.reason) lines.push(` reason: ${r.reason}`);
|
|
@@ -3411,6 +3433,9 @@ function renderAcpBenchText(result) {
|
|
|
3411
3433
|
);
|
|
3412
3434
|
return lines.join("\n");
|
|
3413
3435
|
}
|
|
3436
|
+
async function removeBenchFile(filePath, remove = fsp2.rm) {
|
|
3437
|
+
await remove(filePath, { force: true }).catch(() => void 0);
|
|
3438
|
+
}
|
|
3414
3439
|
|
|
3415
3440
|
// src/registry/agents.catalog.ts
|
|
3416
3441
|
var AGENTS_CATALOG = [
|
|
@@ -3799,16 +3824,8 @@ function mapACPKind(acpKind) {
|
|
|
3799
3824
|
}
|
|
3800
3825
|
}
|
|
3801
3826
|
function isRetryable(kind) {
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
case "provider_rate_limit":
|
|
3805
|
-
case "provider_timeout":
|
|
3806
|
-
case "tool_threw":
|
|
3807
|
-
case "budget_timeout":
|
|
3808
|
-
return true;
|
|
3809
|
-
default:
|
|
3810
|
-
return false;
|
|
3811
|
-
}
|
|
3827
|
+
void kind;
|
|
3828
|
+
return false;
|
|
3812
3829
|
}
|
|
3813
3830
|
var REGISTRY_ID_ALIASES = {
|
|
3814
3831
|
"claude-code": "claude-acp",
|
|
@@ -3948,11 +3965,15 @@ async function probeAcpAgents(opts) {
|
|
|
3948
3965
|
}
|
|
3949
3966
|
await runPhase(local, opts.concurrency ?? 4, localTimeout);
|
|
3950
3967
|
await runPhase(pkg, 2, pkgTimeout);
|
|
3951
|
-
return ids.map((id) => byId.get(id)
|
|
3968
|
+
return ids.map((id) => byId.get(id));
|
|
3952
3969
|
}
|
|
3953
3970
|
|
|
3971
|
+
// src/integration/ensemble-runner.ts
|
|
3972
|
+
import { SubagentBudget } from "@wrongstack/core/coordination";
|
|
3973
|
+
|
|
3954
3974
|
// src/registry/ensemble-registry.ts
|
|
3955
3975
|
import { spawn as spawn2 } from "node:child_process";
|
|
3976
|
+
import { treeKill as treeKill2 } from "@wrongstack/core/utils/tree-kill";
|
|
3956
3977
|
var PROBE_TIMEOUT_MS = 5e3;
|
|
3957
3978
|
var PROBE_CACHE_MS = 5e3;
|
|
3958
3979
|
var MAX_PARALLEL_PROBES = 4;
|
|
@@ -3976,7 +3997,7 @@ async function probeWithBound(items, worker, limit) {
|
|
|
3976
3997
|
await Promise.all(runners);
|
|
3977
3998
|
return results;
|
|
3978
3999
|
}
|
|
3979
|
-
async function defaultProbe(desc, timeoutMs) {
|
|
4000
|
+
async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = process.platform) {
|
|
3980
4001
|
const start = Date.now();
|
|
3981
4002
|
return new Promise((resolve3) => {
|
|
3982
4003
|
let settled = false;
|
|
@@ -3986,7 +4007,9 @@ async function defaultProbe(desc, timeoutMs) {
|
|
|
3986
4007
|
if (settled) return;
|
|
3987
4008
|
settled = true;
|
|
3988
4009
|
try {
|
|
3989
|
-
child.
|
|
4010
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
4011
|
+
treeKill2(child);
|
|
4012
|
+
}
|
|
3990
4013
|
} catch {
|
|
3991
4014
|
}
|
|
3992
4015
|
resolve3(result);
|
|
@@ -3994,8 +4017,8 @@ async function defaultProbe(desc, timeoutMs) {
|
|
|
3994
4017
|
let child;
|
|
3995
4018
|
try {
|
|
3996
4019
|
const probeArgs = [...desc.probe.args ?? []];
|
|
3997
|
-
const shim =
|
|
3998
|
-
child =
|
|
4020
|
+
const shim = platform === "win32" ? buildWin32CmdShimInvocation(desc.probe.command, probeArgs) : null;
|
|
4021
|
+
child = spawnProcess(shim?.command ?? desc.probe.command, shim?.args ?? probeArgs, {
|
|
3999
4022
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4000
4023
|
windowsHide: true,
|
|
4001
4024
|
...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
|
|
@@ -4028,7 +4051,7 @@ async function defaultProbe(desc, timeoutMs) {
|
|
|
4028
4051
|
clearTimeout(timer);
|
|
4029
4052
|
const durationMs = Date.now() - start;
|
|
4030
4053
|
const out = (stdout + stderr).trim();
|
|
4031
|
-
const isWindowsShellMiss =
|
|
4054
|
+
const isWindowsShellMiss = platform === "win32" && out.toLowerCase().includes("is not recognized");
|
|
4032
4055
|
if (isWindowsShellMiss) {
|
|
4033
4056
|
finish({
|
|
4034
4057
|
ok: false,
|
|
@@ -4040,7 +4063,7 @@ async function defaultProbe(desc, timeoutMs) {
|
|
|
4040
4063
|
if (out.length > 0) {
|
|
4041
4064
|
finish({
|
|
4042
4065
|
ok: true,
|
|
4043
|
-
version: out.split("\n")[0]
|
|
4066
|
+
version: out.split("\n")[0].trim(),
|
|
4044
4067
|
path: desc.probe.command,
|
|
4045
4068
|
durationMs
|
|
4046
4069
|
});
|
|
@@ -4116,7 +4139,6 @@ var EnsembleRegistry = class {
|
|
|
4116
4139
|
};
|
|
4117
4140
|
|
|
4118
4141
|
// src/integration/ensemble-runner.ts
|
|
4119
|
-
import { SubagentBudget } from "@wrongstack/core/coordination";
|
|
4120
4142
|
var DEFAULT_MAX_CONCURRENCY = 4;
|
|
4121
4143
|
async function mapBound(items, worker, limit) {
|
|
4122
4144
|
const results = new Array(items.length);
|
|
@@ -4259,10 +4281,7 @@ async function runEnsemble(opts) {
|
|
|
4259
4281
|
}
|
|
4260
4282
|
runnable.push({ id, cmd });
|
|
4261
4283
|
}
|
|
4262
|
-
const concurrency = Math.max(
|
|
4263
|
-
1,
|
|
4264
|
-
opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY
|
|
4265
|
-
);
|
|
4284
|
+
const concurrency = Math.max(1, opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY);
|
|
4266
4285
|
await mapBound(
|
|
4267
4286
|
runnable,
|
|
4268
4287
|
async ({ id, cmd }) => {
|
|
@@ -4311,15 +4330,11 @@ function renderEnsembleText(result) {
|
|
|
4311
4330
|
);
|
|
4312
4331
|
break;
|
|
4313
4332
|
case "failed":
|
|
4314
|
-
lines.push(
|
|
4315
|
-
`[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}`
|
|
4316
|
-
);
|
|
4333
|
+
lines.push(`[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}`);
|
|
4317
4334
|
lines.push(`[${r.agentId}] failed ${r.durationMs}ms`);
|
|
4318
4335
|
break;
|
|
4319
4336
|
case "cancelled":
|
|
4320
|
-
lines.push(
|
|
4321
|
-
`[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}`
|
|
4322
|
-
);
|
|
4337
|
+
lines.push(`[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}`);
|
|
4323
4338
|
lines.push(`[${r.agentId}] cancelled ${r.durationMs}ms`);
|
|
4324
4339
|
break;
|
|
4325
4340
|
case "skipped":
|
|
@@ -4381,15 +4396,14 @@ async function runOneAcpTask(opts) {
|
|
|
4381
4396
|
|
|
4382
4397
|
// src/registry/acp-registry-fetch.ts
|
|
4383
4398
|
var ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
4384
|
-
function currentPlatformKey() {
|
|
4385
|
-
const os =
|
|
4386
|
-
const arch =
|
|
4399
|
+
function currentPlatformKey(platform = process.platform, architecture = process.arch) {
|
|
4400
|
+
const os = platform === "win32" ? "windows" : platform === "darwin" ? "darwin" : "linux";
|
|
4401
|
+
const arch = architecture === "arm64" ? "aarch64" : architecture === "x64" ? "x86_64" : architecture;
|
|
4387
4402
|
return `${os}-${arch}`;
|
|
4388
4403
|
}
|
|
4389
4404
|
function basename(cmd) {
|
|
4390
4405
|
const cleaned = cmd.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
4391
|
-
|
|
4392
|
-
return parts[parts.length - 1] || cleaned;
|
|
4406
|
+
return cleaned.slice(cleaned.lastIndexOf("/") + 1);
|
|
4393
4407
|
}
|
|
4394
4408
|
function mapRegistryEntry(entry, platformKey = currentPlatformKey()) {
|
|
4395
4409
|
if (!entry || typeof entry.id !== "string" || entry.id.length === 0) return null;
|