commonswarm 0.1.26 → 0.1.28

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 (2) hide show
  1. package/cswarm.cjs +492 -147
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -28443,7 +28443,7 @@ function parseSignalRecord(value) {
28443
28443
  throw new Error("signal read returned a malformed row");
28444
28444
  }
28445
28445
  const row = value;
28446
- if (typeof row.from_kind !== "string" || !["user", "agent"].includes(row.from_kind) || typeof row.kind !== "string" || !SIGNAL_KINDS.has(row.kind) || typeof row.body !== "string" || row.body.length < 1 || row.body.length > 2e3 || !(row.about === null || typeof row.about === "string" && row.about.length <= 500)) {
28446
+ if (typeof row.from_kind !== "string" || !["user", "agent"].includes(row.from_kind) || typeof row.kind !== "string" || !SIGNAL_KINDS.has(row.kind) || typeof row.body !== "string" || row.body.length < 1 || row.body.length > 8e3 || !(row.about === null || typeof row.about === "string" && row.about.length <= 500)) {
28447
28447
  throw new Error("signal read returned malformed signal data");
28448
28448
  }
28449
28449
  let senderOwnerRelation = "unknown";
@@ -28550,12 +28550,12 @@ function parseRetryAfterMs(header, nowMs = Date.now()) {
28550
28550
  if (!Number.isFinite(when)) return null;
28551
28551
  return Math.max(0, Math.min(when - nowMs, SIGNAL_FOLLOW_BACKOFF_MAX_MS));
28552
28552
  }
28553
- function throwSignalHttp(response, body) {
28553
+ function throwSignalHttp(response, body, failure = "signal read failed") {
28554
28554
  const status = response.status;
28555
28555
  const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
28556
28556
  const envelope = parseServerErrorEnvelope(body);
28557
28557
  const error = new Error(
28558
- describeServerError(`signal read failed (HTTP ${status})`, envelope)
28558
+ describeServerError(`${failure} (HTTP ${status})`, envelope)
28559
28559
  );
28560
28560
  plainHttpStatus.set(error, status);
28561
28561
  plainHttpRetryAfterMs.set(error, retryAfterMs);
@@ -28912,7 +28912,7 @@ async function readAgentSignalDirectory(target2, token, workspaceId2, fetcherOrO
28912
28912
  }
28913
28913
  const { response, body } = result;
28914
28914
  if (!response.ok) {
28915
- throw new Error(`member read failed (HTTP ${response.status})`);
28915
+ throwSignalHttp(response, body, "member read failed");
28916
28916
  }
28917
28917
  if (!body || typeof body !== "object" || Array.isArray(body)) {
28918
28918
  throw new Error("member read returned malformed JSON");
@@ -29130,6 +29130,37 @@ function postSignalTargets(recipient) {
29130
29130
  };
29131
29131
  }
29132
29132
  var ASK_WAIT_TIMEOUT_MESSAGE = "Ask shared. No reply arrived before the wait ended; the ask remains live. Check for a reply with: cswarm inbox";
29133
+ function askFailureDetail(error) {
29134
+ const readHttp = followHttpDetails(error);
29135
+ if (readHttp !== null) {
29136
+ return {
29137
+ detail: describeServerError(
29138
+ `HTTP ${readHttp.status}`,
29139
+ followErrorEnvelope(error)
29140
+ ),
29141
+ serverError: true
29142
+ };
29143
+ }
29144
+ if (error instanceof CommandHttpError) {
29145
+ return {
29146
+ detail: `HTTP ${error.status}; server detail: ${error.message.replace(/^signal read failed \(HTTP \d+\)(?:: )?/, "").slice(0, 240)}`,
29147
+ serverError: true
29148
+ };
29149
+ }
29150
+ return {
29151
+ detail: error instanceof Error ? error.message.slice(0, 300) : "unknown error",
29152
+ serverError: false
29153
+ };
29154
+ }
29155
+ function askCreateFailureMessage(workspaceId2, error) {
29156
+ const failure = askFailureDetail(error);
29157
+ const cause = failure.serverError ? `server error before it was confirmed: ${failure.detail}` : `request failure before it was confirmed: ${failure.detail}`;
29158
+ return `Your message may not have been posted (${cause}). Check with: cswarm feed --workspace-id ${workspaceId2} \u2014 and resend if it is not there.`;
29159
+ }
29160
+ function askReplyReadFailureMessage(workspaceId2, error) {
29161
+ const failure = askFailureDetail(error);
29162
+ return `Your message was posted, but its reply could not be fetched (${failure.detail}). Do not resend this ask. Check with: cswarm inbox --workspace-id ${workspaceId2}`;
29163
+ }
29133
29164
  function renderSignals(signals, options) {
29134
29165
  if (signals.length === 0) {
29135
29166
  return [
@@ -29539,11 +29570,15 @@ var ACP_MAX_ACCUMULATED_TEXT_CHARS = 4194304;
29539
29570
  var ACP_DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
29540
29571
  var ACP_VERSION_CHECK_TIMEOUT_MS = 5e3;
29541
29572
  var ACP_CANARY_TIMEOUT_MS = 3e4;
29542
- var GROK_MEASURED_VERSION = "0.2.117";
29543
- var OPENCODE_MEASURED_VERSION = "1.18.10";
29544
- var CLAUDE_ACP_MEASURED_VERSION = "0.64.2";
29573
+ var GROK_MIN_VERSION = "0.2.117";
29574
+ var GROK_LAST_MEASURED_VERSION = "0.2.117";
29575
+ var OPENCODE_MIN_VERSION = "1.18.10";
29576
+ var OPENCODE_LAST_MEASURED_VERSION = "1.18.10";
29577
+ var CLAUDE_ACP_MIN_VERSION = "0.64.2";
29578
+ var CLAUDE_ACP_LAST_MEASURED_VERSION = "0.64.2";
29545
29579
  var CLAUDE_PERMISSION_MODE_ID = "default";
29546
- var CODEX_ACP_MEASURED_VERSION = "1.1.9";
29580
+ var CODEX_ACP_MIN_VERSION = "1.1.9";
29581
+ var CODEX_ACP_LAST_MEASURED_VERSION = "1.1.9";
29547
29582
  var CODEX_PERMISSION_MODE_ID = "read-only";
29548
29583
  var ACP_PROTOCOL_VERSION = 1;
29549
29584
  var OPENCODE_FORCED_PERMISSION_TOOLS = [
@@ -29758,11 +29793,32 @@ var AcpTransportError = class extends AcpHostError {
29758
29793
  cause;
29759
29794
  };
29760
29795
  var AcpVersionError = class extends AcpHostError {
29761
- constructor(message) {
29762
- super("version_refused", message);
29796
+ constructor(message, code = "version_refused") {
29797
+ super(code, message);
29763
29798
  this.name = "AcpVersionError";
29764
29799
  }
29765
29800
  };
29801
+ var AcpVersionParseError = class extends AcpVersionError {
29802
+ constructor(message) {
29803
+ super(message, "version_unparseable");
29804
+ this.name = "AcpVersionParseError";
29805
+ }
29806
+ };
29807
+ var AcpVersionBelowFloorError = class extends AcpVersionError {
29808
+ constructor(provider, minimum, actual) {
29809
+ super(
29810
+ `refusing ${provider} ${actual}; CommonSwarm requires ${minimum} or newer`,
29811
+ "version_below_floor"
29812
+ );
29813
+ this.provider = provider;
29814
+ this.minimum = minimum;
29815
+ this.actual = actual;
29816
+ this.name = "AcpVersionBelowFloorError";
29817
+ }
29818
+ provider;
29819
+ minimum;
29820
+ actual;
29821
+ };
29766
29822
  var AcpPermissionCanaryError = class extends AcpHostError {
29767
29823
  constructor(message) {
29768
29824
  super("permission_canary_failed", message);
@@ -29800,11 +29856,26 @@ var AcpTransport = class extends import_node_events.EventEmitter {
29800
29856
  this.writable = options.writable;
29801
29857
  this.handlers = options.handlers ?? {};
29802
29858
  this.requestTimeoutMs = options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS;
29859
+ const readableEndGraceMs = Math.max(0, options.readableEndGraceMs ?? 0);
29860
+ let readableEndTimer = null;
29803
29861
  options.readable.on("data", (chunk) => {
29804
29862
  this.onData(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
29805
29863
  });
29806
29864
  options.readable.on("end", () => {
29807
- this.failAll(new AcpChildExitError(this.childExit?.code ?? null, this.childExit?.signal ?? null));
29865
+ const fail = () => {
29866
+ readableEndTimer = null;
29867
+ this.failAll(
29868
+ new AcpChildExitError(
29869
+ this.childExit?.code ?? null,
29870
+ this.childExit?.signal ?? null
29871
+ )
29872
+ );
29873
+ };
29874
+ if (readableEndGraceMs > 0 && options.onChildExit) {
29875
+ readableEndTimer = setTimeout(fail, readableEndGraceMs);
29876
+ } else {
29877
+ fail();
29878
+ }
29808
29879
  });
29809
29880
  options.readable.on("error", (err) => {
29810
29881
  this.failAll(asAcpHostError(err));
@@ -29813,6 +29884,10 @@ var AcpTransport = class extends import_node_events.EventEmitter {
29813
29884
  this.failAll(asAcpHostError(err));
29814
29885
  });
29815
29886
  options.onChildExit?.((code, signal) => {
29887
+ if (readableEndTimer) {
29888
+ clearTimeout(readableEndTimer);
29889
+ readableEndTimer = null;
29890
+ }
29816
29891
  this.childExit = { code, signal };
29817
29892
  this.failAll(new AcpChildExitError(code, signal));
29818
29893
  });
@@ -30633,6 +30708,7 @@ function createBoundTransport(options) {
30633
30708
  writable: options.writable,
30634
30709
  requestTimeoutMs: options.requestTimeoutMs,
30635
30710
  onChildExit: options.onChildExit,
30711
+ readableEndGraceMs: options.readableEndGraceMs,
30636
30712
  handlers: {
30637
30713
  onNotification: (method, params) => {
30638
30714
  options.getSession()?.handleAgentNotification(method, params);
@@ -30648,6 +30724,98 @@ function createBoundTransport(options) {
30648
30724
  });
30649
30725
  }
30650
30726
 
30727
+ // src/host/version.ts
30728
+ var CORE_IDENTIFIER = "(?:0|[1-9]\\d*)";
30729
+ var PRERELEASE_IDENTIFIER = "(?:0|[1-9]\\d*|[A-Za-z-][0-9A-Za-z-]*)";
30730
+ var BUILD_IDENTIFIER = "[0-9A-Za-z-]+";
30731
+ var SEMVER_SOURCE = `${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}(?:-${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*)?(?:\\+${BUILD_IDENTIFIER}(?:\\.${BUILD_IDENTIFIER})*)?`;
30732
+ var SEMVER_RE = new RegExp(`^${SEMVER_SOURCE}$`);
30733
+ function parseSemVer(value) {
30734
+ if (!SEMVER_RE.test(value)) return null;
30735
+ const withoutBuild = value.split("+", 1)[0];
30736
+ const dash = withoutBuild.indexOf("-");
30737
+ const coreText = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
30738
+ const prereleaseText = dash === -1 ? null : withoutBuild.slice(dash + 1);
30739
+ const coreParts = coreText.split(".");
30740
+ if (coreParts.length !== 3) return null;
30741
+ return {
30742
+ core: [BigInt(coreParts[0]), BigInt(coreParts[1]), BigInt(coreParts[2])],
30743
+ prerelease: prereleaseText === null ? null : prereleaseText.split(".")
30744
+ };
30745
+ }
30746
+ function compareSemVer(left, right) {
30747
+ const a = parseSemVer(left);
30748
+ const b2 = parseSemVer(right);
30749
+ if (!a || !b2) {
30750
+ throw new AcpVersionParseError(
30751
+ `cannot compare invalid semantic versions: ${JSON.stringify(left)} and ${JSON.stringify(right)}`
30752
+ );
30753
+ }
30754
+ for (let index = 0; index < 3; index += 1) {
30755
+ if (a.core[index] < b2.core[index]) return -1;
30756
+ if (a.core[index] > b2.core[index]) return 1;
30757
+ }
30758
+ if (a.prerelease === null && b2.prerelease === null) return 0;
30759
+ if (a.prerelease === null) return 1;
30760
+ if (b2.prerelease === null) return -1;
30761
+ const length = Math.max(a.prerelease.length, b2.prerelease.length);
30762
+ for (let index = 0; index < length; index += 1) {
30763
+ const leftPart = a.prerelease[index];
30764
+ const rightPart = b2.prerelease[index];
30765
+ if (leftPart === void 0) return -1;
30766
+ if (rightPart === void 0) return 1;
30767
+ if (leftPart === rightPart) continue;
30768
+ const leftNumeric = /^\d+$/.test(leftPart);
30769
+ const rightNumeric = /^\d+$/.test(rightPart);
30770
+ if (leftNumeric && rightNumeric) {
30771
+ return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1;
30772
+ }
30773
+ if (leftNumeric) return -1;
30774
+ if (rightNumeric) return 1;
30775
+ return leftPart < rightPart ? -1 : 1;
30776
+ }
30777
+ return 0;
30778
+ }
30779
+ function parseProviderVersionOutput(stdout, productPattern, allowBare = true) {
30780
+ const lines = stdout.split(/\r?\n/);
30781
+ for (const line of lines) {
30782
+ const pattern = new RegExp(productPattern.source, productPattern.flags.replace("g", ""));
30783
+ const product = pattern.exec(line);
30784
+ if (!product) continue;
30785
+ const after = line.slice(product.index + product[0].length);
30786
+ const afterMatch = new RegExp(
30787
+ `^\\s+(${SEMVER_SOURCE})(?=$|\\s|\\()`
30788
+ ).exec(after);
30789
+ if (afterMatch?.[1]) return afterMatch[1];
30790
+ const before = line.slice(0, product.index);
30791
+ const beforeMatch = new RegExp(`(${SEMVER_SOURCE})\\s*\\($`).exec(before);
30792
+ if (beforeMatch?.[1]) return beforeMatch[1];
30793
+ }
30794
+ if (!allowBare) return null;
30795
+ for (const line of lines) {
30796
+ const trimmed = line.trim();
30797
+ const match = new RegExp(`^(${SEMVER_SOURCE})$`).exec(trimmed);
30798
+ if (match?.[1]) return match[1];
30799
+ }
30800
+ return null;
30801
+ }
30802
+ function assertProviderVersionFloor(options) {
30803
+ if (compareSemVer(options.version, options.minimumVersion) < 0) {
30804
+ throw new AcpVersionBelowFloorError(
30805
+ options.provider,
30806
+ options.minimumVersion,
30807
+ options.version
30808
+ );
30809
+ }
30810
+ if (compareSemVer(options.version, options.lastMeasuredVersion) > 0) {
30811
+ options.onNewerVersion?.({
30812
+ provider: options.provider,
30813
+ runningVersion: options.version,
30814
+ lastMeasuredVersion: options.lastMeasuredVersion
30815
+ });
30816
+ }
30817
+ }
30818
+
30651
30819
  // src/host/opencode.ts
30652
30820
  var OPENCODE_HOME_OWNER_FILE = ".cswarm-opencode-owner.json";
30653
30821
  var MAX_OPENCODE_AUTH_BYTES = 256 * 1024;
@@ -30756,11 +30924,11 @@ async function releaseOpenCodeHome(home, instanceId) {
30756
30924
  }
30757
30925
  }
30758
30926
  function parseOpenCodeVersionOutput(stdout) {
30759
- const m = stdout.match(/\b(\d+\.\d+\.\d+)\b/);
30760
- return m?.[1] ?? null;
30927
+ return parseProviderVersionOutput(stdout, /\bopencode\b/i);
30761
30928
  }
30762
- async function assertOpenCodeMeasuredVersion(executable, options) {
30763
- const expected = options?.expected ?? OPENCODE_MEASURED_VERSION;
30929
+ async function assertOpenCodeVersionFloor(executable, options) {
30930
+ const minimumVersion = options?.minimumVersion ?? OPENCODE_MIN_VERSION;
30931
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? OPENCODE_LAST_MEASURED_VERSION;
30764
30932
  const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
30765
30933
  const env = sanitizeChildEnv(options?.env ?? process.env);
30766
30934
  const stdout = await new Promise((resolve, reject) => {
@@ -30783,15 +30951,17 @@ async function assertOpenCodeMeasuredVersion(executable, options) {
30783
30951
  });
30784
30952
  const version3 = parseOpenCodeVersionOutput(stdout);
30785
30953
  if (!version3) {
30786
- throw new AcpVersionError(
30954
+ throw new AcpVersionParseError(
30787
30955
  `could not parse opencode version from: ${stdout.trim().slice(0, 200)}`
30788
30956
  );
30789
30957
  }
30790
- if (version3 !== expected) {
30791
- throw new AcpVersionError(
30792
- `refusing opencode ${version3}; host core is measured for ${expected} only`
30793
- );
30794
- }
30958
+ assertProviderVersionFloor({
30959
+ provider: "opencode",
30960
+ version: version3,
30961
+ minimumVersion,
30962
+ lastMeasuredVersion,
30963
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
30964
+ });
30795
30965
  return version3;
30796
30966
  }
30797
30967
  function buildOpenCodeAcpArgs() {
@@ -31158,8 +31328,9 @@ async function openOpenCodeAcpSession(options) {
31158
31328
  };
31159
31329
  try {
31160
31330
  if (!options.skipVersionCheck) {
31161
- await assertOpenCodeMeasuredVersion(executable, {
31162
- env
31331
+ await assertOpenCodeVersionFloor(executable, {
31332
+ env,
31333
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
31163
31334
  });
31164
31335
  }
31165
31336
  if (!options.skipConfigProbe) {
@@ -31256,6 +31427,8 @@ var import_node_fs4 = require("node:fs");
31256
31427
  var import_node_path6 = require("node:path");
31257
31428
  var CHILD_EXIT_WAIT_MS2 = 3e3;
31258
31429
  var CHILD_KILL_WAIT_MS2 = 1e3;
31430
+ var STDERR_EXIT_GRACE_MS = 100;
31431
+ var READABLE_END_GRACE_MS = STDERR_EXIT_GRACE_MS + 50;
31259
31432
  var WINDOWS_NPM_SHIM_MAX_BYTES = 64 * 1024;
31260
31433
  var WINDOWS_NPM_ENTRYPOINT = [
31261
31434
  "node_modules",
@@ -31264,6 +31437,30 @@ var WINDOWS_NPM_ENTRYPOINT = [
31264
31437
  "dist",
31265
31438
  "index.js"
31266
31439
  ];
31440
+ function isPackagedClaudeBridge(executable) {
31441
+ const normalized = executable.replaceAll("\\", "/");
31442
+ return normalized.endsWith(
31443
+ "/node_modules/@agentclientprotocol/claude-agent-acp/dist/index.js"
31444
+ );
31445
+ }
31446
+ function resolvePackagedClaudeBridge(pathEnv, platform = process.platform) {
31447
+ const pathValue = pathEnv ?? process.env.PATH ?? "";
31448
+ const names = platform === "win32" ? ["claude-agent-acp.cmd"] : ["claude-agent-acp"];
31449
+ for (const dir of pathValue.split(import_node_path6.delimiter)) {
31450
+ if (!dir) continue;
31451
+ for (const name of names) {
31452
+ try {
31453
+ const candidate = resolvedClaudeCandidate((0, import_node_path6.join)(dir, name), platform);
31454
+ if (isPackagedClaudeBridge(candidate)) return candidate;
31455
+ } catch {
31456
+ }
31457
+ }
31458
+ }
31459
+ throw new AcpHostError(
31460
+ "executable_missing",
31461
+ "packaged claude-agent-acp executable not found; install @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2)"
31462
+ );
31463
+ }
31267
31464
  function resolveWindowsNpmShim(shim) {
31268
31465
  let source;
31269
31466
  try {
@@ -31333,15 +31530,16 @@ function buildClaudeLaunch(executable, args, platform = process.platform) {
31333
31530
  return platform === "win32" && (0, import_node_path6.extname)(executable).toLowerCase() === ".js" ? { command: process.execPath, args: [executable, ...args] } : { command: executable, args: [...args] };
31334
31531
  }
31335
31532
  function parseClaudeVersionOutput(stdout) {
31336
- const match = stdout.trim().match(/^(\d+\.\d+\.\d+)$/);
31337
- return match?.[1] ?? null;
31533
+ return parseProviderVersionOutput(stdout, /\bclaude-agent-acp\b/i);
31338
31534
  }
31339
- async function assertClaudeMeasuredVersion(executable, options) {
31340
- const expected = options?.expected ?? CLAUDE_ACP_MEASURED_VERSION;
31535
+ function parseClaudeCodeVersionOutput(stdout) {
31536
+ return parseProviderVersionOutput(stdout, /\bClaude Code\b/i, false);
31537
+ }
31538
+ async function readClaudeVersionOutput(executable, options) {
31341
31539
  const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
31342
31540
  const env = options?.env ?? sanitizeChildEnv(process.env);
31343
31541
  const launch = buildClaudeLaunch(executable, ["--version"], options?.platform);
31344
- const stdout = await new Promise((resolve, reject) => {
31542
+ return await new Promise((resolve, reject) => {
31345
31543
  (0, import_node_child_process4.execFile)(
31346
31544
  launch.command,
31347
31545
  launch.args,
@@ -31359,24 +31557,35 @@ async function assertClaudeMeasuredVersion(executable, options) {
31359
31557
  }
31360
31558
  );
31361
31559
  });
31560
+ }
31561
+ async function assertClaudeVersionFloor(executable, options) {
31562
+ const minimumVersion = options?.minimumVersion ?? CLAUDE_ACP_MIN_VERSION;
31563
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? CLAUDE_ACP_LAST_MEASURED_VERSION;
31564
+ const stdout = await readClaudeVersionOutput(executable, options);
31362
31565
  const version3 = parseClaudeVersionOutput(stdout);
31363
31566
  if (!version3) {
31364
- throw new AcpVersionError(
31567
+ throw new AcpVersionParseError(
31365
31568
  `could not parse claude-agent-acp version from: ${stdout.trim().slice(0, 200)}`
31366
31569
  );
31367
31570
  }
31368
- if (version3 !== expected) {
31369
- throw new AcpVersionError(
31370
- `refusing claude-agent-acp ${version3}; host core is measured for ${expected} only`
31371
- );
31372
- }
31571
+ assertProviderVersionFloor({
31572
+ provider: "claude-agent-acp",
31573
+ version: version3,
31574
+ minimumVersion,
31575
+ lastMeasuredVersion,
31576
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
31577
+ });
31373
31578
  return version3;
31374
31579
  }
31375
31580
  function buildClaudeAcpArgs() {
31376
31581
  return [];
31377
31582
  }
31378
- function buildClaudeChildEnv(parent) {
31379
- return sanitizeChildEnv(parent);
31583
+ function buildClaudeChildEnv(parent, claudeCodeExecutable) {
31584
+ const env = sanitizeChildEnv(parent);
31585
+ if (claudeCodeExecutable) {
31586
+ env.CLAUDE_CODE_EXECUTABLE = claudeCodeExecutable;
31587
+ }
31588
+ return env;
31380
31589
  }
31381
31590
  function waitForChildExit2(child, timeoutMs) {
31382
31591
  if (child.exitCode !== null || child.signalCode !== null) {
@@ -31419,13 +31628,56 @@ async function openClaudeAcpSession(options) {
31419
31628
  );
31420
31629
  }
31421
31630
  const pathEnv = parentEnv.PATH;
31422
- const executable = resolveClaudeExecutable(
31423
- options.executable ?? "claude-agent-acp",
31424
- typeof pathEnv === "string" ? pathEnv : void 0
31425
- );
31426
- const env = buildClaudeChildEnv(parentEnv);
31427
- if (!options.skipVersionCheck) {
31428
- await assertClaudeMeasuredVersion(executable, { env });
31631
+ const resolvedPathEnv = typeof pathEnv === "string" ? pathEnv : void 0;
31632
+ if (options.skipVersionCheck && options.executable) {
31633
+ throw new AcpHostError(
31634
+ "version_check_required",
31635
+ "skipVersionCheck cannot classify an explicit Claude executable"
31636
+ );
31637
+ }
31638
+ const requestedExecutable = options.executable ? resolveClaudeExecutable(options.executable, resolvedPathEnv) : null;
31639
+ const baseEnv = buildClaudeChildEnv(parentEnv);
31640
+ let executable;
31641
+ let env = baseEnv;
31642
+ let claudeCodeExecutable;
31643
+ if (requestedExecutable) {
31644
+ const output = await readClaudeVersionOutput(requestedExecutable, {
31645
+ env: baseEnv
31646
+ });
31647
+ const bridgeVersion = parseClaudeVersionOutput(output);
31648
+ if (bridgeVersion) {
31649
+ assertProviderVersionFloor({
31650
+ provider: "claude-agent-acp",
31651
+ version: bridgeVersion,
31652
+ minimumVersion: CLAUDE_ACP_MIN_VERSION,
31653
+ lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
31654
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
31655
+ });
31656
+ executable = requestedExecutable;
31657
+ } else if (parseClaudeCodeVersionOutput(output)) {
31658
+ claudeCodeExecutable = requestedExecutable;
31659
+ executable = resolvePackagedClaudeBridge(resolvedPathEnv);
31660
+ env = buildClaudeChildEnv(parentEnv, claudeCodeExecutable);
31661
+ await assertClaudeVersionFloor(executable, {
31662
+ env,
31663
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
31664
+ });
31665
+ } else {
31666
+ throw new AcpVersionError(
31667
+ `could not identify Claude executable from: ${output.trim().slice(0, 200)}`
31668
+ );
31669
+ }
31670
+ } else {
31671
+ executable = requestedExecutable ?? resolveClaudeExecutable(
31672
+ "claude-agent-acp",
31673
+ resolvedPathEnv
31674
+ );
31675
+ if (!options.skipVersionCheck) {
31676
+ await assertClaudeVersionFloor(executable, {
31677
+ env: baseEnv,
31678
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
31679
+ });
31680
+ }
31429
31681
  }
31430
31682
  if (options.signal?.aborted) {
31431
31683
  throw new AcpHostError(
@@ -31474,25 +31726,33 @@ async function openClaudeAcpSession(options) {
31474
31726
  throw new AcpHostError("spawn_failed", "child missing stdio pipes");
31475
31727
  }
31476
31728
  const stderrTail = attachStderrTailRing(child.stderr);
31477
- if (options.onStderrTail) {
31478
- const deliverTail = options.onStderrTail;
31479
- let tailDelivered = false;
31480
- const publishTail = () => {
31481
- if (tailDelivered) return;
31482
- tailDelivered = true;
31483
- deliverTail(stderrTail.read());
31484
- };
31485
- child.once("exit", publishTail);
31486
- child.once("close", publishTail);
31487
- }
31488
31729
  let sessionRef = null;
31489
31730
  const transport = createBoundTransport({
31490
31731
  readable: child.stdout,
31491
31732
  writable: child.stdin,
31492
31733
  requestTimeoutMs: options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS,
31734
+ readableEndGraceMs: READABLE_END_GRACE_MS,
31493
31735
  getSession: () => sessionRef,
31494
31736
  onChildExit: (handler) => {
31495
- child.on("exit", (code, signal) => handler(code, signal));
31737
+ const observeExit = (code, signal) => {
31738
+ let completed = false;
31739
+ let timer2 = null;
31740
+ const complete = () => {
31741
+ if (completed) return;
31742
+ completed = true;
31743
+ if (timer2) clearTimeout(timer2);
31744
+ child.removeListener("close", complete);
31745
+ options.onStderrTail?.(stderrTail.read());
31746
+ handler(code, signal);
31747
+ };
31748
+ child.once("close", complete);
31749
+ timer2 = setTimeout(complete, STDERR_EXIT_GRACE_MS);
31750
+ };
31751
+ if (child.exitCode !== null || child.signalCode !== null) {
31752
+ observeExit(child.exitCode, child.signalCode);
31753
+ } else {
31754
+ child.once("exit", observeExit);
31755
+ }
31496
31756
  }
31497
31757
  });
31498
31758
  try {
@@ -31617,13 +31877,11 @@ function buildCodexLaunch(executable, args, platform = process.platform) {
31617
31877
  return platform === "win32" && (0, import_node_path7.extname)(executable).toLowerCase() === ".js" ? { command: process.execPath, args: [executable, ...args] } : { command: executable, args: [...args] };
31618
31878
  }
31619
31879
  function parseCodexVersionOutput(stdout) {
31620
- const match = stdout.trim().match(
31621
- /^@agentclientprotocol\/codex-acp (\d+\.\d+\.\d+)$/
31622
- );
31623
- return match?.[1] ?? null;
31880
+ return parseProviderVersionOutput(stdout, /@agentclientprotocol\/codex-acp\b/i);
31624
31881
  }
31625
- async function assertCodexMeasuredVersion(executable, options) {
31626
- const expected = options?.expected ?? CODEX_ACP_MEASURED_VERSION;
31882
+ async function assertCodexVersionFloor(executable, options) {
31883
+ const minimumVersion = options?.minimumVersion ?? CODEX_ACP_MIN_VERSION;
31884
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? CODEX_ACP_LAST_MEASURED_VERSION;
31627
31885
  const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
31628
31886
  const env = options?.env ?? sanitizeChildEnv(process.env);
31629
31887
  const launch = buildCodexLaunch(executable, ["--version"], options?.platform);
@@ -31647,15 +31905,17 @@ async function assertCodexMeasuredVersion(executable, options) {
31647
31905
  });
31648
31906
  const version3 = parseCodexVersionOutput(stdout);
31649
31907
  if (!version3) {
31650
- throw new AcpVersionError(
31908
+ throw new AcpVersionParseError(
31651
31909
  `could not parse codex-acp version from: ${stdout.trim().slice(0, 200)}`
31652
31910
  );
31653
31911
  }
31654
- if (version3 !== expected) {
31655
- throw new AcpVersionError(
31656
- `refusing codex-acp ${version3}; host core is measured for ${expected} only`
31657
- );
31658
- }
31912
+ assertProviderVersionFloor({
31913
+ provider: "codex-acp",
31914
+ version: version3,
31915
+ minimumVersion,
31916
+ lastMeasuredVersion,
31917
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
31918
+ });
31659
31919
  return version3;
31660
31920
  }
31661
31921
  function buildCodexAcpArgs() {
@@ -31711,7 +31971,10 @@ async function openCodexAcpSession(options) {
31711
31971
  );
31712
31972
  const env = buildCodexChildEnv(parentEnv);
31713
31973
  if (!options.skipVersionCheck) {
31714
- await assertCodexMeasuredVersion(executable, { env });
31974
+ await assertCodexVersionFloor(executable, {
31975
+ env,
31976
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
31977
+ });
31715
31978
  }
31716
31979
  if (options.signal?.aborted) {
31717
31980
  throw new AcpHostError(
@@ -32621,10 +32884,12 @@ function resolveGrokExecutable(executable = "grok") {
32621
32884
  return executable;
32622
32885
  }
32623
32886
  function parseGrokVersionOutput(stdout) {
32624
- const m = stdout.match(/\bgrok\s+(\d+\.\d+\.\d+)\b/i);
32625
- return m?.[1] ?? null;
32887
+ return parseProviderVersionOutput(stdout, /\bgrok\b/i);
32626
32888
  }
32627
- async function assertGrokMeasuredVersion(executable, expected = GROK_MEASURED_VERSION, timeoutMs = ACP_VERSION_CHECK_TIMEOUT_MS) {
32889
+ async function assertGrokVersionFloor(executable, options) {
32890
+ const minimumVersion = options?.minimumVersion ?? GROK_MIN_VERSION;
32891
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? GROK_LAST_MEASURED_VERSION;
32892
+ const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
32628
32893
  const stdout = await new Promise((resolve, reject) => {
32629
32894
  (0, import_node_child_process6.execFile)(
32630
32895
  executable,
@@ -32645,15 +32910,17 @@ async function assertGrokMeasuredVersion(executable, expected = GROK_MEASURED_VE
32645
32910
  });
32646
32911
  const version3 = parseGrokVersionOutput(stdout);
32647
32912
  if (!version3) {
32648
- throw new AcpVersionError(
32913
+ throw new AcpVersionParseError(
32649
32914
  `could not parse grok version from: ${stdout.trim().slice(0, 200)}`
32650
32915
  );
32651
32916
  }
32652
- if (version3 !== expected) {
32653
- throw new AcpVersionError(
32654
- `refusing grok ${version3}; host core is measured for ${expected} only`
32655
- );
32656
- }
32917
+ assertProviderVersionFloor({
32918
+ provider: "grok",
32919
+ version: version3,
32920
+ minimumVersion,
32921
+ lastMeasuredVersion,
32922
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
32923
+ });
32657
32924
  return version3;
32658
32925
  }
32659
32926
  function buildGrokAcpArgs(options) {
@@ -32711,7 +32978,9 @@ async function terminateGrokChild(child) {
32711
32978
  async function openGrokAcpSession(options) {
32712
32979
  const executable = resolveGrokExecutable(options.executable ?? "grok");
32713
32980
  if (!options.skipVersionCheck) {
32714
- await assertGrokMeasuredVersion(executable);
32981
+ await assertGrokVersionFloor(executable, {
32982
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
32983
+ });
32715
32984
  }
32716
32985
  const args = buildGrokAcpArgs({
32717
32986
  model: options.model,
@@ -32840,6 +33109,7 @@ var GrokListenerModel = class {
32840
33109
  ...this.options.model ? { model: this.options.model } : {},
32841
33110
  ...this.options.effort ? { effort: this.options.effort } : {},
32842
33111
  ...this.options.env ? { env: this.options.env } : {},
33112
+ ...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
32843
33113
  ...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
32844
33114
  clientName: "cswarm-listener"
32845
33115
  });
@@ -33248,6 +33518,7 @@ var OpenCodeListenerModel = class {
33248
33518
  ...this.options.executable ? { executable: this.options.executable } : {},
33249
33519
  ...this.options.model ? { model: this.options.model } : {},
33250
33520
  ...this.options.env ? { env: this.options.env } : {},
33521
+ ...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
33251
33522
  ...this.options.allowMissingAuth === true ? { allowMissingAuth: true } : {},
33252
33523
  ...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
33253
33524
  clientName: "cswarm-listener"
@@ -33457,6 +33728,7 @@ var ClaudeListenerModel = class {
33457
33728
  permissionCallback,
33458
33729
  ...this.options.executable ? { executable: this.options.executable } : {},
33459
33730
  ...this.options.env ? { env: this.options.env } : {},
33731
+ ...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
33460
33732
  signal: controller.signal,
33461
33733
  ...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
33462
33734
  clientName: "cswarm-listener"
@@ -33625,6 +33897,7 @@ var CodexListenerModel = class {
33625
33897
  permissionCallback,
33626
33898
  ...this.options.executable ? { executable: this.options.executable } : {},
33627
33899
  ...this.options.env ? { env: this.options.env } : {},
33900
+ ...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
33628
33901
  signal: controller.signal,
33629
33902
  ...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
33630
33903
  clientName: "cswarm-listener"
@@ -35518,6 +35791,7 @@ var import_node_net = require("node:net");
35518
35791
  var import_promises9 = require("node:fs/promises");
35519
35792
  var import_node_path15 = require("node:path");
35520
35793
  var UUID_RE13 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
35794
+ var SEMVER_RE2 = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
35521
35795
  var MAX_STATUS_BYTES = 16 * 1024;
35522
35796
  var MAX_CONTROL_BYTES = 8 * 1024;
35523
35797
  var CONTROL_TIMEOUT_MS = 2e3;
@@ -35564,6 +35838,8 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
35564
35838
  "stoppedAt",
35565
35839
  "lastSignalId",
35566
35840
  "lastErrorCode",
35841
+ "providerVersion",
35842
+ "providerLastMeasuredVersion",
35567
35843
  "lastWorkerStderrTail",
35568
35844
  "logPath",
35569
35845
  "deliveryMode",
@@ -35623,7 +35899,7 @@ function parseStatus(raw) {
35623
35899
  const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE13.test(candidate);
35624
35900
  const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
35625
35901
  const nullableTimestamp = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
35626
- if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE13.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE13.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE13.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path15.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
35902
+ if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE13.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE13.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE13.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path15.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
35627
35903
  throw new Error("stored listener status is malformed");
35628
35904
  }
35629
35905
  const routeMode = row.routeMode ?? "worker";
@@ -35640,6 +35916,8 @@ function parseStatus(raw) {
35640
35916
  lastClaimAt: row.lastClaimAt ?? null,
35641
35917
  lastAckAt: row.lastAckAt ?? null,
35642
35918
  lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
35919
+ providerVersion: row.providerVersion ?? null,
35920
+ providerLastMeasuredVersion: row.providerLastMeasuredVersion ?? null,
35643
35921
  routeMode,
35644
35922
  deferOverChars,
35645
35923
  pendingForMainCount: row.pendingForMainCount ?? 0,
@@ -36051,6 +36329,8 @@ async function runListenerSupervisor(options) {
36051
36329
  stoppedAt: null,
36052
36330
  lastSignalId: null,
36053
36331
  lastErrorCode: null,
36332
+ providerVersion: null,
36333
+ providerLastMeasuredVersion: null,
36054
36334
  lastWorkerStderrTail: null,
36055
36335
  deliveryMode: null,
36056
36336
  pendingDeliveryCount: null,
@@ -36120,10 +36400,13 @@ async function runListenerSupervisor(options) {
36120
36400
  };
36121
36401
  const onEvent = (event) => {
36122
36402
  if (event.type === "ready") {
36403
+ const versionNotice = options.getProviderVersionNotice?.() ?? null;
36123
36404
  transition("ready", {
36124
36405
  readyAt: event.ts,
36125
36406
  lastErrorCode: null,
36126
- lastWorkerStderrTail: null
36407
+ lastWorkerStderrTail: null,
36408
+ providerVersion: versionNotice?.runningVersion ?? null,
36409
+ providerLastMeasuredVersion: versionNotice?.lastMeasuredVersion ?? null
36127
36410
  });
36128
36411
  log({ ts: event.ts, event: "listener_ready" });
36129
36412
  return;
@@ -36321,7 +36604,9 @@ async function runListenerSupervisor(options) {
36321
36604
  transition("starting", {
36322
36605
  readyAt: null,
36323
36606
  lastErrorCode: restartCode,
36324
- lastWorkerStderrTail: restartStderrTail
36607
+ lastWorkerStderrTail: restartStderrTail,
36608
+ providerVersion: null,
36609
+ providerLastMeasuredVersion: null
36325
36610
  });
36326
36611
  await restartSleep(delayMs, controller.signal);
36327
36612
  if (controller.signal.aborted) {
@@ -37850,8 +38135,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
37850
38135
  AGENT_CREDENTIAL_MESSAGE_D088
37851
38136
  ];
37852
38137
  function packageVersion() {
37853
- if ("0.1.26".length > 0) {
37854
- return "0.1.26";
38138
+ if ("0.1.28".length > 0) {
38139
+ return "0.1.28";
37855
38140
  }
37856
38141
  try {
37857
38142
  const value = JSON.parse(
@@ -37967,8 +38252,8 @@ Usage:
37967
38252
  cswarm status [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
37968
38253
  cswarm members [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
37969
38254
  cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--about <ref>] [--until <dur>] [--json]
37970
- cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--to <member|agent>] [--about <ref>] [--until <dur>] [--json]
37971
- cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--to <member|agent>] [--about <ref>] [--until <dur>] [--wait <seconds>] [--json]
38255
+ cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--to <member|agent>] [--about <ref>] [--until <dur>] [--json] # text: 1..8000 characters
38256
+ cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--to <member|agent>] [--about <ref>] [--until <dur>] [--wait <seconds>] [--json] # text: 1..8000 characters
37972
38257
  cswarm reply <signal-id> "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--until <dur>] [--json]
37973
38258
  cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--about <ref>] [--kind <kind>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
37974
38259
  cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
@@ -38034,7 +38319,7 @@ Signals (intention sharing) accept the same credential selection. Agent mode
38034
38319
  never opens a browser or infers a human's saved workspace. Durations use a whole
38035
38320
  number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
38036
38321
  Place -- before signal text that itself begins with -- to stop option parsing.
38037
- Signal text is at most 2000 characters and --about at most 500; a longer body is
38322
+ Signal text is at most 8000 characters and --about at most 500; a longer body is
38038
38323
  refused locally before any network call, so compose within the limit.
38039
38324
 
38040
38325
  listen start --turn-budget bounds ONE worker prompt turn (default 10m): how long
@@ -39518,13 +39803,18 @@ function resolveTurnBudgetOrDefer(configuredBudgetMs, credentialExpiresAt, nowMs
39518
39803
  }
39519
39804
  return clampTurnBudgetToCredential(configuredBudgetMs, credentialExpiresAt, nowMs);
39520
39805
  }
39521
- var SIGNAL_BODY_MAX = 2e3;
39806
+ var SIGNAL_BODY_MAX = 8e3;
39522
39807
  var SIGNAL_ABOUT_MAX = 500;
39523
39808
  function signalText(value, label) {
39524
39809
  const maximum = label === "body" ? SIGNAL_BODY_MAX : SIGNAL_ABOUT_MAX;
39525
39810
  if (value.length < (label === "body" ? 1 : 0) || value.length > maximum) {
39811
+ if (label === "body") {
39812
+ throw new Error(
39813
+ `signal text is ${value.length} characters; the maximum is ${maximum}`
39814
+ );
39815
+ }
39526
39816
  throw new Error(
39527
- `${label === "body" ? "signal text" : "--about"} must be ${label === "body" ? "1.." : "at most "}${maximum} characters`
39817
+ `--about must be at most ${maximum} characters`
39528
39818
  );
39529
39819
  }
39530
39820
  return value;
@@ -39666,10 +39956,25 @@ async function runPostSignal(args, kind) {
39666
39956
  validateHumanWorkspace: true
39667
39957
  });
39668
39958
  const toSelector = allowTo ? args.optional("to") : void 0;
39669
- const recipient = toSelector === void 0 ? null : resolveSignalRecipient(
39670
- toSelector,
39671
- await signalDirectory(cloud, credential.selectedWorkspace, credential)
39672
- );
39959
+ let recipient = null;
39960
+ if (toSelector !== void 0) {
39961
+ let directory;
39962
+ try {
39963
+ directory = await signalDirectory(
39964
+ cloud,
39965
+ credential.selectedWorkspace,
39966
+ credential
39967
+ );
39968
+ } catch (error) {
39969
+ if (kind === "ask") {
39970
+ throw new Error(
39971
+ askCreateFailureMessage(credential.selectedWorkspace, error)
39972
+ );
39973
+ }
39974
+ throw error;
39975
+ }
39976
+ recipient = resolveSignalRecipient(toSelector, directory);
39977
+ }
39673
39978
  if (waitSeconds !== void 0 && recipient === null) {
39674
39979
  throw new Error(
39675
39980
  "ask --wait requires --to with a direct member or agent recipient"
@@ -39684,21 +39989,38 @@ async function runPostSignal(args, kind) {
39684
39989
  about: args.optional("about") === void 0 ? null : signalText(args.required("about"), "about"),
39685
39990
  ...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
39686
39991
  };
39687
- const result = await postSignalCommand(cloud, credential, command2);
39992
+ let result;
39993
+ try {
39994
+ result = await postSignalCommand(cloud, credential, command2);
39995
+ } catch (error) {
39996
+ if (kind === "ask") {
39997
+ throw new Error(
39998
+ askCreateFailureMessage(credential.selectedWorkspace, error)
39999
+ );
40000
+ }
40001
+ throw error;
40002
+ }
39688
40003
  const signal = result.response.signal;
39689
40004
  if (waitSeconds !== void 0) {
39690
40005
  const credentialForRead = signalCredentialOf(credential);
39691
40006
  const deadlineMs = waitDeadlineMs(waitSeconds);
39692
- const waitResult = await pollForSignals({
39693
- deadlineMs,
39694
- read: () => readSignals(cloud, credentialForRead, {
39695
- workspaceId: credential.selectedWorkspace,
39696
- inbox: true,
39697
- in_reply_to: signal.id,
39698
- includeStale: false,
39699
- limit: 1
39700
- }, { deadlineMs })
39701
- });
40007
+ let waitResult;
40008
+ try {
40009
+ waitResult = await pollForSignals({
40010
+ deadlineMs,
40011
+ read: () => readSignals(cloud, credentialForRead, {
40012
+ workspaceId: credential.selectedWorkspace,
40013
+ inbox: true,
40014
+ in_reply_to: signal.id,
40015
+ includeStale: false,
40016
+ limit: 1
40017
+ }, { deadlineMs })
40018
+ });
40019
+ } catch (error) {
40020
+ throw new Error(
40021
+ askReplyReadFailureMessage(credential.selectedWorkspace, error)
40022
+ );
40023
+ }
39702
40024
  const reply = waitResult.signals[0] ?? null;
39703
40025
  if (args.has("json")) {
39704
40026
  printJson(askWaitJsonPayload(signal, reply, waitResult.timedOut));
@@ -40153,7 +40475,7 @@ function listenerModelLabel(provider) {
40153
40475
  }
40154
40476
  function listenerProvider(args) {
40155
40477
  const provider = args.optional("provider");
40156
- const hints = "supported providers: grok \u2014 install Grok CLI 0.2.117 and run grok login; opencode \u2014 install OpenCode 1.18.10 and authenticate it; claude \u2014 npm install -g @agentclientprotocol/claude-agent-acp@0.64.2; codex \u2014 npm install -g @agentclientprotocol/codex-acp@1.1.9. You can use working-on, note, ask, and feed now; detached live receipt needs one of these adapters";
40478
+ const hints = "supported providers: grok \u2014 install Grok CLI 0.2.117 or newer and run grok login; opencode \u2014 install OpenCode 1.18.10 or newer and authenticate it; claude \u2014 npm install -g @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2); codex \u2014 npm install -g @agentclientprotocol/codex-acp@latest (minimum 1.1.9). You can use working-on, note, ask, and feed now; detached live receipt needs one of these adapters";
40157
40479
  if (provider === void 0) {
40158
40480
  throw new Error(`--provider is required; ${hints}`);
40159
40481
  }
@@ -40214,7 +40536,7 @@ function listenerHostLimits(provider) {
40214
40536
  };
40215
40537
  }
40216
40538
  if (provider === "claude") {
40217
- const host_configuration2 = "The Claude worker uses the operator-selected cwd and the normal Claude Code home through claude-agent-acp 0.64.2. Keychain/OAuth auth was measured; ANTHROPIC_API_KEY is stripped by the listener environment sanitizer.";
40539
+ const host_configuration2 = "The Claude worker uses the operator-selected cwd and the normal Claude Code home through claude-agent-acp 0.64.2 or newer. Keychain/OAuth auth was measured; ANTHROPIC_API_KEY is stripped by the listener environment sanitizer.";
40218
40540
  const deny_canary_scope2 = "The deny canary proves host reject + correlated terminal deny only.";
40219
40541
  const steady_allow_unproven2 = "The deny canary does not prove steady-state --permissions allow behavior.";
40220
40542
  const cross_owner_context2 = "All sender relations reach that same worker and local context; each prompt carries sender and operator provenance.";
@@ -40238,7 +40560,7 @@ function listenerHostLimits(provider) {
40238
40560
  };
40239
40561
  }
40240
40562
  if (provider === "codex") {
40241
- const host_configuration2 = "The Codex worker uses the operator-selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9. CommonSwarm explicitly selects read-only mode after every session/new; API-key variables are stripped by the listener environment sanitizer.";
40563
+ const host_configuration2 = "The Codex worker uses the operator-selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9 or newer. CommonSwarm explicitly selects read-only mode after every session/new; API-key variables are stripped by the listener environment sanitizer.";
40242
40564
  const deny_canary_scope2 = "The deny canary proves host reject + correlated terminal deny only.";
40243
40565
  const steady_allow_unproven2 = "The deny canary does not prove steady-state --permissions allow behavior.";
40244
40566
  const cross_owner_context2 = "All sender relations reach that same worker and local context; each prompt carries sender and operator provenance.";
@@ -40327,6 +40649,11 @@ function renderListenerStatus(status) {
40327
40649
  lines.push(` ${line}`);
40328
40650
  }
40329
40651
  }
40652
+ if (status.providerVersion && status.providerLastMeasuredVersion) {
40653
+ lines.push(
40654
+ `Provider version ${status.providerVersion} is newer than the last measured version ${status.providerLastMeasuredVersion}. It is unverified but allowed because the startup permission canary passed. Next: verify this provider release with CommonSwarm and update the last-measured version.`
40655
+ );
40656
+ }
40330
40657
  if (status.deliveryMode === "durable_claim") {
40331
40658
  lines.push("Delivery mode: durable claim and acknowledgement.");
40332
40659
  } else if (status.deliveryMode === "cursor_fallback") {
@@ -40369,29 +40696,35 @@ function renderListenerStatus(status) {
40369
40696
  return lines.join("\n");
40370
40697
  }
40371
40698
  function listenerFailureMessage(code, provider) {
40372
- if (code === "version_refused") {
40699
+ if (code === "version_below_floor") {
40373
40700
  if (provider === "codex") {
40374
- return "the Codex listener requires codex-acp 1.1.9; run npm install -g @agentclientprotocol/codex-acp@1.1.9, then retry";
40701
+ return "the Codex listener requires codex-acp 1.1.9 or newer; update the bridge, then retry";
40375
40702
  }
40376
40703
  if (provider === "claude") {
40377
- return "the Claude listener requires claude-agent-acp 0.64.2; run npm install -g @agentclientprotocol/claude-agent-acp@0.64.2, then retry";
40704
+ return "the Claude listener requires claude-agent-acp 0.64.2 or newer; update the bridge, then retry";
40378
40705
  }
40379
40706
  if (provider === "opencode") {
40380
- return "the OpenCode listener requires OpenCode 1.18.10; install that pinned build, then retry";
40707
+ return "the OpenCode listener requires OpenCode 1.18.10 or newer; update OpenCode, then retry";
40381
40708
  }
40382
- return "the Grok listener requires Grok 0.2.117; install that pinned build and run grok login, then retry";
40709
+ return "the Grok listener requires Grok 0.2.117 or newer; update Grok, confirm grok login, then retry";
40710
+ }
40711
+ if (code === "version_unparseable") {
40712
+ return `the ${provider ?? "provider"} version output was not valid semantic version data, so startup stopped. Next: run the provider's --version command, then update or reinstall it`;
40713
+ }
40714
+ if (code === "version_refused") {
40715
+ return `the ${provider ?? "provider"} version check could not run, so startup stopped. Next: run the provider's --version command and fix that error, then retry`;
40383
40716
  }
40384
40717
  if (code === "executable_missing" && provider === "claude") {
40385
- return "claude-agent-acp is not installed; run npm install -g @agentclientprotocol/claude-agent-acp@0.64.2, then retry";
40718
+ return "claude-agent-acp is not installed; run npm install -g @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2), then retry";
40386
40719
  }
40387
40720
  if (code === "executable_missing" && provider === "codex") {
40388
- return "codex-acp is not installed; run npm install -g @agentclientprotocol/codex-acp@1.1.9, then retry";
40721
+ return "codex-acp is not installed; run npm install -g @agentclientprotocol/codex-acp@latest (minimum 1.1.9), then retry";
40389
40722
  }
40390
40723
  if (code === "permission_mode_unavailable" && provider === "claude") {
40391
- return "the Claude bridge does not expose the required manual permission mode; reinstall claude-agent-acp 0.64.2, then retry";
40724
+ return "the Claude bridge does not expose the required manual permission mode; update or reinstall claude-agent-acp, then retry";
40392
40725
  }
40393
40726
  if (code === "permission_mode_unavailable" && provider === "codex") {
40394
- return "the Codex bridge does not expose the required read-only permission mode; reinstall codex-acp 1.1.9, then retry";
40727
+ return "the Codex bridge does not expose the required read-only permission mode; update or reinstall codex-acp, then retry";
40395
40728
  }
40396
40729
  if (provider === "claude" && (code === "rpc_error" || code === "child_exit" || code === "timeout")) {
40397
40730
  return `the Claude bridge could not start (${code}); confirm Claude Code keychain/OAuth sign-in and network access, then retry. ANTHROPIC_API_KEY is not forwarded to detached listeners`;
@@ -40443,7 +40776,7 @@ function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEn
40443
40776
  if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
40444
40777
  const detail = error instanceof Error ? error.message : code;
40445
40778
  throw new Error(
40446
- `could not use --claude-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/claude-agent-acp@0.64.2 if this path should be replaced`
40779
+ `could not use --claude-executable: ${detail}; install the current bridge with npm install -g @agentclientprotocol/claude-agent-acp@latest if this path should be replaced`
40447
40780
  );
40448
40781
  }
40449
40782
  throw new Error(listenerFailureMessage(code, "claude"));
@@ -40460,7 +40793,7 @@ function resolveDetachedCodexExecutable(executable = "codex-acp", pathEnv = proc
40460
40793
  if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
40461
40794
  const detail = error instanceof Error ? error.message : code;
40462
40795
  throw new Error(
40463
- `could not use --codex-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/codex-acp@1.1.9 if this path should be replaced`
40796
+ `could not use --codex-executable: ${detail}; install the current bridge with npm install -g @agentclientprotocol/codex-acp@latest if this path should be replaced`
40464
40797
  );
40465
40798
  }
40466
40799
  throw new Error(listenerFailureMessage(code, "codex"));
@@ -40564,6 +40897,10 @@ async function runConfiguredListener(options) {
40564
40897
  };
40565
40898
  let lastWorkerStderrTail = null;
40566
40899
  let workerStderrGeneration = 0;
40900
+ let providerVersionNotice = null;
40901
+ const onVersionNotice = (notice) => {
40902
+ providerVersionNotice = notice;
40903
+ };
40567
40904
  const newWorkerStderrTailSink = () => {
40568
40905
  const generation = ++workerStderrGeneration;
40569
40906
  lastWorkerStderrTail = null;
@@ -40572,34 +40909,41 @@ async function runConfiguredListener(options) {
40572
40909
  lastWorkerStderrTail = tail.length > 0 ? tail : null;
40573
40910
  };
40574
40911
  };
40575
- const newModel = () => options.provider === "opencode" ? new OpenCodeListenerModel({
40576
- cwd: options.cwd,
40577
- permissionMode: options.permissionMode,
40578
- promptTimeoutMs: resolveTurnBudgetMs,
40579
- onWorkerStderrTail: newWorkerStderrTailSink(),
40580
- ...options.model ? { model: options.model } : {},
40581
- ...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
40582
- }) : options.provider === "claude" ? new ClaudeListenerModel({
40583
- cwd: options.cwd,
40584
- permissionMode: options.permissionMode,
40585
- promptTimeoutMs: resolveTurnBudgetMs,
40586
- onWorkerStderrTail: newWorkerStderrTailSink(),
40587
- ...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
40588
- }) : options.provider === "codex" ? new CodexListenerModel({
40589
- cwd: options.cwd,
40590
- permissionMode: options.permissionMode,
40591
- promptTimeoutMs: resolveTurnBudgetMs,
40592
- onWorkerStderrTail: newWorkerStderrTailSink(),
40593
- ...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
40594
- }) : new GrokListenerModel({
40595
- cwd: options.cwd,
40596
- permissionMode: options.permissionMode,
40597
- promptTimeoutMs: resolveTurnBudgetMs,
40598
- onWorkerStderrTail: newWorkerStderrTailSink(),
40599
- ...options.model ? { model: options.model } : {},
40600
- ...options.effort ? { effort: options.effort } : {},
40601
- ...options.executable ? { executable: options.executable } : {}
40602
- });
40912
+ const newModel = () => {
40913
+ providerVersionNotice = null;
40914
+ return options.provider === "opencode" ? new OpenCodeListenerModel({
40915
+ cwd: options.cwd,
40916
+ permissionMode: options.permissionMode,
40917
+ promptTimeoutMs: resolveTurnBudgetMs,
40918
+ onWorkerStderrTail: newWorkerStderrTailSink(),
40919
+ onVersionNotice,
40920
+ ...options.model ? { model: options.model } : {},
40921
+ ...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
40922
+ }) : options.provider === "claude" ? new ClaudeListenerModel({
40923
+ cwd: options.cwd,
40924
+ permissionMode: options.permissionMode,
40925
+ promptTimeoutMs: resolveTurnBudgetMs,
40926
+ onWorkerStderrTail: newWorkerStderrTailSink(),
40927
+ onVersionNotice,
40928
+ ...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
40929
+ }) : options.provider === "codex" ? new CodexListenerModel({
40930
+ cwd: options.cwd,
40931
+ permissionMode: options.permissionMode,
40932
+ promptTimeoutMs: resolveTurnBudgetMs,
40933
+ onWorkerStderrTail: newWorkerStderrTailSink(),
40934
+ onVersionNotice,
40935
+ ...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
40936
+ }) : new GrokListenerModel({
40937
+ cwd: options.cwd,
40938
+ permissionMode: options.permissionMode,
40939
+ promptTimeoutMs: resolveTurnBudgetMs,
40940
+ onWorkerStderrTail: newWorkerStderrTailSink(),
40941
+ onVersionNotice,
40942
+ ...options.model ? { model: options.model } : {},
40943
+ ...options.effort ? { effort: options.effort } : {},
40944
+ ...options.executable ? { executable: options.executable } : {}
40945
+ });
40946
+ };
40603
40947
  const onProcessSignal = () => {
40604
40948
  void stopListener(paths);
40605
40949
  };
@@ -40623,6 +40967,7 @@ async function runConfiguredListener(options) {
40623
40967
  // The bound a timeout event reports: the last turn's clamped budget when
40624
40968
  // one has run, else the configured cap.
40625
40969
  getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
40970
+ getProviderVersionNotice: () => providerVersionNotice,
40626
40971
  takeWorkerStderrTail: () => {
40627
40972
  const tail = lastWorkerStderrTail;
40628
40973
  lastWorkerStderrTail = null;
@@ -40841,8 +41186,8 @@ async function runListenStart(args) {
40841
41186
  ` : "";
40842
41187
  const workerAudience = routing.routeMode === "main" ? "Directed asks do not reach that worker." : routing.routeMode === "split" ? "Only asks at or below the split threshold reach that worker, with sender and operator provenance in the prompt." : "Every sender reaches that worker with sender and operator provenance in the prompt.";
40843
41188
  const hostNote = provider === "opencode" ? `The OpenCode worker uses one private auth/config home and your selected project cwd. ${workerAudience} Tool requests are approved one at a time by default, when the worker asks and the host offers a one-time approval; --permissions deny refuses them. The deny canary does not cover steady-state allow.
40844
- ` : provider === "claude" ? `The Claude worker uses your selected cwd and normal Claude Code keychain/OAuth state through claude-agent-acp 0.64.2. ${workerAudience}
40845
- ` : provider === "codex" ? `The Codex worker uses your selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9. CommonSwarm selects read-only mode before its deny canary. ${workerAudience}
41189
+ ` : provider === "claude" ? `The Claude worker uses your selected cwd and normal Claude Code keychain/OAuth state through claude-agent-acp 0.64.2 or newer. ${workerAudience}
41190
+ ` : provider === "codex" ? `The Codex worker uses your selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9 or newer. CommonSwarm selects read-only mode before its deny canary. ${workerAudience}
40846
41191
  ` : `The Grok worker uses your selected cwd and local Grok configuration, including user and cmux hooks. ${workerAudience}
40847
41192
  `;
40848
41193
  process.stdout.write(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "CommonSwarm CLI — coordination for teams where people and AI agents work side by side.",
5
5
  "bin": {
6
6
  "cswarm": "cswarm.cjs"