@wrongstack/acp 0.295.0 → 0.295.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/agent/protocol-handler.d.ts +9 -0
  2. package/dist/agent/protocol-handler.d.ts.map +1 -1
  3. package/dist/agent/server-agent-turn.d.ts +83 -1
  4. package/dist/agent/server-agent-turn.d.ts.map +1 -1
  5. package/dist/agent/session-store.d.ts.map +1 -1
  6. package/dist/agent/stdio-transport.d.ts +18 -0
  7. package/dist/agent/stdio-transport.d.ts.map +1 -1
  8. package/dist/agent/wrongstack-acp-agent.d.ts +35 -0
  9. package/dist/agent/wrongstack-acp-agent.d.ts.map +1 -1
  10. package/dist/agent.js +30 -23
  11. package/dist/agent.js.map +3 -3
  12. package/dist/client/file-server.d.ts +19 -0
  13. package/dist/client/file-server.d.ts.map +1 -1
  14. package/dist/client/terminal-server.d.ts.map +1 -1
  15. package/dist/client/trust-boundary-permission.d.ts +16 -1
  16. package/dist/client/trust-boundary-permission.d.ts.map +1 -1
  17. package/dist/client/websocket-transport.d.ts.map +1 -1
  18. package/dist/client.js +40 -35
  19. package/dist/client.js.map +2 -2
  20. package/dist/index.js +91 -82
  21. package/dist/index.js.map +4 -4
  22. package/dist/integration/acp-bench.d.ts +32 -0
  23. package/dist/integration/acp-bench.d.ts.map +1 -1
  24. package/dist/integration/acp-subagent-runner.d.ts.map +1 -1
  25. package/dist/integration/ensemble-runner.d.ts +22 -1
  26. package/dist/integration/ensemble-runner.d.ts.map +1 -1
  27. package/dist/registry/acp-registry-fetch.d.ts +1 -1
  28. package/dist/registry/acp-registry-fetch.d.ts.map +1 -1
  29. package/dist/registry/ensemble-registry.d.ts +22 -0
  30. package/dist/registry/ensemble-registry.d.ts.map +1 -1
  31. package/dist/version.d.ts +8 -0
  32. package/dist/version.d.ts.map +1 -1
  33. package/dist/wrongstack-acp-agent.js +26 -20
  34. package/dist/wrongstack-acp-agent.js.map +2 -2
  35. package/package.json +2 -2
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 = require2("../package.json");
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) && (opts.maxSessions ?? 0) > 0 ? Math.floor(opts.maxSessions) : DEFAULT_MAX_SESSIONS;
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 = sessionId ? this.replayFor?.(sessionId) : void 0;
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
- if (sessionId) {
430
- this.sessions.delete(sessionId);
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) && (value ?? 0) > 0 ? Math.floor(value) : fallback;
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) return Promise.resolve(expectDefined(this.messageQueue.shift()));
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 shim = process.platform === "win32" ? buildWin32CmdShimInvocation(this.opts.command, childArgs) : null;
934
- this.child = spawn3(shim?.command ?? this.opts.command, shim?.args ?? childArgs, {
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
- ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
933
+ ...verbatimOptions(invocation)
940
934
  });
941
935
  } catch (err) {
942
936
  clearTimeout(timeout);
@@ -1001,7 +995,8 @@ var ClientTransport = class {
1001
995
  });
1002
996
  }
1003
997
  read() {
1004
- if (this.messageQueue.length > 0) return Promise.resolve(expectDefined(this.messageQueue.shift()));
998
+ if (this.messageQueue.length > 0)
999
+ return Promise.resolve(expectDefined(this.messageQueue.shift()));
1005
1000
  if (this.closed) return Promise.resolve(null);
1006
1001
  return new Promise((resolve3) => {
1007
1002
  this.resolveRead = resolve3;
@@ -1020,10 +1015,7 @@ var ClientTransport = class {
1020
1015
  this.handlers.clear();
1021
1016
  const child = this.child;
1022
1017
  if (!child) return;
1023
- try {
1024
- child.kill();
1025
- } catch {
1026
- }
1018
+ treeKill(child);
1027
1019
  this.child = null;
1028
1020
  }
1029
1021
  onChildData(chunk) {
@@ -1087,6 +1079,13 @@ var ClientTransport = class {
1087
1079
  }
1088
1080
  }
1089
1081
  };
1082
+ function spawnInvocation(command, args, platform) {
1083
+ if (platform !== "win32") return { command, args };
1084
+ return buildWin32CmdShimInvocation(command, args);
1085
+ }
1086
+ function verbatimOptions(invocation) {
1087
+ return invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {};
1088
+ }
1090
1089
 
1091
1090
  // src/agent/tools-registry.ts
1092
1091
  var ACPToolsRegistry = class {
@@ -1203,8 +1202,8 @@ function toolToPriority(tool) {
1203
1202
  }
1204
1203
 
1205
1204
  // src/agent/wrongstack-acp-agent.ts
1206
- import { fileURLToPath } from "node:url";
1207
1205
  import { createServer } from "node:http";
1206
+ import { fileURLToPath } from "node:url";
1208
1207
  import { writeErr as writeErr2 } from "@wrongstack/core/utils";
1209
1208
  var WrongStackACPServer = class {
1210
1209
  transport;
@@ -1265,10 +1264,9 @@ var WrongStackACPServer = class {
1265
1264
  let httpChain = Promise.resolve();
1266
1265
  this.httpServer = createServer(async (req, res) => {
1267
1266
  if (authToken) {
1268
- const url = new URL(req.url ?? "/", `http://${host}:${port}`);
1267
+ const url = new URL(requestPath(req.url), `http://${host}:${port}`);
1269
1268
  const queryToken = url.searchParams.get("token");
1270
- const authHeader = req.headers["authorization"];
1271
- const bearerToken = Array.isArray(authHeader) ? authHeader[0]?.replace(/^Bearer\s+/i, "") : authHeader?.replace(/^Bearer\s+/i, "");
1269
+ const bearerToken = headerValue(req.headers.authorization)?.replace(/^Bearer\s+/i, "");
1272
1270
  const supplied = queryToken ?? bearerToken ?? "";
1273
1271
  if (supplied !== authToken) {
1274
1272
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -1277,7 +1275,7 @@ var WrongStackACPServer = class {
1277
1275
  }
1278
1276
  }
1279
1277
  const selfOrigin = `http://${host}:${port}`;
1280
- const reqOrigin = Array.isArray(req.headers.origin) ? req.headers.origin[0] : req.headers.origin;
1278
+ const reqOrigin = headerValue(req.headers.origin);
1281
1279
  if (reqOrigin && reqOrigin !== selfOrigin) {
1282
1280
  res.writeHead(403);
1283
1281
  res.end(JSON.stringify({ error: "cross-origin request forbidden" }));
@@ -1357,6 +1355,8 @@ var WrongStackACPServer = class {
1357
1355
  try {
1358
1356
  await requestPromise;
1359
1357
  } catch {
1358
+ res.writeHead(500, { "Content-Type": "application/json" });
1359
+ res.end(JSON.stringify({ error: { code: -32603, message: "Internal error" } }));
1360
1360
  }
1361
1361
  });
1362
1362
  return new Promise((resolve3) => {
@@ -1382,6 +1382,12 @@ var WrongStackACPServer = class {
1382
1382
  var defaultEchoRunTurn = async (_input, _emit) => {
1383
1383
  return { stopReason: "end_turn" };
1384
1384
  };
1385
+ function headerValue(value) {
1386
+ return Array.isArray(value) ? value[0] : value;
1387
+ }
1388
+ function requestPath(value) {
1389
+ return value ?? "/";
1390
+ }
1385
1391
  async function main() {
1386
1392
  const server = new WrongStackACPServer();
1387
1393
  await server.start();
@@ -1400,6 +1406,14 @@ import { randomBytes } from "node:crypto";
1400
1406
  import { realpathSync } from "node:fs";
1401
1407
  import * as fsp from "node:fs/promises";
1402
1408
  import * as path from "node:path";
1409
+ var DEFAULT_FILE_OPERATIONS = {
1410
+ stat: fsp.stat,
1411
+ readFile: fsp.readFile,
1412
+ writeFile: fsp.writeFile,
1413
+ realpath: fsp.realpath,
1414
+ rename: fsp.rename,
1415
+ unlink: fsp.unlink
1416
+ };
1403
1417
  var DEFAULT_MAX_READ_BYTES = 5 * 1024 * 1024;
1404
1418
  var DEFAULT_MAX_WRITE_BYTES = 5 * 1024 * 1024;
1405
1419
  var FsError = class extends Error {
@@ -1418,12 +1432,14 @@ var FileServer = class {
1418
1432
  timeoutMs;
1419
1433
  maxReadBytes;
1420
1434
  maxWriteBytes;
1435
+ operations;
1421
1436
  constructor(opts) {
1422
1437
  this.root = path.resolve(opts.projectRoot);
1423
1438
  this.realRoot = safeRealpathSync(this.root);
1424
1439
  this.timeoutMs = opts.timeoutMs ?? 3e4;
1425
1440
  this.maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
1426
1441
  this.maxWriteBytes = opts.maxWriteBytes ?? DEFAULT_MAX_WRITE_BYTES;
1442
+ this.operations = opts.operations ?? DEFAULT_FILE_OPERATIONS;
1427
1443
  }
1428
1444
  /** Read a text file. Returns the content as a string. */
1429
1445
  async readTextFile(params) {
@@ -1431,7 +1447,7 @@ var FileServer = class {
1431
1447
  const controller = new AbortController();
1432
1448
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
1433
1449
  try {
1434
- const stat2 = await fsp.stat(safe).catch((err) => {
1450
+ const stat2 = await this.operations.stat(safe).catch((err) => {
1435
1451
  throw mapFsError(err, safe);
1436
1452
  });
1437
1453
  if (stat2.size > this.maxReadBytes) {
@@ -1441,7 +1457,7 @@ var FileServer = class {
1441
1457
  `file is ${stat2.size} bytes, max read is ${this.maxReadBytes} bytes`
1442
1458
  );
1443
1459
  }
1444
- const content = await fsp.readFile(safe, {
1460
+ const content = await this.operations.readFile(safe, {
1445
1461
  encoding: "utf8",
1446
1462
  signal: controller.signal
1447
1463
  });
@@ -1471,20 +1487,20 @@ var FileServer = class {
1471
1487
  const timer = setTimeout(() => controller.abort(), this.timeoutMs);
1472
1488
  const tmp = `${safe}.${randomBytes(6).toString("hex")}.tmp`;
1473
1489
  try {
1474
- await fsp.writeFile(tmp, params.content, {
1490
+ await this.operations.writeFile(tmp, params.content, {
1475
1491
  encoding: "utf8",
1476
1492
  signal: controller.signal
1477
1493
  });
1478
1494
  await this.assertRealInside(tmp);
1479
1495
  await this.assertRealInside(path.dirname(safe));
1480
- await fsp.rename(tmp, safe);
1496
+ await this.operations.rename(tmp, safe);
1481
1497
  } catch (err) {
1482
1498
  if (err instanceof FsError) {
1483
- await fsp.unlink(tmp).catch(() => void 0);
1499
+ await this.operations.unlink(tmp).catch(() => void 0);
1484
1500
  throw err;
1485
1501
  }
1486
1502
  try {
1487
- await fsp.unlink(tmp);
1503
+ await this.operations.unlink(tmp);
1488
1504
  } catch {
1489
1505
  }
1490
1506
  if (controller.signal.aborted) {
@@ -1528,12 +1544,14 @@ var FileServer = class {
1528
1544
  for (; ; ) {
1529
1545
  let real;
1530
1546
  try {
1531
- real = await fsp.realpath(probe);
1547
+ real = await this.operations.realpath(probe);
1532
1548
  } catch (err) {
1533
1549
  const code = err.code;
1534
1550
  if (code === "ENOENT") {
1535
1551
  const parent = path.dirname(probe);
1536
- if (parent === probe) return;
1552
+ if (parent === probe) {
1553
+ throw new FsError("ENOENT", resolvedPath, `no existing ancestor: ${resolvedPath}`);
1554
+ }
1537
1555
  probe = parent;
1538
1556
  continue;
1539
1557
  }
@@ -1631,7 +1649,6 @@ var TerminalServer = class {
1631
1649
  this.outputByteLimit = opts.outputByteLimit ?? 1024 * 1024;
1632
1650
  this.maxOutputByteLimit = opts.maxOutputByteLimit ?? 16 * 1024 * 1024;
1633
1651
  this.maxTerminals = this.clampFiniteInt(opts.maxTerminals, 32);
1634
- if (this.maxTerminals < 1) throw new RangeError("maxTerminals must be at least 1");
1635
1652
  this.abortSignal = opts.signal;
1636
1653
  if (opts.signal) {
1637
1654
  opts.signal.addEventListener("abort", this.abortHandler, { once: true });
@@ -1962,7 +1979,6 @@ var WebSocketClientTransport = class {
1962
1979
  const ws = new WS(this.opts.url, this.opts.protocols);
1963
1980
  this.ws = ws;
1964
1981
  const timer = setTimeout(() => {
1965
- if (settled) return;
1966
1982
  settled = true;
1967
1983
  try {
1968
1984
  ws.close();
@@ -2000,7 +2016,7 @@ var WebSocketClientTransport = class {
2000
2016
  }
2001
2017
  try {
2002
2018
  const serialized = JSON.stringify(msg);
2003
- const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount ?? 0 : 0;
2019
+ const buffered = Number.isFinite(this.ws.bufferedAmount) ? this.ws.bufferedAmount : 0;
2004
2020
  if (buffered + Buffer.byteLength(serialized, "utf8") > this.maxBufferedBytes) {
2005
2021
  this.stop();
2006
2022
  return Promise.reject(new Error("WebSocket transport send buffer limit exceeded"));
@@ -2057,7 +2073,7 @@ var WebSocketClientTransport = class {
2057
2073
  }
2058
2074
  };
2059
2075
  function finitePositiveLimit(value, fallback) {
2060
- return Number.isFinite(value) && (value ?? 0) > 0 ? Math.floor(value) : fallback;
2076
+ return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
2061
2077
  }
2062
2078
 
2063
2079
  // src/client/acp-session.ts
@@ -3286,12 +3302,12 @@ async function benchOne(agentId, cmd, opts) {
3286
3302
  signal
3287
3303
  );
3288
3304
  fsOk = fsRes.text.includes(fileToken);
3289
- if (!fsOk) fsDetail = "agent did not return the file contents (may not have used a read tool)";
3305
+ if (!fsOk)
3306
+ fsDetail = "agent did not return the file contents (may not have used a read tool)";
3290
3307
  } catch (err) {
3291
3308
  fsDetail = err instanceof Error ? err.message : String(err);
3292
3309
  } finally {
3293
- await fsp2.rm(filePath, { force: true }).catch(() => {
3294
- });
3310
+ await removeBenchFile(filePath);
3295
3311
  }
3296
3312
  checks.push({ name: "fs", ok: fsOk, detail: fsDetail });
3297
3313
  }
@@ -3306,8 +3322,7 @@ async function benchOne(agentId, cmd, opts) {
3306
3322
  }
3307
3323
  const required = checks.filter((c) => c.name !== "fs" || opts.checkFs);
3308
3324
  const allReq = required.every((c) => c.ok);
3309
- const handshakeOk = checks.find((c) => c.name === "handshake")?.ok === true;
3310
- const status = allReq ? "pass" : handshakeOk ? "partial" : "fail";
3325
+ const status = allReq ? "pass" : "partial";
3311
3326
  return {
3312
3327
  agentId,
3313
3328
  status,
@@ -3399,7 +3414,9 @@ function renderAcpBenchText(result) {
3399
3414
  for (const r of result.results) {
3400
3415
  const checks = r.checks.map((c) => `${c.ok ? "\u2713" : "\u2717"}${c.name}`).join(" ");
3401
3416
  const timing = r.handshakeMs !== void 0 ? ` hs=${r.handshakeMs}ms${r.promptMs !== void 0 ? ` prompt=${r.promptMs}ms` : ""}` : "";
3402
- lines.push(` ${icon(r.status)} ${r.agentId.padEnd(16)} ${r.status.toUpperCase().padEnd(7)} ${checks}${timing}`);
3417
+ lines.push(
3418
+ ` ${icon(r.status)} ${r.agentId.padEnd(16)} ${r.status.toUpperCase().padEnd(7)} ${checks}${timing}`
3419
+ );
3403
3420
  if (r.agentInfo) lines.push(` agent: ${r.agentInfo.name} ${r.agentInfo.version}`);
3404
3421
  if (r.sample) lines.push(` reply: ${r.sample}`);
3405
3422
  if (r.reason) lines.push(` reason: ${r.reason}`);
@@ -3411,6 +3428,9 @@ function renderAcpBenchText(result) {
3411
3428
  );
3412
3429
  return lines.join("\n");
3413
3430
  }
3431
+ async function removeBenchFile(filePath, remove = fsp2.rm) {
3432
+ await remove(filePath, { force: true }).catch(() => void 0);
3433
+ }
3414
3434
 
3415
3435
  // src/registry/agents.catalog.ts
3416
3436
  var AGENTS_CATALOG = [
@@ -3799,16 +3819,8 @@ function mapACPKind(acpKind) {
3799
3819
  }
3800
3820
  }
3801
3821
  function isRetryable(kind) {
3802
- switch (kind) {
3803
- case "provider_5xx":
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
- }
3822
+ void kind;
3823
+ return false;
3812
3824
  }
3813
3825
  var REGISTRY_ID_ALIASES = {
3814
3826
  "claude-code": "claude-acp",
@@ -3948,11 +3960,15 @@ async function probeAcpAgents(opts) {
3948
3960
  }
3949
3961
  await runPhase(local, opts.concurrency ?? 4, localTimeout);
3950
3962
  await runPhase(pkg, 2, pkgTimeout);
3951
- return ids.map((id) => byId.get(id) ?? { id, ok: false, ms: 0, error: "not probed" });
3963
+ return ids.map((id) => byId.get(id));
3952
3964
  }
3953
3965
 
3966
+ // src/integration/ensemble-runner.ts
3967
+ import { SubagentBudget } from "@wrongstack/core/coordination";
3968
+
3954
3969
  // src/registry/ensemble-registry.ts
3955
3970
  import { spawn as spawn2 } from "node:child_process";
3971
+ import { treeKill as treeKill2 } from "@wrongstack/core/utils/tree-kill";
3956
3972
  var PROBE_TIMEOUT_MS = 5e3;
3957
3973
  var PROBE_CACHE_MS = 5e3;
3958
3974
  var MAX_PARALLEL_PROBES = 4;
@@ -3976,7 +3992,7 @@ async function probeWithBound(items, worker, limit) {
3976
3992
  await Promise.all(runners);
3977
3993
  return results;
3978
3994
  }
3979
- async function defaultProbe(desc, timeoutMs) {
3995
+ async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = process.platform) {
3980
3996
  const start = Date.now();
3981
3997
  return new Promise((resolve3) => {
3982
3998
  let settled = false;
@@ -3986,7 +4002,9 @@ async function defaultProbe(desc, timeoutMs) {
3986
4002
  if (settled) return;
3987
4003
  settled = true;
3988
4004
  try {
3989
- child.kill();
4005
+ if (child.exitCode === null && child.signalCode === null) {
4006
+ treeKill2(child);
4007
+ }
3990
4008
  } catch {
3991
4009
  }
3992
4010
  resolve3(result);
@@ -3994,8 +4012,8 @@ async function defaultProbe(desc, timeoutMs) {
3994
4012
  let child;
3995
4013
  try {
3996
4014
  const probeArgs = [...desc.probe.args ?? []];
3997
- const shim = process.platform === "win32" ? buildWin32CmdShimInvocation(desc.probe.command, probeArgs) : null;
3998
- child = spawn2(shim?.command ?? desc.probe.command, shim?.args ?? probeArgs, {
4015
+ const shim = platform === "win32" ? buildWin32CmdShimInvocation(desc.probe.command, probeArgs) : null;
4016
+ child = spawnProcess(shim?.command ?? desc.probe.command, shim?.args ?? probeArgs, {
3999
4017
  stdio: ["ignore", "pipe", "pipe"],
4000
4018
  windowsHide: true,
4001
4019
  ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
@@ -4028,7 +4046,7 @@ async function defaultProbe(desc, timeoutMs) {
4028
4046
  clearTimeout(timer);
4029
4047
  const durationMs = Date.now() - start;
4030
4048
  const out = (stdout + stderr).trim();
4031
- const isWindowsShellMiss = process.platform === "win32" && out.toLowerCase().includes("is not recognized");
4049
+ const isWindowsShellMiss = platform === "win32" && out.toLowerCase().includes("is not recognized");
4032
4050
  if (isWindowsShellMiss) {
4033
4051
  finish({
4034
4052
  ok: false,
@@ -4040,7 +4058,7 @@ async function defaultProbe(desc, timeoutMs) {
4040
4058
  if (out.length > 0) {
4041
4059
  finish({
4042
4060
  ok: true,
4043
- version: out.split("\n")[0]?.trim() ?? "",
4061
+ version: out.split("\n")[0].trim(),
4044
4062
  path: desc.probe.command,
4045
4063
  durationMs
4046
4064
  });
@@ -4116,7 +4134,6 @@ var EnsembleRegistry = class {
4116
4134
  };
4117
4135
 
4118
4136
  // src/integration/ensemble-runner.ts
4119
- import { SubagentBudget } from "@wrongstack/core/coordination";
4120
4137
  var DEFAULT_MAX_CONCURRENCY = 4;
4121
4138
  async function mapBound(items, worker, limit) {
4122
4139
  const results = new Array(items.length);
@@ -4259,10 +4276,7 @@ async function runEnsemble(opts) {
4259
4276
  }
4260
4277
  runnable.push({ id, cmd });
4261
4278
  }
4262
- const concurrency = Math.max(
4263
- 1,
4264
- opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY
4265
- );
4279
+ const concurrency = Math.max(1, opts.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY);
4266
4280
  await mapBound(
4267
4281
  runnable,
4268
4282
  async ({ id, cmd }) => {
@@ -4311,15 +4325,11 @@ function renderEnsembleText(result) {
4311
4325
  );
4312
4326
  break;
4313
4327
  case "failed":
4314
- lines.push(
4315
- `[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}`
4316
- );
4328
+ lines.push(`[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}`);
4317
4329
  lines.push(`[${r.agentId}] failed ${r.durationMs}ms`);
4318
4330
  break;
4319
4331
  case "cancelled":
4320
- lines.push(
4321
- `[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}`
4322
- );
4332
+ lines.push(`[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}`);
4323
4333
  lines.push(`[${r.agentId}] cancelled ${r.durationMs}ms`);
4324
4334
  break;
4325
4335
  case "skipped":
@@ -4381,15 +4391,14 @@ async function runOneAcpTask(opts) {
4381
4391
 
4382
4392
  // src/registry/acp-registry-fetch.ts
4383
4393
  var ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
4384
- function currentPlatformKey() {
4385
- const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux";
4386
- const arch = process.arch === "arm64" ? "aarch64" : process.arch === "x64" ? "x86_64" : process.arch;
4394
+ function currentPlatformKey(platform = process.platform, architecture = process.arch) {
4395
+ const os = platform === "win32" ? "windows" : platform === "darwin" ? "darwin" : "linux";
4396
+ const arch = architecture === "arm64" ? "aarch64" : architecture === "x64" ? "x86_64" : architecture;
4387
4397
  return `${os}-${arch}`;
4388
4398
  }
4389
4399
  function basename(cmd) {
4390
4400
  const cleaned = cmd.replace(/^\.\//, "").replace(/\\/g, "/");
4391
- const parts = cleaned.split("/");
4392
- return parts[parts.length - 1] || cleaned;
4401
+ return cleaned.slice(cleaned.lastIndexOf("/") + 1);
4393
4402
  }
4394
4403
  function mapRegistryEntry(entry, platformKey = currentPlatformKey()) {
4395
4404
  if (!entry || typeof entry.id !== "string" || entry.id.length === 0) return null;