commonswarm 0.1.62 → 0.1.64

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 +3048 -280
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -4381,8 +4381,8 @@ var require_RealtimeChannel = __commonJS({
4381
4381
  }
4382
4382
  /** @internal */
4383
4383
  _notThisChannelEvent(event, ref) {
4384
- const { close, error, leave, join: join13 } = constants_1.CHANNEL_EVENTS;
4385
- const events = [close, error, leave, join13];
4384
+ const { close, error, leave, join: join16 } = constants_1.CHANNEL_EVENTS;
4385
+ const events = [close, error, leave, join16];
4386
4386
  return ref && events.includes(event) && ref !== this.joinPush.ref;
4387
4387
  }
4388
4388
  /** @internal */
@@ -13499,6 +13499,2885 @@ var require_main3 = __commonJS({
13499
13499
  }
13500
13500
  });
13501
13501
 
13502
+ // src/host/types.ts
13503
+ var TRANSIENT_ACP_CODES, AcpHostError, AcpProtocolError, AcpTimeoutError, AcpChildExitError, AcpTransportError, AcpVersionError, AcpVersionParseError, AcpVersionBelowFloorError, AcpPermissionCanaryError, AcpPromptsBlockedError;
13504
+ var init_types = __esm({
13505
+ "src/host/types.ts"() {
13506
+ "use strict";
13507
+ TRANSIENT_ACP_CODES = /* @__PURE__ */ new Set([
13508
+ "timeout",
13509
+ "child_exit",
13510
+ "transport"
13511
+ ]);
13512
+ AcpHostError = class extends Error {
13513
+ code;
13514
+ constructor(code, message) {
13515
+ super(message);
13516
+ this.name = "AcpHostError";
13517
+ this.code = code;
13518
+ }
13519
+ };
13520
+ AcpProtocolError = class extends AcpHostError {
13521
+ constructor(message, code = "protocol_error", peerError = null) {
13522
+ super(code, message);
13523
+ this.peerError = peerError;
13524
+ this.name = "AcpProtocolError";
13525
+ }
13526
+ peerError;
13527
+ };
13528
+ AcpTimeoutError = class extends AcpHostError {
13529
+ constructor(message) {
13530
+ super("timeout", message);
13531
+ this.name = "AcpTimeoutError";
13532
+ }
13533
+ };
13534
+ AcpChildExitError = class extends AcpHostError {
13535
+ exitCode;
13536
+ signal;
13537
+ constructor(exitCode, signal) {
13538
+ super(
13539
+ "child_exit",
13540
+ `ACP child exited (code=${exitCode ?? "null"}, signal=${signal ?? "null"})`
13541
+ );
13542
+ this.name = "AcpChildExitError";
13543
+ this.exitCode = exitCode;
13544
+ this.signal = signal;
13545
+ }
13546
+ };
13547
+ AcpTransportError = class extends AcpHostError {
13548
+ constructor(cause) {
13549
+ super("transport", `ACP transport failed: ${cause.message}`);
13550
+ this.cause = cause;
13551
+ this.name = "AcpTransportError";
13552
+ }
13553
+ cause;
13554
+ };
13555
+ AcpVersionError = class extends AcpHostError {
13556
+ constructor(message, code = "version_refused") {
13557
+ super(code, message);
13558
+ this.name = "AcpVersionError";
13559
+ }
13560
+ };
13561
+ AcpVersionParseError = class extends AcpVersionError {
13562
+ constructor(message) {
13563
+ super(message, "version_unparseable");
13564
+ this.name = "AcpVersionParseError";
13565
+ }
13566
+ };
13567
+ AcpVersionBelowFloorError = class extends AcpVersionError {
13568
+ constructor(provider, minimum, actual) {
13569
+ super(
13570
+ `refusing ${provider} ${actual}; CommonSwarm requires ${minimum} or newer`,
13571
+ "version_below_floor"
13572
+ );
13573
+ this.provider = provider;
13574
+ this.minimum = minimum;
13575
+ this.actual = actual;
13576
+ this.name = "AcpVersionBelowFloorError";
13577
+ }
13578
+ provider;
13579
+ minimum;
13580
+ actual;
13581
+ };
13582
+ AcpPermissionCanaryError = class extends AcpHostError {
13583
+ constructor(message, reasonCode = null, minimumRequiredVersion = null, peerError = null) {
13584
+ super("permission_canary_failed", message);
13585
+ this.reasonCode = reasonCode;
13586
+ this.minimumRequiredVersion = minimumRequiredVersion;
13587
+ this.peerError = peerError;
13588
+ this.name = "AcpPermissionCanaryError";
13589
+ }
13590
+ reasonCode;
13591
+ minimumRequiredVersion;
13592
+ peerError;
13593
+ };
13594
+ AcpPromptsBlockedError = class extends AcpHostError {
13595
+ constructor() {
13596
+ super(
13597
+ "prompts_blocked",
13598
+ "Real prompts are blocked until the permission-boundary canary passes"
13599
+ );
13600
+ this.name = "AcpPromptsBlockedError";
13601
+ }
13602
+ };
13603
+ }
13604
+ });
13605
+
13606
+ // src/host/version.ts
13607
+ function parseSemVer(value) {
13608
+ if (!SEMVER_RE.test(value)) return null;
13609
+ const withoutBuild = value.split("+", 1)[0];
13610
+ const dash = withoutBuild.indexOf("-");
13611
+ const coreText = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
13612
+ const prereleaseText = dash === -1 ? null : withoutBuild.slice(dash + 1);
13613
+ const coreParts = coreText.split(".");
13614
+ if (coreParts.length !== 3) return null;
13615
+ return {
13616
+ core: [BigInt(coreParts[0]), BigInt(coreParts[1]), BigInt(coreParts[2])],
13617
+ prerelease: prereleaseText === null ? null : prereleaseText.split(".")
13618
+ };
13619
+ }
13620
+ function compareSemVer(left, right) {
13621
+ const a = parseSemVer(left);
13622
+ const b2 = parseSemVer(right);
13623
+ if (!a || !b2) {
13624
+ throw new AcpVersionParseError(
13625
+ `cannot compare invalid semantic versions: ${JSON.stringify(left)} and ${JSON.stringify(right)}`
13626
+ );
13627
+ }
13628
+ for (let index = 0; index < 3; index += 1) {
13629
+ if (a.core[index] < b2.core[index]) return -1;
13630
+ if (a.core[index] > b2.core[index]) return 1;
13631
+ }
13632
+ if (a.prerelease === null && b2.prerelease === null) return 0;
13633
+ if (a.prerelease === null) return 1;
13634
+ if (b2.prerelease === null) return -1;
13635
+ const length = Math.max(a.prerelease.length, b2.prerelease.length);
13636
+ for (let index = 0; index < length; index += 1) {
13637
+ const leftPart = a.prerelease[index];
13638
+ const rightPart = b2.prerelease[index];
13639
+ if (leftPart === void 0) return -1;
13640
+ if (rightPart === void 0) return 1;
13641
+ if (leftPart === rightPart) continue;
13642
+ const leftNumeric = /^\d+$/.test(leftPart);
13643
+ const rightNumeric = /^\d+$/.test(rightPart);
13644
+ if (leftNumeric && rightNumeric) {
13645
+ return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1;
13646
+ }
13647
+ if (leftNumeric) return -1;
13648
+ if (rightNumeric) return 1;
13649
+ return leftPart < rightPart ? -1 : 1;
13650
+ }
13651
+ return 0;
13652
+ }
13653
+ function parseProviderVersionOutput(stdout, productPattern, allowBare = true) {
13654
+ const lines = stdout.split(/\r?\n/);
13655
+ for (const line of lines) {
13656
+ const pattern = new RegExp(productPattern.source, productPattern.flags.replace("g", ""));
13657
+ const product = pattern.exec(line);
13658
+ if (!product) continue;
13659
+ const after = line.slice(product.index + product[0].length);
13660
+ const afterMatch = new RegExp(
13661
+ `^\\s+(${SEMVER_SOURCE})(?=$|\\s|\\()`
13662
+ ).exec(after);
13663
+ if (afterMatch?.[1]) return afterMatch[1];
13664
+ const before = line.slice(0, product.index);
13665
+ const beforeMatch = new RegExp(`(${SEMVER_SOURCE})\\s*\\($`).exec(before);
13666
+ if (beforeMatch?.[1]) return beforeMatch[1];
13667
+ }
13668
+ if (!allowBare) return null;
13669
+ for (const line of lines) {
13670
+ const trimmed = line.trim();
13671
+ const match = new RegExp(`^(${SEMVER_SOURCE})$`).exec(trimmed);
13672
+ if (match?.[1]) return match[1];
13673
+ }
13674
+ return null;
13675
+ }
13676
+ function assertProviderVersionFloor(options) {
13677
+ if (compareSemVer(options.version, options.minimumVersion) < 0) {
13678
+ throw new AcpVersionBelowFloorError(
13679
+ options.provider,
13680
+ options.minimumVersion,
13681
+ options.version
13682
+ );
13683
+ }
13684
+ if (compareSemVer(options.version, options.lastMeasuredVersion) > 0) {
13685
+ options.onNewerVersion?.({
13686
+ provider: options.provider,
13687
+ runningVersion: options.version,
13688
+ lastMeasuredVersion: options.lastMeasuredVersion
13689
+ });
13690
+ }
13691
+ }
13692
+ var CORE_IDENTIFIER, PRERELEASE_IDENTIFIER, BUILD_IDENTIFIER, SEMVER_SOURCE, SEMVER_RE;
13693
+ var init_version = __esm({
13694
+ "src/host/version.ts"() {
13695
+ "use strict";
13696
+ init_types();
13697
+ CORE_IDENTIFIER = "(?:0|[1-9]\\d*)";
13698
+ PRERELEASE_IDENTIFIER = "(?:0|[1-9]\\d*|[A-Za-z-][0-9A-Za-z-]*)";
13699
+ BUILD_IDENTIFIER = "[0-9A-Za-z-]+";
13700
+ SEMVER_SOURCE = `${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}(?:-${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*)?(?:\\+${BUILD_IDENTIFIER}(?:\\.${BUILD_IDENTIFIER})*)?`;
13701
+ SEMVER_RE = new RegExp(`^${SEMVER_SOURCE}$`);
13702
+ }
13703
+ });
13704
+
13705
+ // src/host/bounds.ts
13706
+ var ACP_MAX_LINE_BYTES, ACP_MAX_FRAME_BYTES, ACP_MAX_PENDING_REQUESTS, ACP_MAX_ACCUMULATED_TEXT_CHARS, ACP_DEFAULT_REQUEST_TIMEOUT_MS, ACP_VERSION_CHECK_TIMEOUT_MS, OPENCODE_MIN_VERSION, OPENCODE_LAST_MEASURED_VERSION, CLAUDE_ACP_MIN_VERSION, CLAUDE_ACP_LAST_MEASURED_VERSION, CLAUDE_PERMISSION_MODE_ID, CODEX_ACP_MIN_VERSION, CODEX_ACP_LAST_MEASURED_VERSION, CODEX_PERMISSION_MODE_ID, ACP_PROTOCOL_VERSION, OPENCODE_FORCED_PERMISSION_TOOLS;
13707
+ var init_bounds = __esm({
13708
+ "src/host/bounds.ts"() {
13709
+ "use strict";
13710
+ ACP_MAX_LINE_BYTES = 1048576;
13711
+ ACP_MAX_FRAME_BYTES = ACP_MAX_LINE_BYTES;
13712
+ ACP_MAX_PENDING_REQUESTS = 32;
13713
+ ACP_MAX_ACCUMULATED_TEXT_CHARS = 4194304;
13714
+ ACP_DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
13715
+ ACP_VERSION_CHECK_TIMEOUT_MS = 5e3;
13716
+ OPENCODE_MIN_VERSION = "1.18.10";
13717
+ OPENCODE_LAST_MEASURED_VERSION = "1.18.10";
13718
+ CLAUDE_ACP_MIN_VERSION = "0.64.2";
13719
+ CLAUDE_ACP_LAST_MEASURED_VERSION = "0.64.2";
13720
+ CLAUDE_PERMISSION_MODE_ID = "default";
13721
+ CODEX_ACP_MIN_VERSION = "1.1.9";
13722
+ CODEX_ACP_LAST_MEASURED_VERSION = "1.8.0";
13723
+ CODEX_PERMISSION_MODE_ID = "read-only";
13724
+ ACP_PROTOCOL_VERSION = 1;
13725
+ OPENCODE_FORCED_PERMISSION_TOOLS = [
13726
+ "bash",
13727
+ "glob",
13728
+ "read",
13729
+ "grep",
13730
+ "webfetch",
13731
+ "websearch",
13732
+ "write",
13733
+ "edit",
13734
+ "task",
13735
+ "apply_patch",
13736
+ "todowrite",
13737
+ "question",
13738
+ "skill",
13739
+ "execute",
13740
+ "external_directory",
13741
+ "*"
13742
+ ];
13743
+ }
13744
+ });
13745
+
13746
+ // src/host/credential-redaction.ts
13747
+ function redactCredentialText(value) {
13748
+ return value.replace(ANSI_ESCAPE_GLOBAL_RE2, "").replace(CONTROL_AND_SEPARATOR_STRIP_RE, "").replace(SECRET_SHAPE_GLOBAL_RE, "[redacted-credential]");
13749
+ }
13750
+ var EXOTIC_SEPARATORS, SEPARATOR_CLASS_SOURCE, ANSI_ESCAPE_GLOBAL_RE2, CONTROL_AND_SEPARATOR_STRIP_RE, SECRET_SHAPE_RE, SECRET_SHAPE_GLOBAL_RE;
13751
+ var init_credential_redaction = __esm({
13752
+ "src/host/credential-redaction.ts"() {
13753
+ "use strict";
13754
+ EXOTIC_SEPARATORS = "\\u00a0\\u1680\\u2000-\\u200d\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\u202f\\u205f\\u3000\\ufeff";
13755
+ SEPARATOR_CLASS_SOURCE = "\\t\\n\\x0b\\f\\r " + EXOTIC_SEPARATORS;
13756
+ ANSI_ESCAPE_GLOBAL_RE2 = new RegExp("\\u001b\\[[0-?]*[ -\\/]*[@-~]", "g");
13757
+ CONTROL_AND_SEPARATOR_STRIP_RE = new RegExp(
13758
+ "[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f" + EXOTIC_SEPARATORS + "]",
13759
+ "g"
13760
+ );
13761
+ SECRET_SHAPE_RE = new RegExp(
13762
+ `swm_(?:agt|inv|cap)_[^${SEPARATOR_CLASS_SOURCE}]*|cswarm-wake:[A-Za-z0-9_-]{43}`,
13763
+ "i"
13764
+ );
13765
+ SECRET_SHAPE_GLOBAL_RE = new RegExp(SECRET_SHAPE_RE.source, "gi");
13766
+ }
13767
+ });
13768
+
13769
+ // src/host/env.ts
13770
+ function sanitizeChildEnv(parent = process.env) {
13771
+ const out = {};
13772
+ for (const [key2, value] of Object.entries(parent)) {
13773
+ if (value === void 0) continue;
13774
+ if (!ALLOWED_EXACT.has(key2)) continue;
13775
+ if (DENY_NAME_RE.test(key2)) continue;
13776
+ if (key2.startsWith("SWARM_")) continue;
13777
+ out[key2] = value;
13778
+ }
13779
+ return out;
13780
+ }
13781
+ var ALLOWED_EXACT, DENY_NAME_RE;
13782
+ var init_env = __esm({
13783
+ "src/host/env.ts"() {
13784
+ "use strict";
13785
+ ALLOWED_EXACT = /* @__PURE__ */ new Set([
13786
+ "PATH",
13787
+ "HOME",
13788
+ "USER",
13789
+ "LOGNAME",
13790
+ "SHELL",
13791
+ "TMPDIR",
13792
+ "TMP",
13793
+ "TEMP",
13794
+ "LANG",
13795
+ "LC_ALL",
13796
+ "LC_CTYPE",
13797
+ "LC_MESSAGES",
13798
+ "LC_COLLATE",
13799
+ "LC_TIME",
13800
+ "TERM",
13801
+ "COLORTERM",
13802
+ "NO_COLOR",
13803
+ "FORCE_COLOR",
13804
+ "XDG_CONFIG_HOME",
13805
+ "XDG_DATA_HOME",
13806
+ "XDG_CACHE_HOME",
13807
+ "XDG_RUNTIME_DIR",
13808
+ "XDG_STATE_HOME",
13809
+ "GROK_HOME"
13810
+ ]);
13811
+ DENY_NAME_RE = /(?:^|_)(?:SWARM|SECRET|TOKEN|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|API_KEY|AUTH|COOKIE)(?:_|$)/i;
13812
+ }
13813
+ });
13814
+
13815
+ // src/host/sanitize.ts
13816
+ function redactString(value) {
13817
+ return redactCredentialText(value).replace(SECRET_VALUE_RE, (_m, q) => `redacted=${q}***${q}`).replace(JWT_RE, "[redacted-jwt]");
13818
+ }
13819
+ function redactUnknown(value, depth = 0) {
13820
+ if (depth > 6) return "[truncated]";
13821
+ if (typeof value === "string") {
13822
+ if (value.length > 4096) {
13823
+ return redactString(value.slice(0, 4096)) + "\u2026";
13824
+ }
13825
+ return redactString(value);
13826
+ }
13827
+ if (Array.isArray(value)) {
13828
+ return value.slice(0, 32).map((item) => redactUnknown(item, depth + 1));
13829
+ }
13830
+ if (value && typeof value === "object") {
13831
+ const out = {};
13832
+ for (const [k, v] of Object.entries(value)) {
13833
+ if (/secret|token|password|authorization|api[_-]?key|credential/i.test(k)) {
13834
+ out[k] = "[redacted]";
13835
+ continue;
13836
+ }
13837
+ if (k === "rawInput" || k === "rawOutput" || k === "env") {
13838
+ out[k] = "[redacted]";
13839
+ continue;
13840
+ }
13841
+ out[k] = redactUnknown(v, depth + 1);
13842
+ }
13843
+ return out;
13844
+ }
13845
+ return value;
13846
+ }
13847
+ function sanitizeUpdateDetail(detail) {
13848
+ if (!detail) return void 0;
13849
+ return redactUnknown(detail);
13850
+ }
13851
+ function sanitizeText(text) {
13852
+ return redactString(text);
13853
+ }
13854
+ var SECRET_VALUE_RE, JWT_RE;
13855
+ var init_sanitize = __esm({
13856
+ "src/host/sanitize.ts"() {
13857
+ "use strict";
13858
+ init_credential_redaction();
13859
+ SECRET_VALUE_RE = /(?:(?:api[_-]?key|token|secret|password|authorization|bearer)\s*[:=]\s*)(["']?)([^\s"'\\]{8,})\1/gi;
13860
+ JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
13861
+ }
13862
+ });
13863
+
13864
+ // src/host/stderr-tail.ts
13865
+ function sanitizeStderrTail(raw) {
13866
+ return redactCredentialText(raw).slice(-TAIL_MAX_CHARS).trim();
13867
+ }
13868
+ function attachStderrTailRing(stderr) {
13869
+ const chunks = [];
13870
+ let total = 0;
13871
+ let evicted = false;
13872
+ stderr.on("data", (chunk) => {
13873
+ const buffer2 = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
13874
+ chunks.push(buffer2);
13875
+ total += buffer2.length;
13876
+ while (total > RING_CAPACITY_BYTES && chunks.length > 0) {
13877
+ evicted = true;
13878
+ const head2 = chunks[0];
13879
+ const excess = total - RING_CAPACITY_BYTES;
13880
+ if (head2.length <= excess) {
13881
+ chunks.shift();
13882
+ total -= head2.length;
13883
+ } else {
13884
+ chunks[0] = head2.subarray(excess);
13885
+ total -= excess;
13886
+ }
13887
+ }
13888
+ });
13889
+ stderr.resume();
13890
+ return {
13891
+ read() {
13892
+ let text = Buffer.concat(chunks).toString("utf8");
13893
+ if (evicted) {
13894
+ const newline = text.indexOf("\n");
13895
+ text = newline === -1 ? "" : text.slice(newline + 1);
13896
+ }
13897
+ return sanitizeStderrTail(text);
13898
+ }
13899
+ };
13900
+ }
13901
+ function attachStderrTailExitObserver(child, onStderrTail) {
13902
+ const stderrTail = attachStderrTailRing(child.stderr);
13903
+ return (handler) => {
13904
+ const observeExit = (code, signal) => {
13905
+ let completed = false;
13906
+ let timer2 = null;
13907
+ const complete = () => {
13908
+ if (completed) return;
13909
+ completed = true;
13910
+ if (timer2) clearTimeout(timer2);
13911
+ child.removeListener("close", complete);
13912
+ try {
13913
+ onStderrTail?.(stderrTail.read());
13914
+ } finally {
13915
+ handler(code, signal);
13916
+ }
13917
+ };
13918
+ child.once("close", complete);
13919
+ timer2 = setTimeout(complete, STDERR_EXIT_GRACE_MS);
13920
+ timer2.unref();
13921
+ };
13922
+ if (child.exitCode !== null || child.signalCode !== null) {
13923
+ observeExit(child.exitCode, child.signalCode);
13924
+ } else {
13925
+ child.once("exit", observeExit);
13926
+ }
13927
+ };
13928
+ }
13929
+ var RING_CAPACITY_BYTES, TAIL_MAX_CHARS, STDERR_EXIT_GRACE_MS, STDERR_READABLE_END_GRACE_MS;
13930
+ var init_stderr_tail = __esm({
13931
+ "src/host/stderr-tail.ts"() {
13932
+ "use strict";
13933
+ init_credential_redaction();
13934
+ RING_CAPACITY_BYTES = 4096;
13935
+ TAIL_MAX_CHARS = 2048;
13936
+ STDERR_EXIT_GRACE_MS = 100;
13937
+ STDERR_READABLE_END_GRACE_MS = STDERR_EXIT_GRACE_MS + 50;
13938
+ }
13939
+ });
13940
+
13941
+ // src/host/permission.ts
13942
+ function defaultPermissionCallback(request) {
13943
+ const rejectOnce = request.options.find((opt) => opt.kind === "reject_once");
13944
+ if (rejectOnce) {
13945
+ return { outcome: "selected", optionId: rejectOnce.optionId };
13946
+ }
13947
+ const rejectAlways = request.options.find((opt) => opt.kind === "reject_always");
13948
+ if (rejectAlways) {
13949
+ return { outcome: "selected", optionId: rejectAlways.optionId };
13950
+ }
13951
+ return { outcome: "cancelled" };
13952
+ }
13953
+ function resolvePermissionCallback(callback) {
13954
+ return callback ?? defaultPermissionCallback;
13955
+ }
13956
+ function parsePermissionOptions(raw) {
13957
+ if (!Array.isArray(raw)) return [];
13958
+ const seen = /* @__PURE__ */ new Map();
13959
+ for (const item of raw) {
13960
+ if (!item || typeof item !== "object") continue;
13961
+ const id = item.optionId;
13962
+ if (typeof id === "string" && id) seen.set(id, (seen.get(id) ?? 0) + 1);
13963
+ }
13964
+ const options = [];
13965
+ for (const item of raw) {
13966
+ if (!item || typeof item !== "object") continue;
13967
+ const rec = item;
13968
+ const optionId = rec.optionId;
13969
+ const name = rec.name;
13970
+ const kind = rec.kind;
13971
+ if (typeof optionId !== "string" || !optionId) continue;
13972
+ if ((seen.get(optionId) ?? 0) > 1) continue;
13973
+ if (typeof name !== "string") continue;
13974
+ if (kind !== "allow_once" && kind !== "allow_always" && kind !== "reject_once" && kind !== "reject_always") {
13975
+ continue;
13976
+ }
13977
+ options.push({ optionId, name, kind });
13978
+ }
13979
+ return options;
13980
+ }
13981
+ function permissionDecisionToResult(decision) {
13982
+ if (decision.outcome === "cancelled") {
13983
+ return { outcome: { outcome: "cancelled" } };
13984
+ }
13985
+ return {
13986
+ outcome: {
13987
+ outcome: "selected",
13988
+ optionId: decision.optionId
13989
+ }
13990
+ };
13991
+ }
13992
+ var init_permission = __esm({
13993
+ "src/host/permission.ts"() {
13994
+ "use strict";
13995
+ }
13996
+ });
13997
+
13998
+ // src/host/transport.ts
13999
+ function asAcpHostError(error) {
14000
+ if (error instanceof AcpHostError) return error;
14001
+ return new AcpTransportError(
14002
+ error instanceof Error ? error : new Error(String(error))
14003
+ );
14004
+ }
14005
+ var import_node_events, AcpTransport;
14006
+ var init_transport = __esm({
14007
+ "src/host/transport.ts"() {
14008
+ "use strict";
14009
+ import_node_events = require("node:events");
14010
+ init_bounds();
14011
+ init_types();
14012
+ AcpTransport = class extends import_node_events.EventEmitter {
14013
+ writable;
14014
+ handlers;
14015
+ requestTimeoutMs;
14016
+ pending = /* @__PURE__ */ new Map();
14017
+ nextId = 1;
14018
+ closed = false;
14019
+ buffer = Buffer.alloc(0);
14020
+ childExit = null;
14021
+ constructor(options) {
14022
+ super();
14023
+ this.writable = options.writable;
14024
+ this.handlers = options.handlers ?? {};
14025
+ this.requestTimeoutMs = options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS;
14026
+ const readableEndGraceMs = Math.max(0, options.readableEndGraceMs ?? 0);
14027
+ let readableEndTimer = null;
14028
+ options.readable.on("data", (chunk) => {
14029
+ this.onData(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
14030
+ });
14031
+ options.readable.on("end", () => {
14032
+ const fail = () => {
14033
+ readableEndTimer = null;
14034
+ this.failAll(
14035
+ new AcpChildExitError(
14036
+ this.childExit?.code ?? null,
14037
+ this.childExit?.signal ?? null
14038
+ )
14039
+ );
14040
+ };
14041
+ if (readableEndGraceMs > 0 && options.onChildExit) {
14042
+ readableEndTimer = setTimeout(fail, readableEndGraceMs);
14043
+ } else {
14044
+ fail();
14045
+ }
14046
+ });
14047
+ options.readable.on("error", (err) => {
14048
+ this.failAll(asAcpHostError(err));
14049
+ });
14050
+ options.writable.on("error", (err) => {
14051
+ this.failAll(asAcpHostError(err));
14052
+ });
14053
+ options.onChildExit?.((code, signal) => {
14054
+ if (readableEndTimer) {
14055
+ clearTimeout(readableEndTimer);
14056
+ readableEndTimer = null;
14057
+ }
14058
+ this.childExit = { code, signal };
14059
+ this.failAll(new AcpChildExitError(code, signal));
14060
+ });
14061
+ }
14062
+ get pendingCount() {
14063
+ return this.pending.size;
14064
+ }
14065
+ get isClosed() {
14066
+ return this.closed;
14067
+ }
14068
+ request(method, params, timeoutMs) {
14069
+ if (this.closed) {
14070
+ return Promise.reject(new AcpProtocolError("transport closed", "closed"));
14071
+ }
14072
+ if (this.childExit) {
14073
+ return Promise.reject(
14074
+ new AcpChildExitError(this.childExit.code, this.childExit.signal)
14075
+ );
14076
+ }
14077
+ if (this.pending.size >= ACP_MAX_PENDING_REQUESTS) {
14078
+ return Promise.reject(
14079
+ new AcpProtocolError(
14080
+ `pending request limit ${ACP_MAX_PENDING_REQUESTS} exceeded`,
14081
+ "pending_limit"
14082
+ )
14083
+ );
14084
+ }
14085
+ const id = this.nextId++;
14086
+ const key2 = String(id);
14087
+ const frame = {
14088
+ jsonrpc: "2.0",
14089
+ id,
14090
+ method,
14091
+ ...params !== void 0 ? { params } : {}
14092
+ };
14093
+ return new Promise((resolve4, reject) => {
14094
+ const timer2 = setTimeout(() => {
14095
+ this.pending.delete(key2);
14096
+ reject(new AcpTimeoutError(`ACP request timed out: ${method}`));
14097
+ }, timeoutMs ?? this.requestTimeoutMs);
14098
+ this.pending.set(key2, { resolve: resolve4, reject, timer: timer2, method });
14099
+ try {
14100
+ this.writeFrame(frame);
14101
+ } catch (err) {
14102
+ clearTimeout(timer2);
14103
+ this.pending.delete(key2);
14104
+ reject(asAcpHostError(err));
14105
+ }
14106
+ });
14107
+ }
14108
+ /** Notification — no id field (ACP session/cancel). */
14109
+ notify(method, params) {
14110
+ if (this.closed) {
14111
+ throw new AcpProtocolError("transport closed", "closed");
14112
+ }
14113
+ const frame = {
14114
+ jsonrpc: "2.0",
14115
+ method
14116
+ };
14117
+ if (params !== void 0) frame.params = params;
14118
+ this.writeFrame(frame);
14119
+ }
14120
+ respond(id, result) {
14121
+ this.writeFrame({ jsonrpc: "2.0", id, result });
14122
+ }
14123
+ respondError(id, code, message) {
14124
+ this.writeFrame({
14125
+ jsonrpc: "2.0",
14126
+ id,
14127
+ error: { code, message }
14128
+ });
14129
+ }
14130
+ close() {
14131
+ if (this.closed) return;
14132
+ this.closed = true;
14133
+ this.failAll(new AcpProtocolError("transport closed", "closed"));
14134
+ try {
14135
+ this.writable.end();
14136
+ } catch {
14137
+ }
14138
+ }
14139
+ writeFrame(frame) {
14140
+ const line = JSON.stringify(frame);
14141
+ const bytes = Buffer.byteLength(line, "utf8");
14142
+ if (bytes > ACP_MAX_FRAME_BYTES) {
14143
+ throw new AcpProtocolError(
14144
+ `outbound frame exceeds ${ACP_MAX_FRAME_BYTES} bytes`,
14145
+ "frame_too_large"
14146
+ );
14147
+ }
14148
+ this.writable.write(line + "\n");
14149
+ }
14150
+ onData(chunk) {
14151
+ if (this.closed) return;
14152
+ if (this.buffer.length + chunk.length > ACP_MAX_LINE_BYTES * 2) {
14153
+ this.buffer = Buffer.alloc(0);
14154
+ const err = new AcpProtocolError(
14155
+ "inbound buffer exceeded safe limit",
14156
+ "buffer_overflow"
14157
+ );
14158
+ this.handlers.onProtocolError?.(err);
14159
+ this.emit("protocolError", err);
14160
+ return;
14161
+ }
14162
+ this.buffer = Buffer.concat([this.buffer, chunk]);
14163
+ while (true) {
14164
+ const nl = this.buffer.indexOf(10);
14165
+ if (nl === -1) {
14166
+ if (this.buffer.length > ACP_MAX_LINE_BYTES) {
14167
+ this.buffer = Buffer.alloc(0);
14168
+ const err = new AcpProtocolError(
14169
+ `inbound line exceeds ${ACP_MAX_LINE_BYTES} bytes`,
14170
+ "line_too_large"
14171
+ );
14172
+ this.handlers.onProtocolError?.(err);
14173
+ this.emit("protocolError", err);
14174
+ }
14175
+ break;
14176
+ }
14177
+ const lineBuf = this.buffer.subarray(0, nl);
14178
+ this.buffer = this.buffer.subarray(nl + 1);
14179
+ const end = lineBuf.length > 0 && lineBuf[lineBuf.length - 1] === 13 ? lineBuf.length - 1 : lineBuf.length;
14180
+ if (end === 0) continue;
14181
+ if (end > ACP_MAX_LINE_BYTES) {
14182
+ const err = new AcpProtocolError(
14183
+ `inbound line exceeds ${ACP_MAX_LINE_BYTES} bytes`,
14184
+ "line_too_large"
14185
+ );
14186
+ this.handlers.onProtocolError?.(err);
14187
+ this.emit("protocolError", err);
14188
+ continue;
14189
+ }
14190
+ const line = lineBuf.subarray(0, end).toString("utf8");
14191
+ this.handleLine(line);
14192
+ }
14193
+ }
14194
+ handleLine(line) {
14195
+ let msg;
14196
+ try {
14197
+ msg = JSON.parse(line);
14198
+ } catch {
14199
+ const err2 = new AcpProtocolError("malformed JSON line", "malformed_json");
14200
+ this.handlers.onProtocolError?.(err2);
14201
+ this.emit("protocolError", err2);
14202
+ return;
14203
+ }
14204
+ if (!msg || typeof msg !== "object") {
14205
+ const err2 = new AcpProtocolError("non-object JSON-RPC frame", "malformed_frame");
14206
+ this.handlers.onProtocolError?.(err2);
14207
+ this.emit("protocolError", err2);
14208
+ return;
14209
+ }
14210
+ const rec = msg;
14211
+ if (rec.jsonrpc !== "2.0") {
14212
+ const err2 = new AcpProtocolError("missing jsonrpc 2.0", "malformed_frame");
14213
+ this.handlers.onProtocolError?.(err2);
14214
+ this.emit("protocolError", err2);
14215
+ return;
14216
+ }
14217
+ if ("id" in rec && rec.id !== null && rec.id !== void 0 && !("method" in rec)) {
14218
+ const hasResult = "result" in rec;
14219
+ const hasError = "error" in rec;
14220
+ if (hasResult === hasError) {
14221
+ const err2 = new AcpProtocolError(
14222
+ "response must carry exactly one of result or error",
14223
+ "malformed_frame"
14224
+ );
14225
+ this.handlers.onProtocolError?.(err2);
14226
+ this.emit("protocolError", err2);
14227
+ return;
14228
+ }
14229
+ this.handleResponse(rec);
14230
+ return;
14231
+ }
14232
+ if (typeof rec.method === "string" && "id" in rec && rec.id !== null && rec.id !== void 0) {
14233
+ const id = rec.id;
14234
+ if (typeof id !== "string" && typeof id !== "number") {
14235
+ const err2 = new AcpProtocolError("invalid request id", "malformed_frame");
14236
+ this.handlers.onProtocolError?.(err2);
14237
+ return;
14238
+ }
14239
+ void Promise.resolve(this.handlers.onRequest?.(id, rec.method, rec.params)).catch(
14240
+ (err2) => {
14241
+ const message = err2 instanceof Error ? err2.message : String(err2);
14242
+ try {
14243
+ this.respondError(id, -32e3, message);
14244
+ } catch {
14245
+ }
14246
+ }
14247
+ );
14248
+ return;
14249
+ }
14250
+ if (typeof rec.method === "string") {
14251
+ try {
14252
+ this.handlers.onNotification?.(rec.method, rec.params);
14253
+ } catch (err2) {
14254
+ this.emit("handlerError", err2);
14255
+ }
14256
+ return;
14257
+ }
14258
+ const err = new AcpProtocolError("unrecognized JSON-RPC frame", "malformed_frame");
14259
+ this.handlers.onProtocolError?.(err);
14260
+ this.emit("protocolError", err);
14261
+ }
14262
+ handleResponse(rec) {
14263
+ const key2 = String(rec.id);
14264
+ const pending = this.pending.get(key2);
14265
+ if (!pending) {
14266
+ return;
14267
+ }
14268
+ clearTimeout(pending.timer);
14269
+ this.pending.delete(key2);
14270
+ if ("error" in rec && rec.error !== void 0) {
14271
+ const errObj = rec.error;
14272
+ const message = errObj && typeof errObj.message === "string" ? errObj.message : `RPC error for ${pending.method}`;
14273
+ const peerError = errObj && typeof errObj.code === "number" && Number.isInteger(errObj.code) ? {
14274
+ code: errObj.code,
14275
+ ...Object.prototype.hasOwnProperty.call(errObj, "data") ? { data: errObj.data } : {}
14276
+ } : null;
14277
+ pending.reject(new AcpProtocolError(message, "rpc_error", peerError));
14278
+ return;
14279
+ }
14280
+ pending.resolve(rec.result);
14281
+ }
14282
+ failAll(error) {
14283
+ if (this.closed && this.pending.size === 0) return;
14284
+ for (const [key2, pending] of this.pending) {
14285
+ clearTimeout(pending.timer);
14286
+ pending.reject(error);
14287
+ this.pending.delete(key2);
14288
+ }
14289
+ }
14290
+ };
14291
+ }
14292
+ });
14293
+
14294
+ // src/host/session.ts
14295
+ function assertAbsoluteExistingCwd(cwd) {
14296
+ if (!cwd || typeof cwd !== "string") {
14297
+ throw new AcpProtocolError("cwd is required", "invalid_cwd");
14298
+ }
14299
+ if (!(0, import_node_path13.isAbsolute)(cwd)) {
14300
+ throw new AcpProtocolError("cwd must be an absolute path", "invalid_cwd");
14301
+ }
14302
+ let st;
14303
+ try {
14304
+ st = (0, import_node_fs2.statSync)(cwd);
14305
+ } catch {
14306
+ throw new AcpProtocolError(`cwd does not exist: ${cwd}`, "invalid_cwd");
14307
+ }
14308
+ if (!st.isDirectory()) {
14309
+ throw new AcpProtocolError(`cwd is not a directory: ${cwd}`, "invalid_cwd");
14310
+ }
14311
+ return cwd;
14312
+ }
14313
+ function isRecord(value) {
14314
+ return !!value && typeof value === "object" && !Array.isArray(value);
14315
+ }
14316
+ function asStopReason(value) {
14317
+ if (value === "end_turn" || value === "max_tokens" || value === "max_turn_requests" || value === "refusal" || value === "cancelled") {
14318
+ return value;
14319
+ }
14320
+ throw new AcpProtocolError(
14321
+ `invalid stopReason: ${String(value)}`,
14322
+ "invalid_response"
14323
+ );
14324
+ }
14325
+ function isHostRejectDecision(decision, options) {
14326
+ if (decision.outcome === "cancelled") return true;
14327
+ if (decision.outcome !== "selected") return false;
14328
+ const chosen = options.find((opt) => opt.optionId === decision.optionId);
14329
+ return chosen?.kind === "reject_once" || chosen?.kind === "reject_always";
14330
+ }
14331
+ function updateKind(raw) {
14332
+ switch (raw) {
14333
+ case "agent_message_chunk":
14334
+ case "agent_thought_chunk":
14335
+ case "tool_call":
14336
+ case "tool_call_update":
14337
+ case "plan":
14338
+ case "available_commands_update":
14339
+ return raw;
14340
+ default:
14341
+ return "unknown";
14342
+ }
14343
+ }
14344
+ function createBoundTransport(options) {
14345
+ return new AcpTransport({
14346
+ readable: options.readable,
14347
+ writable: options.writable,
14348
+ requestTimeoutMs: options.requestTimeoutMs,
14349
+ onChildExit: options.onChildExit,
14350
+ readableEndGraceMs: options.readableEndGraceMs,
14351
+ handlers: {
14352
+ onNotification: (method, params) => {
14353
+ options.getSession()?.handleAgentNotification(method, params);
14354
+ },
14355
+ onRequest: async (id, method, params) => {
14356
+ const session = options.getSession();
14357
+ if (!session) {
14358
+ return;
14359
+ }
14360
+ await session.handleAgentRequest(id, method, params);
14361
+ }
14362
+ }
14363
+ });
14364
+ }
14365
+ var import_node_fs2, import_node_path13, CANARY_TERMINAL_DENY_STATUSES, AcpHostSession;
14366
+ var init_session = __esm({
14367
+ "src/host/session.ts"() {
14368
+ "use strict";
14369
+ import_node_fs2 = require("node:fs");
14370
+ import_node_path13 = require("node:path");
14371
+ init_bounds();
14372
+ init_permission();
14373
+ init_sanitize();
14374
+ init_transport();
14375
+ init_types();
14376
+ CANARY_TERMINAL_DENY_STATUSES = /* @__PURE__ */ new Set([
14377
+ "rejected",
14378
+ "denied",
14379
+ "cancelled",
14380
+ "canceled",
14381
+ "failed",
14382
+ "error"
14383
+ ]);
14384
+ AcpHostSession = class _AcpHostSession {
14385
+ transport;
14386
+ cwd;
14387
+ permissionCallback;
14388
+ requiredModeId;
14389
+ events;
14390
+ requestTimeoutMs;
14391
+ sessionId = null;
14392
+ agentVersion;
14393
+ promptsEnabled;
14394
+ promptInFlight = false;
14395
+ closed = false;
14396
+ /**
14397
+ * Canary denial is host-authored only: we record toolCallIds we ourselves
14398
+ * rejected, then accept a bounded structured terminal status on that same id.
14399
+ * Provider free-text / error-body regex never unlocks prompts.
14400
+ */
14401
+ canaryState = {
14402
+ sawPermissionRequest: false,
14403
+ sawDeniedToolResult: false,
14404
+ rejectedToolKeys: /* @__PURE__ */ new Set()
14405
+ };
14406
+ constructor(options) {
14407
+ this.transport = options.transport;
14408
+ this.cwd = assertAbsoluteExistingCwd(options.cwd);
14409
+ this.requiredModeId = options.requiredModeId;
14410
+ this.permissionCallback = resolvePermissionCallback(options.permissionCallback);
14411
+ this.events = options.events ?? {};
14412
+ this.requestTimeoutMs = options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS;
14413
+ this.promptsEnabled = options.promptsEnabled === true;
14414
+ }
14415
+ /**
14416
+ * Wire an existing transport, run initialize + session/new, return a ready session.
14417
+ * Real prompts stay blocked until {@link enablePromptsAfterCanary} (or test opt-in).
14418
+ */
14419
+ static async connect(options) {
14420
+ const session = new _AcpHostSession(options);
14421
+ session.attachHandlers();
14422
+ await session.initialize(options.clientName, options.clientVersion);
14423
+ await session.newSession();
14424
+ return session;
14425
+ }
14426
+ /**
14427
+ * Build a session around a transport that is already initialized (tests).
14428
+ */
14429
+ static attachInitialized(options) {
14430
+ const session = new _AcpHostSession(options);
14431
+ session.attachHandlers();
14432
+ session.sessionId = options.sessionId;
14433
+ session.agentVersion = options.agentVersion;
14434
+ return session;
14435
+ }
14436
+ get info() {
14437
+ if (!this.sessionId) {
14438
+ throw new AcpProtocolError("session not opened", "no_session");
14439
+ }
14440
+ return {
14441
+ sessionId: this.sessionId,
14442
+ cwd: this.cwd,
14443
+ protocolVersion: ACP_PROTOCOL_VERSION,
14444
+ agentVersion: this.agentVersion
14445
+ };
14446
+ }
14447
+ get arePromptsEnabled() {
14448
+ return this.promptsEnabled;
14449
+ }
14450
+ get canaryObservation() {
14451
+ return { ...this.canaryState };
14452
+ }
14453
+ /**
14454
+ * Permission-boundary canary. Drives a side-effect-free probe that must
14455
+ * produce (1) a session/request_permission we answer with reject and
14456
+ * (2) a structured tool_call(_update) for that same toolCallId with a
14457
+ * bounded terminal deny status — never provider free-text matching.
14458
+ *
14459
+ * Ambient provider hooks remain outside this boundary — see permission.ts.
14460
+ * Steady-state `--permissions allow` is not proven by a deny-only canary;
14461
+ * allow_once is only selected after this gate, by the listener model.
14462
+ */
14463
+ /**
14464
+ * D-081. ONE BOUNDED RETRY, because the canary's pass condition depends on a REMOTE MODEL
14465
+ * CHOOSING to attempt a tool call, and re-prompting re-samples that choice.
14466
+ *
14467
+ * `runPermissionBoundaryCanary` resets its own observation state and sends a fresh prompt, so a
14468
+ * second call is a genuine second sample rather than a re-read of the first verdict — that is
14469
+ * what makes a retry meaningful here and it was checked before this was written.
14470
+ *
14471
+ * MITIGATION, NOT A DIAGNOSIS, and deliberately so: seven mechanisms for D-081 were proposed
14472
+ * and refuted in a single afternoon, and the cause is still not established. The precedent is
14473
+ * D-076, shipped in 0.1.11 as a bounded one-shot retry with its root cause open and documented.
14474
+ *
14475
+ * The cost is real and is recorded rather than hidden: a genuinely dead host now takes up to
14476
+ * two canary timeouts before failing. Measured first-attempt failures on this machine were 24s,
14477
+ * 25s and 9s against a 30s timeout, so a doubled worst case is a minute-scale wait. That is the
14478
+ * price of not reporting a healthy listener as failed, which is the defect being mitigated.
14479
+ *
14480
+ * It must NOT be able to hide a deterministic failure: every attempt is reported through
14481
+ * `onAttempt`, and the thrown error names how many were made and why the last one failed, so
14482
+ * "flaky, retried, ready" and "failed twice" are distinguishable in the log rather than
14483
+ * collapsing into one line.
14484
+ */
14485
+ async enablePromptsAfterCanary(options) {
14486
+ if (this.promptsEnabled) return;
14487
+ const total = Math.max(1, options?.attempts ?? 2);
14488
+ let last = null;
14489
+ for (let attempt = 1; attempt <= total; attempt += 1) {
14490
+ const result = await this.runPermissionBoundaryCanary(options);
14491
+ last = result;
14492
+ options?.onAttempt?.(attempt, total, result);
14493
+ if (result.passed) {
14494
+ this.promptsEnabled = true;
14495
+ return;
14496
+ }
14497
+ }
14498
+ const detail = last?.reason ?? "permission-boundary canary failed: need host reject + correlated terminal tool status";
14499
+ throw new AcpPermissionCanaryError(
14500
+ total === 1 ? detail : `${detail} (failed ${total} attempts)`,
14501
+ last?.reasonCode ?? null,
14502
+ null,
14503
+ last?.peerError ?? null
14504
+ );
14505
+ }
14506
+ /** Test/helper: force-enable prompts without canary (never used by production open path). */
14507
+ forceEnablePromptsForTests() {
14508
+ this.promptsEnabled = true;
14509
+ }
14510
+ /** Reset the canary gate (used after session/load fallback to session/new). */
14511
+ resetPromptGate() {
14512
+ this.promptsEnabled = false;
14513
+ this.canaryState = {
14514
+ sawPermissionRequest: false,
14515
+ sawDeniedToolResult: false,
14516
+ rejectedToolKeys: /* @__PURE__ */ new Set()
14517
+ };
14518
+ }
14519
+ /** Injective (sessionId, toolCallId) key — a delimiter alone is not, and NUL is not special. */
14520
+ canaryRejectKey(sessionId, toolCallId) {
14521
+ return JSON.stringify([sessionId, toolCallId]);
14522
+ }
14523
+ async runPermissionBoundaryCanary(options) {
14524
+ this.canaryState = {
14525
+ sawPermissionRequest: false,
14526
+ sawDeniedToolResult: false,
14527
+ rejectedToolKeys: /* @__PURE__ */ new Set()
14528
+ };
14529
+ const probe = options?.probeText ?? "cswarm-permission-boundary-canary-v2: if your policy requires a tool permission request, issue one for a no-op check only; do not create, edit, delete, or read any project files; stop after the permission path. Sentinel=CSWARM_CANARY_NOOP";
14530
+ try {
14531
+ const promptResult = await this.promptInternal(probe, {
14532
+ timeoutMs: options?.timeoutMs,
14533
+ bypassCanaryGate: true
14534
+ });
14535
+ const passed = this.canaryState.sawPermissionRequest && this.canaryState.sawDeniedToolResult;
14536
+ return {
14537
+ passed,
14538
+ sawPermissionRequest: this.canaryState.sawPermissionRequest,
14539
+ sawDeniedToolResult: this.canaryState.sawDeniedToolResult,
14540
+ stopReason: promptResult.stopReason,
14541
+ reason: passed ? void 0 : `canary incomplete: permission=${this.canaryState.sawPermissionRequest} deniedTool=${this.canaryState.sawDeniedToolResult}`
14542
+ };
14543
+ } catch (err) {
14544
+ return {
14545
+ passed: false,
14546
+ sawPermissionRequest: this.canaryState.sawPermissionRequest,
14547
+ sawDeniedToolResult: this.canaryState.sawDeniedToolResult,
14548
+ reason: err instanceof Error ? err.message : String(err),
14549
+ ...err instanceof AcpHostError ? { reasonCode: err.code } : {},
14550
+ ...err instanceof AcpProtocolError && err.peerError ? { peerError: err.peerError } : {}
14551
+ };
14552
+ }
14553
+ }
14554
+ /**
14555
+ * After a successful canary on a throwaway cwd, open a new ACP session on the
14556
+ * real work cwd without re-probing tools in that tree. Same child/host
14557
+ * permission path remains in force.
14558
+ */
14559
+ async openWorkCwd(cwd) {
14560
+ this.assertOpen();
14561
+ if (!this.promptsEnabled) {
14562
+ throw new AcpPromptsBlockedError();
14563
+ }
14564
+ this.cwd = assertAbsoluteExistingCwd(cwd);
14565
+ await this.newSession();
14566
+ }
14567
+ async prompt(text, options) {
14568
+ if (!this.promptsEnabled) {
14569
+ throw new AcpPromptsBlockedError();
14570
+ }
14571
+ return this.promptInternal(text, { ...options, bypassCanaryGate: false });
14572
+ }
14573
+ /**
14574
+ * session/cancel as a notification — no JSON-RPC id.
14575
+ */
14576
+ cancel() {
14577
+ if (!this.sessionId) {
14578
+ throw new AcpProtocolError("session not opened", "no_session");
14579
+ }
14580
+ this.transport.notify("session/cancel", { sessionId: this.sessionId });
14581
+ }
14582
+ /**
14583
+ * session/load with fixed cwd and empty mcpServers.
14584
+ * On failure, falls back to session/new and returns the new session id.
14585
+ */
14586
+ async load(sessionId) {
14587
+ this.assertOpen();
14588
+ try {
14589
+ const result = await this.transport.request(
14590
+ "session/load",
14591
+ {
14592
+ sessionId,
14593
+ cwd: this.cwd,
14594
+ mcpServers: []
14595
+ },
14596
+ this.requestTimeoutMs
14597
+ );
14598
+ const resultIsEmptySuccess = result === null || result === void 0;
14599
+ const resultRecord = isRecord(result) ? result : null;
14600
+ if (!resultIsEmptySuccess && resultRecord === null) {
14601
+ throw new AcpProtocolError(
14602
+ "session/load returned an unrecognised result shape",
14603
+ "session_load_malformed"
14604
+ );
14605
+ }
14606
+ const sessionIdAbsent = resultRecord === null || !("sessionId" in resultRecord) || resultRecord.sessionId === void 0;
14607
+ if (sessionIdAbsent) {
14608
+ this.sessionId = sessionId;
14609
+ await this.applyRequiredMode();
14610
+ return { sessionId, loaded: true };
14611
+ }
14612
+ if (typeof resultRecord.sessionId !== "string") {
14613
+ throw new AcpProtocolError(
14614
+ "session/load returned a non-string session id",
14615
+ "session_id_malformed"
14616
+ );
14617
+ }
14618
+ if (resultRecord.sessionId !== sessionId) {
14619
+ throw new AcpProtocolError(
14620
+ "session/load returned a different session id",
14621
+ "session_id_mismatch"
14622
+ );
14623
+ }
14624
+ this.sessionId = resultRecord.sessionId;
14625
+ await this.applyRequiredMode();
14626
+ return { sessionId: resultRecord.sessionId, loaded: true };
14627
+ } catch {
14628
+ this.resetPromptGate();
14629
+ await this.newSession();
14630
+ return { sessionId: this.sessionId, loaded: false };
14631
+ }
14632
+ }
14633
+ async close() {
14634
+ if (this.closed) return;
14635
+ this.closed = true;
14636
+ this.transport.close();
14637
+ }
14638
+ attachHandlers() {
14639
+ }
14640
+ /**
14641
+ * Install request/notification handlers on a transport for this session.
14642
+ * Called by factories after construction.
14643
+ */
14644
+ bindTransportHandlers() {
14645
+ }
14646
+ /** Handle agent→client request. Public for transport wiring. */
14647
+ async handleAgentRequest(id, method, params) {
14648
+ if (method === "session/request_permission") {
14649
+ await this.handlePermissionRequest(id, params);
14650
+ return;
14651
+ }
14652
+ this.transport.respondError(id, -32601, `Method not supported by host: ${method}`);
14653
+ }
14654
+ /** Handle agent notification. Public for transport wiring. */
14655
+ handleAgentNotification(method, params) {
14656
+ if (method === "session/update") {
14657
+ this.handleSessionUpdate(params);
14658
+ return;
14659
+ }
14660
+ this.events.notification?.(method, params);
14661
+ }
14662
+ async initialize(clientName, clientVersion) {
14663
+ const result = await this.transport.request(
14664
+ "initialize",
14665
+ {
14666
+ protocolVersion: ACP_PROTOCOL_VERSION,
14667
+ clientCapabilities: {
14668
+ fs: { readTextFile: false, writeTextFile: false },
14669
+ terminal: false
14670
+ },
14671
+ clientInfo: {
14672
+ name: clientName ?? "cswarm-host",
14673
+ /* "0.0.0" and not a real release number: this fallback only fires when a caller
14674
+ * passes no version, and a hardcoded one here silently rots (it read "0.1.4"
14675
+ * thirteen releases later). Callers that know the build version pass it. */
14676
+ version: clientVersion ?? "0.0.0"
14677
+ }
14678
+ },
14679
+ this.requestTimeoutMs
14680
+ );
14681
+ if (!isRecord(result)) {
14682
+ throw new AcpProtocolError("initialize returned non-object", "invalid_response");
14683
+ }
14684
+ if (result.protocolVersion !== ACP_PROTOCOL_VERSION) {
14685
+ throw new AcpProtocolError(
14686
+ `unsupported protocolVersion ${String(result.protocolVersion)}`,
14687
+ "protocol_version"
14688
+ );
14689
+ }
14690
+ const meta = isRecord(result._meta) ? result._meta : void 0;
14691
+ if (meta && typeof meta.agentVersion === "string") {
14692
+ this.agentVersion = meta.agentVersion;
14693
+ }
14694
+ }
14695
+ async newSession() {
14696
+ const result = await this.transport.request(
14697
+ "session/new",
14698
+ {
14699
+ cwd: this.cwd,
14700
+ mcpServers: [],
14701
+ _meta: { yoloMode: false }
14702
+ },
14703
+ this.requestTimeoutMs
14704
+ );
14705
+ if (!isRecord(result) || typeof result.sessionId !== "string" || !result.sessionId) {
14706
+ throw new AcpProtocolError("session/new missing sessionId", "invalid_response");
14707
+ }
14708
+ this.sessionId = result.sessionId;
14709
+ await this.applyRequiredMode(result);
14710
+ }
14711
+ /** Select the provider-measured permission mode and fail closed if absent. */
14712
+ async applyRequiredMode(newSessionResult) {
14713
+ const requiredModeId = this.requiredModeId;
14714
+ if (!requiredModeId) return;
14715
+ if (!this.sessionId) {
14716
+ throw new AcpProtocolError("session mode requires an open session", "no_session");
14717
+ }
14718
+ if (newSessionResult) {
14719
+ const modes = isRecord(newSessionResult.modes) ? newSessionResult.modes : null;
14720
+ const availableModes = modes && Array.isArray(modes.availableModes) ? modes.availableModes : [];
14721
+ const available = availableModes.some(
14722
+ (mode3) => isRecord(mode3) && mode3.id === requiredModeId
14723
+ );
14724
+ if (!available) {
14725
+ throw new AcpProtocolError(
14726
+ `required session mode is unavailable: ${requiredModeId}`,
14727
+ "permission_mode_unavailable"
14728
+ );
14729
+ }
14730
+ }
14731
+ try {
14732
+ await this.transport.request(
14733
+ "session/set_mode",
14734
+ { sessionId: this.sessionId, modeId: requiredModeId },
14735
+ this.requestTimeoutMs
14736
+ );
14737
+ } catch {
14738
+ throw new AcpProtocolError(
14739
+ `required session mode could not be selected: ${requiredModeId}`,
14740
+ "permission_mode_unavailable"
14741
+ );
14742
+ }
14743
+ }
14744
+ async promptInternal(text, options) {
14745
+ this.assertOpen();
14746
+ if (!options.bypassCanaryGate && !this.promptsEnabled) {
14747
+ throw new AcpPromptsBlockedError();
14748
+ }
14749
+ if (this.promptInFlight) {
14750
+ throw new AcpProtocolError("prompt already in flight (sequential only)", "busy");
14751
+ }
14752
+ if (typeof text !== "string") {
14753
+ throw new AcpProtocolError("prompt text must be a string", "invalid_prompt");
14754
+ }
14755
+ this.promptInFlight = true;
14756
+ const updates = [];
14757
+ let message = "";
14758
+ const prev = this.events.update;
14759
+ this.events.update = (u) => {
14760
+ updates.push(u);
14761
+ const fromOurSession = this.sessionId === null || u.sessionId === this.sessionId;
14762
+ if (u.kind === "agent_message_chunk" && u.text && fromOurSession) {
14763
+ if (message.length + u.text.length > ACP_MAX_ACCUMULATED_TEXT_CHARS) {
14764
+ throw new AcpProtocolError(
14765
+ "accumulated agent message exceeds bound",
14766
+ "message_too_large"
14767
+ );
14768
+ }
14769
+ message += u.text;
14770
+ }
14771
+ prev?.(u);
14772
+ };
14773
+ try {
14774
+ const result = await this.transport.request(
14775
+ "session/prompt",
14776
+ {
14777
+ sessionId: this.sessionId,
14778
+ prompt: [{ type: "text", text }]
14779
+ },
14780
+ options.timeoutMs ?? this.requestTimeoutMs
14781
+ );
14782
+ if (!isRecord(result) || !("stopReason" in result)) {
14783
+ throw new AcpProtocolError("session/prompt missing stopReason", "invalid_response");
14784
+ }
14785
+ const stopReason = asStopReason(result.stopReason);
14786
+ return { stopReason, message, updates };
14787
+ } finally {
14788
+ this.events.update = prev;
14789
+ this.promptInFlight = false;
14790
+ }
14791
+ }
14792
+ handleSessionUpdate(params) {
14793
+ if (!isRecord(params)) return;
14794
+ const claimedSessionId = typeof params.sessionId === "string" ? params.sessionId : null;
14795
+ const sessionId = claimedSessionId ?? "";
14796
+ const update = params.update;
14797
+ if (!isRecord(update)) return;
14798
+ const kind = updateKind(update.sessionUpdate);
14799
+ if (kind === "unknown") {
14800
+ this.events.notification?.("session/update", params);
14801
+ return;
14802
+ }
14803
+ let text;
14804
+ if (kind === "agent_message_chunk" || kind === "agent_thought_chunk") {
14805
+ const content = update.content;
14806
+ if (isRecord(content) && content.type === "text" && typeof content.text === "string") {
14807
+ text = sanitizeText(content.text);
14808
+ }
14809
+ }
14810
+ const toolCallId = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
14811
+ const title = typeof update.title === "string" ? sanitizeText(update.title) : void 0;
14812
+ const status = typeof update.status === "string" ? update.status : void 0;
14813
+ const toolKind = typeof update.kind === "string" ? update.kind : void 0;
14814
+ if ((kind === "tool_call_update" || kind === "tool_call") && toolCallId && status && this.sessionId !== null && claimedSessionId !== null && claimedSessionId === this.sessionId && this.canaryState.rejectedToolKeys.has(
14815
+ this.canaryRejectKey(sessionId, toolCallId)
14816
+ ) && CANARY_TERMINAL_DENY_STATUSES.has(status.toLowerCase())) {
14817
+ this.canaryState.sawDeniedToolResult = true;
14818
+ }
14819
+ const detail = sanitizeUpdateDetail({
14820
+ ...toolKind ? { kind: toolKind } : {},
14821
+ ...status ? { status } : {},
14822
+ ...title ? { title } : {}
14823
+ });
14824
+ const sanitized = {
14825
+ kind,
14826
+ sessionId,
14827
+ text,
14828
+ toolCallId,
14829
+ title,
14830
+ status,
14831
+ toolKind,
14832
+ detail
14833
+ };
14834
+ this.events.update?.(sanitized);
14835
+ }
14836
+ async handlePermissionRequest(id, params) {
14837
+ const rec = isRecord(params) ? params : {};
14838
+ const claimedSessionId = typeof rec.sessionId === "string" ? rec.sessionId : null;
14839
+ const sessionId = claimedSessionId ?? "";
14840
+ const options = parsePermissionOptions(rec.options);
14841
+ const toolCall = isRecord(rec.toolCall) ? rec.toolCall : {};
14842
+ const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : void 0;
14843
+ const title = typeof toolCall.title === "string" ? toolCall.title : void 0;
14844
+ const kind = typeof toolCall.kind === "string" ? toolCall.kind : void 0;
14845
+ const sessionMatches = this.sessionId !== null && claimedSessionId !== null && claimedSessionId === this.sessionId;
14846
+ if (sessionMatches) {
14847
+ this.canaryState.sawPermissionRequest = true;
14848
+ }
14849
+ if (!sessionMatches) {
14850
+ this.transport.respond(
14851
+ id,
14852
+ permissionDecisionToResult(defaultPermissionCallback({
14853
+ sessionId,
14854
+ toolCallId,
14855
+ title,
14856
+ kind,
14857
+ options,
14858
+ summary: sanitizeText(
14859
+ [kind, title, toolCallId].filter(Boolean).join(" ") || "permission request"
14860
+ )
14861
+ }))
14862
+ );
14863
+ return;
14864
+ }
14865
+ const summary = sanitizeText(
14866
+ [kind, title, toolCallId].filter(Boolean).join(" ") || "permission request"
14867
+ );
14868
+ let decision;
14869
+ try {
14870
+ decision = await this.permissionCallback({
14871
+ sessionId,
14872
+ toolCallId,
14873
+ title,
14874
+ kind,
14875
+ options,
14876
+ summary
14877
+ });
14878
+ } catch {
14879
+ decision = defaultPermissionCallback({
14880
+ sessionId,
14881
+ toolCallId,
14882
+ title,
14883
+ kind,
14884
+ options,
14885
+ summary
14886
+ });
14887
+ }
14888
+ if (!decision || decision.outcome !== "cancelled" && decision.outcome !== "selected") {
14889
+ decision = defaultPermissionCallback({
14890
+ sessionId,
14891
+ toolCallId,
14892
+ title,
14893
+ kind,
14894
+ options,
14895
+ summary
14896
+ });
14897
+ }
14898
+ if (sessionMatches && toolCallId && isHostRejectDecision(decision, options)) {
14899
+ this.canaryState.rejectedToolKeys.add(
14900
+ this.canaryRejectKey(sessionId, toolCallId)
14901
+ );
14902
+ }
14903
+ const result = permissionDecisionToResult(decision);
14904
+ this.transport.respond(id, result);
14905
+ }
14906
+ assertOpen() {
14907
+ if (this.closed) {
14908
+ throw new AcpProtocolError("session closed", "closed");
14909
+ }
14910
+ if (!this.sessionId) {
14911
+ throw new AcpProtocolError("session not opened", "no_session");
14912
+ }
14913
+ }
14914
+ };
14915
+ }
14916
+ });
14917
+
14918
+ // src/host/claude.ts
14919
+ var claude_exports = {};
14920
+ __export(claude_exports, {
14921
+ CLAUDE_ACP_LAST_MEASURED_VERSION: () => CLAUDE_ACP_LAST_MEASURED_VERSION,
14922
+ CLAUDE_ACP_MIN_VERSION: () => CLAUDE_ACP_MIN_VERSION,
14923
+ CLAUDE_PERMISSION_MODE_ID: () => CLAUDE_PERMISSION_MODE_ID,
14924
+ assertClaudeVersionFloor: () => assertClaudeVersionFloor,
14925
+ buildClaudeAcpArgs: () => buildClaudeAcpArgs,
14926
+ buildClaudeChildEnv: () => buildClaudeChildEnv,
14927
+ buildClaudeLaunch: () => buildClaudeLaunch,
14928
+ inspectClaudeBridgeExecutable: () => inspectClaudeBridgeExecutable,
14929
+ measureClaudeBundleVersions: () => measureClaudeBundleVersions,
14930
+ openClaudeAcpSession: () => openClaudeAcpSession,
14931
+ parseClaudeCodeVersionOutput: () => parseClaudeCodeVersionOutput,
14932
+ parseClaudeVersionOutput: () => parseClaudeVersionOutput,
14933
+ resolveClaudeExecutable: () => resolveClaudeExecutable,
14934
+ resolvePackagedClaudeBridge: () => resolvePackagedClaudeBridge,
14935
+ terminateClaudeChild: () => terminateClaudeChild
14936
+ });
14937
+ function isPackagedClaudeBridge(executable) {
14938
+ const normalized = executable.replaceAll("\\", "/");
14939
+ return normalized.endsWith(
14940
+ "/node_modules/@agentclientprotocol/claude-agent-acp/dist/index.js"
14941
+ );
14942
+ }
14943
+ function resolvePackagedClaudeBridge(pathEnv, platform = process.platform) {
14944
+ const pathValue = pathEnv ?? process.env.PATH ?? "";
14945
+ const names = platform === "win32" ? ["claude-agent-acp.cmd"] : ["claude-agent-acp"];
14946
+ for (const dir of pathValue.split(import_node_path14.delimiter)) {
14947
+ if (!dir) continue;
14948
+ for (const name of names) {
14949
+ try {
14950
+ const candidate = resolvedClaudeCandidate((0, import_node_path14.join)(dir, name), platform);
14951
+ if (isPackagedClaudeBridge(candidate)) return candidate;
14952
+ } catch {
14953
+ }
14954
+ }
14955
+ }
14956
+ throw new AcpHostError(
14957
+ "executable_missing",
14958
+ "packaged claude-agent-acp executable not found; install @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2)"
14959
+ );
14960
+ }
14961
+ function resolveWindowsNpmShim(shim) {
14962
+ let source;
14963
+ try {
14964
+ source = (0, import_node_fs3.readFileSync)(shim, "utf8");
14965
+ } catch {
14966
+ throw new AcpHostError(
14967
+ "executable_missing",
14968
+ `could not read claude-agent-acp npm shim: ${shim}`
14969
+ );
14970
+ }
14971
+ if (Buffer.byteLength(source, "utf8") > WINDOWS_NPM_SHIM_MAX_BYTES || !source.includes(
14972
+ String.raw`"%dp0%\node_modules\@agentclientprotocol\claude-agent-acp\dist\index.js"`
14973
+ )) {
14974
+ throw new AcpHostError(
14975
+ "executable_missing",
14976
+ `unrecognized claude-agent-acp npm shim: ${shim}`
14977
+ );
14978
+ }
14979
+ const target2 = (0, import_node_path14.join)((0, import_node_path14.dirname)(shim), ...WINDOWS_NPM_ENTRYPOINT);
14980
+ try {
14981
+ (0, import_node_fs3.accessSync)(target2, import_node_fs3.constants.R_OK);
14982
+ return (0, import_node_fs3.realpathSync)(target2);
14983
+ } catch {
14984
+ throw new AcpHostError(
14985
+ "executable_missing",
14986
+ `claude-agent-acp package entrypoint is missing beside npm shim: ${shim}`
14987
+ );
14988
+ }
14989
+ }
14990
+ function resolvedClaudeCandidate(candidate, platform) {
14991
+ (0, import_node_fs3.accessSync)(candidate, import_node_fs3.constants.X_OK);
14992
+ const real = (0, import_node_fs3.realpathSync)(candidate);
14993
+ return platform === "win32" && (0, import_node_path14.extname)(real).toLowerCase() === ".cmd" ? resolveWindowsNpmShim(real) : real;
14994
+ }
14995
+ function resolveClaudeExecutable(executable = "claude-agent-acp", pathEnv, platform = process.platform) {
14996
+ if ((0, import_node_path14.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
14997
+ const abs = (0, import_node_path14.resolve)(executable);
14998
+ const candidates = platform === "win32" && (0, import_node_path14.extname)(abs) === "" ? [`${abs}.cmd`] : [abs];
14999
+ for (const candidate of candidates) {
15000
+ try {
15001
+ return resolvedClaudeCandidate(candidate, platform);
15002
+ } catch (error) {
15003
+ if (error instanceof AcpHostError) throw error;
15004
+ }
15005
+ }
15006
+ throw new AcpHostError("executable_missing", `not executable: ${abs}`);
15007
+ }
15008
+ const pathValue = pathEnv ?? process.env.PATH ?? "";
15009
+ const names = platform === "win32" && (0, import_node_path14.extname)(executable) === "" ? [`${executable}.cmd`] : [executable];
15010
+ for (const dir of pathValue.split(import_node_path14.delimiter)) {
15011
+ if (!dir) continue;
15012
+ for (const name of names) {
15013
+ const candidate = (0, import_node_path14.join)(dir, name);
15014
+ try {
15015
+ return resolvedClaudeCandidate(candidate, platform);
15016
+ } catch (error) {
15017
+ if (error instanceof AcpHostError) throw error;
15018
+ }
15019
+ }
15020
+ }
15021
+ throw new AcpHostError(
15022
+ "executable_missing",
15023
+ `claude-agent-acp executable not found on PATH: ${executable}`
15024
+ );
15025
+ }
15026
+ function buildClaudeLaunch(executable, args, platform = process.platform) {
15027
+ return platform === "win32" && (0, import_node_path14.extname)(executable).toLowerCase() === ".js" ? { command: process.execPath, args: [executable, ...args] } : { command: executable, args: [...args] };
15028
+ }
15029
+ function parseClaudeVersionOutput(stdout) {
15030
+ return parseProviderVersionOutput(stdout, /\bclaude-agent-acp\b/i);
15031
+ }
15032
+ function parseClaudeCodeVersionOutput(stdout) {
15033
+ return parseProviderVersionOutput(stdout, /\bClaude Code\b/i, false);
15034
+ }
15035
+ function semanticVersion(value) {
15036
+ if (typeof value !== "string") return null;
15037
+ return parseProviderVersionOutput(`${value}
15038
+ `, /\bnever-a-product-name\b/i);
15039
+ }
15040
+ function readPackageAtOrAbove(entrypoint, expectedName) {
15041
+ let directory = (0, import_node_path14.dirname)(entrypoint);
15042
+ for (let depth = 0; depth < 5; depth += 1) {
15043
+ const path = (0, import_node_path14.join)(directory, "package.json");
15044
+ try {
15045
+ const row = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
15046
+ if (row && typeof row === "object" && !Array.isArray(row) && row.name === expectedName) {
15047
+ return { path, row };
15048
+ }
15049
+ } catch {
15050
+ }
15051
+ const parent = (0, import_node_path14.dirname)(directory);
15052
+ if (parent === directory) break;
15053
+ directory = parent;
15054
+ }
15055
+ return null;
15056
+ }
15057
+ function measureClaudeBundleVersions(executable) {
15058
+ const adapter = readPackageAtOrAbove(
15059
+ executable,
15060
+ "@agentclientprotocol/claude-agent-acp"
15061
+ );
15062
+ if (!adapter) return { agentSdkVersion: null, claudeCodeVersion: null };
15063
+ try {
15064
+ const sdkEntrypoint = (0, import_node_module.createRequire)(adapter.path).resolve(
15065
+ "@anthropic-ai/claude-agent-sdk"
15066
+ );
15067
+ const sdk = readPackageAtOrAbove(
15068
+ sdkEntrypoint,
15069
+ "@anthropic-ai/claude-agent-sdk"
15070
+ );
15071
+ if (!sdk) return { agentSdkVersion: null, claudeCodeVersion: null };
15072
+ return {
15073
+ agentSdkVersion: semanticVersion(sdk.row.version),
15074
+ claudeCodeVersion: semanticVersion(sdk.row.claudeCodeVersion)
15075
+ };
15076
+ } catch {
15077
+ return { agentSdkVersion: null, claudeCodeVersion: null };
15078
+ }
15079
+ }
15080
+ async function readClaudeVersionOutput(executable, options) {
15081
+ const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
15082
+ const env = options?.env ?? sanitizeChildEnv(process.env);
15083
+ const launch = buildClaudeLaunch(executable, ["--version"], options?.platform);
15084
+ return await new Promise((resolve4, reject) => {
15085
+ (0, import_node_child_process5.execFile)(
15086
+ launch.command,
15087
+ launch.args,
15088
+ { timeout: timeoutMs, encoding: "utf8", env },
15089
+ (error, out, stderr) => {
15090
+ if (error) {
15091
+ reject(
15092
+ new AcpVersionError(
15093
+ `failed to run ${executable} --version: ${error.message}${stderr ? ` (${stderr.trim()})` : ""}`
15094
+ )
15095
+ );
15096
+ return;
15097
+ }
15098
+ resolve4(out);
15099
+ }
15100
+ );
15101
+ });
15102
+ }
15103
+ async function inspectClaudeBridgeExecutable(executable = "claude-agent-acp", options) {
15104
+ const resolved = resolveClaudeExecutable(
15105
+ executable,
15106
+ options?.pathEnv,
15107
+ options?.platform
15108
+ );
15109
+ const output = await readClaudeVersionOutput(resolved, options);
15110
+ const bundle = measureClaudeBundleVersions(resolved);
15111
+ return {
15112
+ executable: resolved,
15113
+ providerVersion: parseClaudeVersionOutput(output),
15114
+ lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
15115
+ bundledAgentSdkVersion: bundle.agentSdkVersion,
15116
+ bundledClaudeCodeVersion: bundle.claudeCodeVersion
15117
+ };
15118
+ }
15119
+ async function assertClaudeVersionFloor(executable, options) {
15120
+ const minimumVersion = options?.minimumVersion ?? CLAUDE_ACP_MIN_VERSION;
15121
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? CLAUDE_ACP_LAST_MEASURED_VERSION;
15122
+ const stdout = await readClaudeVersionOutput(executable, options);
15123
+ const version3 = parseClaudeVersionOutput(stdout);
15124
+ if (!version3) {
15125
+ throw new AcpVersionParseError(
15126
+ `could not parse claude-agent-acp version from: ${stdout.trim().slice(0, 200)}`
15127
+ );
15128
+ }
15129
+ assertProviderVersionFloor({
15130
+ provider: "claude-agent-acp",
15131
+ version: version3,
15132
+ minimumVersion,
15133
+ lastMeasuredVersion,
15134
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
15135
+ });
15136
+ return version3;
15137
+ }
15138
+ function buildClaudeAcpArgs() {
15139
+ return [];
15140
+ }
15141
+ function buildClaudeChildEnv(parent, claudeCodeExecutable) {
15142
+ const env = sanitizeChildEnv(parent);
15143
+ if (claudeCodeExecutable) {
15144
+ env.CLAUDE_CODE_EXECUTABLE = claudeCodeExecutable;
15145
+ }
15146
+ return env;
15147
+ }
15148
+ function waitForChildExit(child, timeoutMs) {
15149
+ if (child.exitCode !== null || child.signalCode !== null) {
15150
+ return Promise.resolve();
15151
+ }
15152
+ return new Promise((resolve4) => {
15153
+ const timer2 = setTimeout(resolve4, timeoutMs);
15154
+ child.once("exit", () => {
15155
+ clearTimeout(timer2);
15156
+ resolve4();
15157
+ });
15158
+ });
15159
+ }
15160
+ async function terminateClaudeChild(child) {
15161
+ if (child.exitCode !== null || child.signalCode !== null) return;
15162
+ try {
15163
+ child.kill("SIGTERM");
15164
+ } catch {
15165
+ }
15166
+ await waitForChildExit(child, CHILD_EXIT_WAIT_MS);
15167
+ if (child.exitCode !== null || child.signalCode !== null) return;
15168
+ try {
15169
+ child.kill("SIGKILL");
15170
+ } catch {
15171
+ }
15172
+ await waitForChildExit(child, CHILD_KILL_WAIT_MS);
15173
+ if (child.exitCode === null && child.signalCode === null) {
15174
+ throw new AcpHostError(
15175
+ "child_exit_timeout",
15176
+ "Claude ACP bridge did not exit after SIGTERM and SIGKILL"
15177
+ );
15178
+ }
15179
+ }
15180
+ async function openClaudeAcpSession(options) {
15181
+ const parentEnv = options.env ?? process.env;
15182
+ if (options.signal?.aborted) {
15183
+ throw new AcpHostError(
15184
+ "cancelled",
15185
+ "Claude ACP bridge opening was cancelled"
15186
+ );
15187
+ }
15188
+ const pathEnv = parentEnv.PATH;
15189
+ const resolvedPathEnv = typeof pathEnv === "string" ? pathEnv : void 0;
15190
+ if (options.skipVersionCheck && options.executable) {
15191
+ throw new AcpHostError(
15192
+ "version_check_required",
15193
+ "skipVersionCheck cannot classify an explicit Claude executable"
15194
+ );
15195
+ }
15196
+ const requestedExecutable = options.executable ? resolveClaudeExecutable(options.executable, resolvedPathEnv) : null;
15197
+ const baseEnv = buildClaudeChildEnv(parentEnv);
15198
+ let executable;
15199
+ let env = baseEnv;
15200
+ let claudeCodeExecutable;
15201
+ let providerVersion;
15202
+ let bundleVersions = {
15203
+ agentSdkVersion: null,
15204
+ claudeCodeVersion: null
15205
+ };
15206
+ const reportRuntime = (resolved, version3) => {
15207
+ bundleVersions = measureClaudeBundleVersions(resolved);
15208
+ options.onRuntimeNotice?.({
15209
+ executable: resolved,
15210
+ providerVersion: version3,
15211
+ lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
15212
+ bundledAgentSdkVersion: bundleVersions.agentSdkVersion,
15213
+ bundledClaudeCodeVersion: bundleVersions.claudeCodeVersion
15214
+ });
15215
+ };
15216
+ const admitBridgeVersion = (resolved, version3) => {
15217
+ providerVersion = version3;
15218
+ reportRuntime(resolved, version3);
15219
+ assertProviderVersionFloor({
15220
+ provider: "claude-agent-acp",
15221
+ version: version3,
15222
+ minimumVersion: CLAUDE_ACP_MIN_VERSION,
15223
+ lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
15224
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
15225
+ });
15226
+ };
15227
+ if (requestedExecutable) {
15228
+ const output = await readClaudeVersionOutput(requestedExecutable, {
15229
+ env: baseEnv
15230
+ });
15231
+ const bridgeVersion = parseClaudeVersionOutput(output);
15232
+ if (bridgeVersion) {
15233
+ executable = requestedExecutable;
15234
+ admitBridgeVersion(executable, bridgeVersion);
15235
+ } else if (parseClaudeCodeVersionOutput(output)) {
15236
+ claudeCodeExecutable = requestedExecutable;
15237
+ executable = resolvePackagedClaudeBridge(resolvedPathEnv);
15238
+ env = buildClaudeChildEnv(parentEnv, claudeCodeExecutable);
15239
+ const bridgeOutput = await readClaudeVersionOutput(executable, {
15240
+ env
15241
+ });
15242
+ const packagedVersion = parseClaudeVersionOutput(bridgeOutput);
15243
+ if (!packagedVersion) {
15244
+ reportRuntime(executable, null);
15245
+ throw new AcpVersionParseError(
15246
+ `could not parse claude-agent-acp version from: ${bridgeOutput.trim().slice(0, 200)}`
15247
+ );
15248
+ }
15249
+ admitBridgeVersion(executable, packagedVersion);
15250
+ } else {
15251
+ reportRuntime(requestedExecutable, null);
15252
+ throw new AcpVersionError(
15253
+ `could not identify Claude executable from: ${output.trim().slice(0, 200)}`
15254
+ );
15255
+ }
15256
+ } else {
15257
+ executable = requestedExecutable ?? resolveClaudeExecutable(
15258
+ "claude-agent-acp",
15259
+ resolvedPathEnv
15260
+ );
15261
+ if (!options.skipVersionCheck) {
15262
+ const output = await readClaudeVersionOutput(executable, {
15263
+ env: baseEnv
15264
+ });
15265
+ const version3 = parseClaudeVersionOutput(output);
15266
+ if (!version3) {
15267
+ reportRuntime(executable, null);
15268
+ throw new AcpVersionParseError(
15269
+ `could not parse claude-agent-acp version from: ${output.trim().slice(0, 200)}`
15270
+ );
15271
+ }
15272
+ admitBridgeVersion(executable, version3);
15273
+ } else {
15274
+ reportRuntime(executable, null);
15275
+ }
15276
+ }
15277
+ if (options.signal?.aborted) {
15278
+ throw new AcpHostError(
15279
+ "cancelled",
15280
+ "Claude ACP bridge opening was cancelled"
15281
+ );
15282
+ }
15283
+ const args = buildClaudeAcpArgs();
15284
+ const launch = buildClaudeLaunch(executable, args);
15285
+ const child = (0, import_node_child_process5.spawn)(launch.command, launch.args, {
15286
+ stdio: ["pipe", "pipe", "pipe"],
15287
+ env,
15288
+ cwd: options.cwd
15289
+ });
15290
+ const spawnError = new Promise((_resolve, reject) => {
15291
+ child.once("error", () => {
15292
+ reject(
15293
+ new AcpHostError(
15294
+ "spawn_failed",
15295
+ "failed to spawn the Claude ACP bridge"
15296
+ )
15297
+ );
15298
+ });
15299
+ });
15300
+ let removeAbortListener = () => void 0;
15301
+ const abortError = new Promise((_resolve, reject) => {
15302
+ const signal = options.signal;
15303
+ if (!signal) return;
15304
+ const onAbort = () => {
15305
+ reject(
15306
+ new AcpHostError(
15307
+ "cancelled",
15308
+ "Claude ACP bridge opening was cancelled"
15309
+ )
15310
+ );
15311
+ };
15312
+ if (signal.aborted) {
15313
+ onAbort();
15314
+ return;
15315
+ }
15316
+ signal.addEventListener("abort", onAbort, { once: true });
15317
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
15318
+ });
15319
+ if (!child.stdin || !child.stdout) {
15320
+ await terminateClaudeChild(child);
15321
+ throw new AcpHostError("spawn_failed", "child missing stdio pipes");
15322
+ }
15323
+ const observeStderrTailOnExit = attachStderrTailExitObserver(
15324
+ child,
15325
+ options.onStderrTail
15326
+ );
15327
+ let sessionRef = null;
15328
+ const transport = createBoundTransport({
15329
+ readable: child.stdout,
15330
+ writable: child.stdin,
15331
+ requestTimeoutMs: options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS,
15332
+ readableEndGraceMs: STDERR_READABLE_END_GRACE_MS,
15333
+ getSession: () => sessionRef,
15334
+ onChildExit: observeStderrTailOnExit
15335
+ });
15336
+ try {
15337
+ const session = await Promise.race([
15338
+ AcpHostSession.connect({
15339
+ transport,
15340
+ cwd: options.cwd,
15341
+ requiredModeId: CLAUDE_PERMISSION_MODE_ID,
15342
+ permissionCallback: options.permissionCallback,
15343
+ events: options.events,
15344
+ requestTimeoutMs: options.requestTimeoutMs,
15345
+ clientName: options.clientName,
15346
+ clientVersion: options.clientVersion,
15347
+ promptsEnabled: options.promptsEnabled
15348
+ }),
15349
+ spawnError,
15350
+ abortError
15351
+ ]);
15352
+ removeAbortListener();
15353
+ sessionRef = session;
15354
+ let closePromise = null;
15355
+ const close = async () => {
15356
+ if (closePromise) return closePromise;
15357
+ closePromise = (async () => {
15358
+ try {
15359
+ await session.close();
15360
+ } finally {
15361
+ await terminateClaudeChild(child);
15362
+ }
15363
+ })();
15364
+ return closePromise;
15365
+ };
15366
+ return {
15367
+ session,
15368
+ child,
15369
+ executable,
15370
+ args,
15371
+ env,
15372
+ ...providerVersion ? { providerVersion } : {},
15373
+ ...bundleVersions.agentSdkVersion ? { bundledAgentSdkVersion: bundleVersions.agentSdkVersion } : {},
15374
+ ...bundleVersions.claudeCodeVersion ? { bundledClaudeCodeVersion: bundleVersions.claudeCodeVersion } : {},
15375
+ close
15376
+ };
15377
+ } catch (error) {
15378
+ removeAbortListener();
15379
+ transport.close();
15380
+ await terminateClaudeChild(child);
15381
+ throw error;
15382
+ }
15383
+ }
15384
+ var import_node_child_process5, import_node_module, import_node_fs3, import_node_path14, CHILD_EXIT_WAIT_MS, CHILD_KILL_WAIT_MS, WINDOWS_NPM_SHIM_MAX_BYTES, WINDOWS_NPM_ENTRYPOINT;
15385
+ var init_claude = __esm({
15386
+ "src/host/claude.ts"() {
15387
+ "use strict";
15388
+ init_stderr_tail();
15389
+ import_node_child_process5 = require("node:child_process");
15390
+ import_node_module = require("node:module");
15391
+ import_node_fs3 = require("node:fs");
15392
+ import_node_path14 = require("node:path");
15393
+ init_bounds();
15394
+ init_env();
15395
+ init_session();
15396
+ init_types();
15397
+ init_version();
15398
+ CHILD_EXIT_WAIT_MS = 3e3;
15399
+ CHILD_KILL_WAIT_MS = 1e3;
15400
+ WINDOWS_NPM_SHIM_MAX_BYTES = 64 * 1024;
15401
+ WINDOWS_NPM_ENTRYPOINT = [
15402
+ "node_modules",
15403
+ "@agentclientprotocol",
15404
+ "claude-agent-acp",
15405
+ "dist",
15406
+ "index.js"
15407
+ ];
15408
+ }
15409
+ });
15410
+
15411
+ // src/host/codex.ts
15412
+ var codex_exports = {};
15413
+ __export(codex_exports, {
15414
+ CODEX_ACP_LAST_MEASURED_VERSION: () => CODEX_ACP_LAST_MEASURED_VERSION,
15415
+ CODEX_ACP_MIN_VERSION: () => CODEX_ACP_MIN_VERSION,
15416
+ CODEX_PERMISSION_MODE_ID: () => CODEX_PERMISSION_MODE_ID,
15417
+ assertCodexVersionFloor: () => assertCodexVersionFloor,
15418
+ buildCodexAcpArgs: () => buildCodexAcpArgs,
15419
+ buildCodexChildEnv: () => buildCodexChildEnv,
15420
+ buildCodexLaunch: () => buildCodexLaunch,
15421
+ openCodexAcpSession: () => openCodexAcpSession,
15422
+ parseCodexVersionOutput: () => parseCodexVersionOutput,
15423
+ resolveCodexExecutable: () => resolveCodexExecutable,
15424
+ terminateCodexChild: () => terminateCodexChild
15425
+ });
15426
+ function resolveWindowsNpmShim2(shim) {
15427
+ let source;
15428
+ try {
15429
+ source = (0, import_node_fs4.readFileSync)(shim, "utf8");
15430
+ } catch {
15431
+ throw new AcpHostError(
15432
+ "executable_missing",
15433
+ `could not read codex-acp npm shim: ${shim}`
15434
+ );
15435
+ }
15436
+ if (Buffer.byteLength(source, "utf8") > WINDOWS_NPM_SHIM_MAX_BYTES2 || !source.includes(
15437
+ String.raw`"%dp0%\node_modules\@agentclientprotocol\codex-acp\dist\index.js"`
15438
+ )) {
15439
+ throw new AcpHostError(
15440
+ "executable_missing",
15441
+ `unrecognized codex-acp npm shim: ${shim}`
15442
+ );
15443
+ }
15444
+ const target2 = (0, import_node_path15.join)((0, import_node_path15.dirname)(shim), ...WINDOWS_NPM_ENTRYPOINT2);
15445
+ try {
15446
+ (0, import_node_fs4.accessSync)(target2, import_node_fs4.constants.R_OK);
15447
+ return (0, import_node_fs4.realpathSync)(target2);
15448
+ } catch {
15449
+ throw new AcpHostError(
15450
+ "executable_missing",
15451
+ `codex-acp package entrypoint is missing beside npm shim: ${shim}`
15452
+ );
15453
+ }
15454
+ }
15455
+ function resolvedCodexCandidate(candidate, platform) {
15456
+ (0, import_node_fs4.accessSync)(candidate, import_node_fs4.constants.X_OK);
15457
+ const real = (0, import_node_fs4.realpathSync)(candidate);
15458
+ return platform === "win32" && (0, import_node_path15.extname)(real).toLowerCase() === ".cmd" ? resolveWindowsNpmShim2(real) : real;
15459
+ }
15460
+ function resolveCodexExecutable(executable = "codex-acp", pathEnv, platform = process.platform) {
15461
+ if ((0, import_node_path15.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
15462
+ const abs = (0, import_node_path15.resolve)(executable);
15463
+ const candidates = platform === "win32" && (0, import_node_path15.extname)(abs) === "" ? [`${abs}.cmd`] : [abs];
15464
+ for (const candidate of candidates) {
15465
+ try {
15466
+ return resolvedCodexCandidate(candidate, platform);
15467
+ } catch (error) {
15468
+ if (error instanceof AcpHostError) throw error;
15469
+ }
15470
+ }
15471
+ throw new AcpHostError("executable_missing", `not executable: ${abs}`);
15472
+ }
15473
+ const pathValue = pathEnv ?? process.env.PATH ?? "";
15474
+ const names = platform === "win32" && (0, import_node_path15.extname)(executable) === "" ? [`${executable}.cmd`] : [executable];
15475
+ for (const dir of pathValue.split(import_node_path15.delimiter)) {
15476
+ if (!dir) continue;
15477
+ for (const name of names) {
15478
+ const candidate = (0, import_node_path15.join)(dir, name);
15479
+ try {
15480
+ return resolvedCodexCandidate(candidate, platform);
15481
+ } catch (error) {
15482
+ if (error instanceof AcpHostError) throw error;
15483
+ }
15484
+ }
15485
+ }
15486
+ throw new AcpHostError(
15487
+ "executable_missing",
15488
+ `codex-acp executable not found on PATH: ${executable}`
15489
+ );
15490
+ }
15491
+ function buildCodexLaunch(executable, args, platform = process.platform) {
15492
+ return platform === "win32" && (0, import_node_path15.extname)(executable).toLowerCase() === ".js" ? { command: process.execPath, args: [executable, ...args] } : { command: executable, args: [...args] };
15493
+ }
15494
+ function parseCodexVersionOutput(stdout) {
15495
+ return parseProviderVersionOutput(stdout, /@agentclientprotocol\/codex-acp\b/i);
15496
+ }
15497
+ async function assertCodexVersionFloor(executable, options) {
15498
+ const minimumVersion = options?.minimumVersion ?? CODEX_ACP_MIN_VERSION;
15499
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? CODEX_ACP_LAST_MEASURED_VERSION;
15500
+ const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
15501
+ const env = options?.env ?? sanitizeChildEnv(process.env);
15502
+ const launch = buildCodexLaunch(executable, ["--version"], options?.platform);
15503
+ const stdout = await new Promise((resolve4, reject) => {
15504
+ (0, import_node_child_process6.execFile)(
15505
+ launch.command,
15506
+ launch.args,
15507
+ { timeout: timeoutMs, encoding: "utf8", env },
15508
+ (error, out, stderr) => {
15509
+ if (error) {
15510
+ reject(
15511
+ new AcpVersionError(
15512
+ `failed to run ${executable} --version: ${error.message}${stderr ? ` (${stderr.trim()})` : ""}`
15513
+ )
15514
+ );
15515
+ return;
15516
+ }
15517
+ resolve4(out);
15518
+ }
15519
+ );
15520
+ });
15521
+ const version3 = parseCodexVersionOutput(stdout);
15522
+ if (!version3) {
15523
+ const codexCliVersion = parseProviderVersionOutput(
15524
+ stdout,
15525
+ /\bcodex-cli\b/i,
15526
+ false
15527
+ );
15528
+ if (codexCliVersion) {
15529
+ throw new AcpVersionError(
15530
+ "this is the Codex CLI; --codex-executable takes the codex-acp bridge (npm i -g @agentclientprotocol/codex-acp)",
15531
+ "executable_not_bridge"
15532
+ );
15533
+ }
15534
+ throw new AcpVersionParseError(
15535
+ `could not parse codex-acp version from: ${stdout.trim().slice(0, 200)}`
15536
+ );
15537
+ }
15538
+ assertProviderVersionFloor({
15539
+ provider: "codex-acp",
15540
+ version: version3,
15541
+ minimumVersion,
15542
+ lastMeasuredVersion,
15543
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
15544
+ });
15545
+ return version3;
15546
+ }
15547
+ function buildCodexAcpArgs() {
15548
+ return [];
15549
+ }
15550
+ function buildCodexChildEnv(parent) {
15551
+ return sanitizeChildEnv(parent);
15552
+ }
15553
+ function waitForChildExit2(child, timeoutMs) {
15554
+ if (child.exitCode !== null || child.signalCode !== null) {
15555
+ return Promise.resolve();
15556
+ }
15557
+ return new Promise((resolve4) => {
15558
+ const timer2 = setTimeout(resolve4, timeoutMs);
15559
+ child.once("exit", () => {
15560
+ clearTimeout(timer2);
15561
+ resolve4();
15562
+ });
15563
+ });
15564
+ }
15565
+ async function terminateCodexChild(child) {
15566
+ if (child.exitCode !== null || child.signalCode !== null) return;
15567
+ try {
15568
+ child.kill("SIGTERM");
15569
+ } catch {
15570
+ }
15571
+ await waitForChildExit2(child, CHILD_EXIT_WAIT_MS2);
15572
+ if (child.exitCode !== null || child.signalCode !== null) return;
15573
+ try {
15574
+ child.kill("SIGKILL");
15575
+ } catch {
15576
+ }
15577
+ await waitForChildExit2(child, CHILD_KILL_WAIT_MS2);
15578
+ if (child.exitCode === null && child.signalCode === null) {
15579
+ throw new AcpHostError(
15580
+ "child_exit_timeout",
15581
+ "Codex ACP bridge did not exit after SIGTERM and SIGKILL"
15582
+ );
15583
+ }
15584
+ }
15585
+ async function openCodexAcpSession(options) {
15586
+ const parentEnv = options.env ?? process.env;
15587
+ if (options.signal?.aborted) {
15588
+ throw new AcpHostError(
15589
+ "cancelled",
15590
+ "Codex ACP bridge opening was cancelled"
15591
+ );
15592
+ }
15593
+ const pathEnv = parentEnv.PATH;
15594
+ const executable = resolveCodexExecutable(
15595
+ options.executable ?? "codex-acp",
15596
+ typeof pathEnv === "string" ? pathEnv : void 0
15597
+ );
15598
+ const env = buildCodexChildEnv(parentEnv);
15599
+ let providerVersion;
15600
+ if (!options.skipVersionCheck) {
15601
+ providerVersion = await assertCodexVersionFloor(executable, { env });
15602
+ options.onVersionNotice?.({
15603
+ provider: "codex-acp",
15604
+ runningVersion: providerVersion,
15605
+ lastMeasuredVersion: CODEX_ACP_LAST_MEASURED_VERSION
15606
+ });
15607
+ }
15608
+ if (options.signal?.aborted) {
15609
+ throw new AcpHostError(
15610
+ "cancelled",
15611
+ "Codex ACP bridge opening was cancelled"
15612
+ );
15613
+ }
15614
+ const args = buildCodexAcpArgs();
15615
+ const launch = buildCodexLaunch(executable, args);
15616
+ const child = (0, import_node_child_process6.spawn)(launch.command, launch.args, {
15617
+ stdio: ["pipe", "pipe", "pipe"],
15618
+ env,
15619
+ cwd: options.cwd
15620
+ });
15621
+ const spawnError = new Promise((_resolve, reject) => {
15622
+ child.once("error", () => {
15623
+ reject(
15624
+ new AcpHostError(
15625
+ "spawn_failed",
15626
+ "failed to spawn the Codex ACP bridge"
15627
+ )
15628
+ );
15629
+ });
15630
+ });
15631
+ let removeAbortListener = () => void 0;
15632
+ const abortError = new Promise((_resolve, reject) => {
15633
+ const signal = options.signal;
15634
+ if (!signal) return;
15635
+ const onAbort = () => {
15636
+ reject(
15637
+ new AcpHostError(
15638
+ "cancelled",
15639
+ "Codex ACP bridge opening was cancelled"
15640
+ )
15641
+ );
15642
+ };
15643
+ if (signal.aborted) {
15644
+ onAbort();
15645
+ return;
15646
+ }
15647
+ signal.addEventListener("abort", onAbort, { once: true });
15648
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
15649
+ });
15650
+ if (!child.stdin || !child.stdout) {
15651
+ await terminateCodexChild(child);
15652
+ throw new AcpHostError("spawn_failed", "child missing stdio pipes");
15653
+ }
15654
+ const observeStderrTailOnExit = attachStderrTailExitObserver(
15655
+ child,
15656
+ options.onStderrTail
15657
+ );
15658
+ let sessionRef = null;
15659
+ const transport = createBoundTransport({
15660
+ readable: child.stdout,
15661
+ writable: child.stdin,
15662
+ requestTimeoutMs: options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS,
15663
+ readableEndGraceMs: STDERR_READABLE_END_GRACE_MS,
15664
+ getSession: () => sessionRef,
15665
+ onChildExit: observeStderrTailOnExit
15666
+ });
15667
+ try {
15668
+ const session = await Promise.race([
15669
+ AcpHostSession.connect({
15670
+ transport,
15671
+ cwd: options.cwd,
15672
+ requiredModeId: CODEX_PERMISSION_MODE_ID,
15673
+ permissionCallback: options.permissionCallback,
15674
+ events: options.events,
15675
+ requestTimeoutMs: options.requestTimeoutMs,
15676
+ clientName: options.clientName,
15677
+ clientVersion: options.clientVersion,
15678
+ promptsEnabled: options.promptsEnabled
15679
+ }),
15680
+ spawnError,
15681
+ abortError
15682
+ ]);
15683
+ removeAbortListener();
15684
+ sessionRef = session;
15685
+ let closePromise = null;
15686
+ const close = async () => {
15687
+ if (closePromise) return closePromise;
15688
+ closePromise = (async () => {
15689
+ try {
15690
+ await session.close();
15691
+ } finally {
15692
+ await terminateCodexChild(child);
15693
+ }
15694
+ })();
15695
+ return closePromise;
15696
+ };
15697
+ return {
15698
+ session,
15699
+ child,
15700
+ executable,
15701
+ args,
15702
+ env,
15703
+ ...providerVersion ? { providerVersion } : {},
15704
+ close
15705
+ };
15706
+ } catch (error) {
15707
+ removeAbortListener();
15708
+ transport.close();
15709
+ await terminateCodexChild(child);
15710
+ throw error;
15711
+ }
15712
+ }
15713
+ var import_node_child_process6, import_node_fs4, import_node_path15, CHILD_EXIT_WAIT_MS2, CHILD_KILL_WAIT_MS2, WINDOWS_NPM_SHIM_MAX_BYTES2, WINDOWS_NPM_ENTRYPOINT2;
15714
+ var init_codex = __esm({
15715
+ "src/host/codex.ts"() {
15716
+ "use strict";
15717
+ init_stderr_tail();
15718
+ import_node_child_process6 = require("node:child_process");
15719
+ import_node_fs4 = require("node:fs");
15720
+ import_node_path15 = require("node:path");
15721
+ init_bounds();
15722
+ init_env();
15723
+ init_session();
15724
+ init_types();
15725
+ init_version();
15726
+ CHILD_EXIT_WAIT_MS2 = 3e3;
15727
+ CHILD_KILL_WAIT_MS2 = 1e3;
15728
+ WINDOWS_NPM_SHIM_MAX_BYTES2 = 64 * 1024;
15729
+ WINDOWS_NPM_ENTRYPOINT2 = [
15730
+ "node_modules",
15731
+ "@agentclientprotocol",
15732
+ "codex-acp",
15733
+ "dist",
15734
+ "index.js"
15735
+ ];
15736
+ }
15737
+ });
15738
+
15739
+ // src/host/opencode.ts
15740
+ var opencode_exports = {};
15741
+ __export(opencode_exports, {
15742
+ OPENCODE_FORCED_PERMISSION_TOOLS: () => OPENCODE_FORCED_PERMISSION_TOOLS,
15743
+ OPENCODE_HOME_OWNER_FILE: () => OPENCODE_HOME_OWNER_FILE,
15744
+ OPENCODE_HOME_PREFIX: () => OPENCODE_HOME_PREFIX,
15745
+ OPENCODE_LAST_MEASURED_VERSION: () => OPENCODE_LAST_MEASURED_VERSION,
15746
+ OPENCODE_MIN_VERSION: () => OPENCODE_MIN_VERSION,
15747
+ assertForcedAskPermissionMap: () => assertForcedAskPermissionMap,
15748
+ assertOpenCodeEffectiveConfig: () => assertOpenCodeEffectiveConfig,
15749
+ assertOpenCodeVersionFloor: () => assertOpenCodeVersionFloor,
15750
+ buildOpenCodeAcpArgs: () => buildOpenCodeAcpArgs,
15751
+ buildOpenCodeChildEnv: () => buildOpenCodeChildEnv,
15752
+ buildOpenCodeForcedPermissionConfig: () => buildOpenCodeForcedPermissionConfig,
15753
+ buildOpenCodeHomeOwner: () => buildOpenCodeHomeOwner,
15754
+ buildOpenCodeSafeConfigJson: () => buildOpenCodeSafeConfigJson,
15755
+ isProcessAlive: () => isProcessAlive,
15756
+ openOpenCodeAcpSession: () => openOpenCodeAcpSession,
15757
+ parseOpenCodeVersionOutput: () => parseOpenCodeVersionOutput,
15758
+ prepareOpenCodeIsolatedHome: () => prepareOpenCodeIsolatedHome,
15759
+ readOpenCodeHomeOwner: () => readOpenCodeHomeOwner,
15760
+ readValidatedOpenCodeAuth: () => readValidatedOpenCodeAuth,
15761
+ releaseOpenCodeHome: () => releaseOpenCodeHome,
15762
+ resolveOpenCodeAuthSourcePath: () => resolveOpenCodeAuthSourcePath,
15763
+ resolveOpenCodeExecutable: () => resolveOpenCodeExecutable,
15764
+ sweepStaleOpenCodeHomes: () => sweepStaleOpenCodeHomes,
15765
+ terminateOpenCodeChild: () => terminateOpenCodeChild,
15766
+ writeOpenCodeHomeOwner: () => writeOpenCodeHomeOwner
15767
+ });
15768
+ function isProcessAlive(pid) {
15769
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
15770
+ try {
15771
+ process.kill(pid, 0);
15772
+ return true;
15773
+ } catch {
15774
+ return false;
15775
+ }
15776
+ }
15777
+ function resolveOpenCodeExecutable(executable = "opencode", pathEnv) {
15778
+ if ((0, import_node_path16.isAbsolute)(executable) || executable.includes("/")) {
15779
+ const abs = (0, import_node_path16.resolve)(executable);
15780
+ try {
15781
+ (0, import_node_fs5.accessSync)(abs, import_node_fs5.constants.X_OK);
15782
+ } catch {
15783
+ throw new AcpHostError("executable_missing", `not executable: ${abs}`);
15784
+ }
15785
+ try {
15786
+ return (0, import_node_fs5.realpathSync)(abs);
15787
+ } catch {
15788
+ throw new AcpHostError(
15789
+ "executable_missing",
15790
+ `could not realpath opencode executable: ${abs}`
15791
+ );
15792
+ }
15793
+ }
15794
+ const pathValue = pathEnv ?? process.env.PATH ?? "";
15795
+ for (const dir of pathValue.split(":")) {
15796
+ if (!dir) continue;
15797
+ const candidate = (0, import_node_path16.join)(dir, executable);
15798
+ try {
15799
+ (0, import_node_fs5.accessSync)(candidate, import_node_fs5.constants.X_OK);
15800
+ try {
15801
+ return (0, import_node_fs5.realpathSync)(candidate);
15802
+ } catch {
15803
+ throw new AcpHostError(
15804
+ "executable_missing",
15805
+ `could not realpath opencode executable: ${candidate}`
15806
+ );
15807
+ }
15808
+ } catch (err) {
15809
+ if (err instanceof AcpHostError) throw err;
15810
+ }
15811
+ }
15812
+ throw new AcpHostError(
15813
+ "executable_missing",
15814
+ `opencode executable not found on PATH: ${executable}`
15815
+ );
15816
+ }
15817
+ function buildOpenCodeHomeOwner(options) {
15818
+ const uid2 = typeof process.getuid === "function" ? process.getuid() : 0;
15819
+ return {
15820
+ version: 1,
15821
+ pid: options.pid ?? process.pid,
15822
+ uid: uid2,
15823
+ instanceId: options.instanceId ?? (0, import_node_crypto19.randomUUID)(),
15824
+ role: options.role,
15825
+ createdAt: new Date((options.now ?? Date.now)()).toISOString()
15826
+ };
15827
+ }
15828
+ async function writeOpenCodeHomeOwner(home, owner) {
15829
+ const path = (0, import_node_path16.join)(home, OPENCODE_HOME_OWNER_FILE);
15830
+ await (0, import_promises9.writeFile)(path, `${JSON.stringify(owner)}
15831
+ `, {
15832
+ flag: "wx",
15833
+ mode: 384
15834
+ });
15835
+ await (0, import_promises9.chmod)(path, 384);
15836
+ }
15837
+ async function readOpenCodeHomeOwner(home) {
15838
+ const path = (0, import_node_path16.join)(home, OPENCODE_HOME_OWNER_FILE);
15839
+ let raw;
15840
+ try {
15841
+ raw = await (0, import_promises9.readFile)(path, "utf8");
15842
+ } catch {
15843
+ return null;
15844
+ }
15845
+ try {
15846
+ const value = JSON.parse(raw);
15847
+ if (value.version !== 1 || !Number.isSafeInteger(value.pid) || !Number.isSafeInteger(value.uid) || typeof value.instanceId !== "string" || !value.instanceId || value.role !== "worker" && value.role !== "isolated" && value.role !== "ephemeral" || typeof value.createdAt !== "string") {
15848
+ return null;
15849
+ }
15850
+ return value;
15851
+ } catch {
15852
+ return null;
15853
+ }
15854
+ }
15855
+ async function releaseOpenCodeHome(home, instanceId) {
15856
+ if (!(0, import_node_path16.isAbsolute)(home)) return;
15857
+ const owner = await readOpenCodeHomeOwner(home);
15858
+ if (owner && owner.instanceId !== instanceId) {
15859
+ return;
15860
+ }
15861
+ try {
15862
+ await (0, import_promises9.rm)(home, { recursive: true, force: true });
15863
+ } catch {
15864
+ await (0, import_promises9.chmod)(home, 448);
15865
+ await (0, import_promises9.rm)(home, { recursive: true, force: true });
15866
+ }
15867
+ }
15868
+ function parseOpenCodeVersionOutput(stdout) {
15869
+ return parseProviderVersionOutput(stdout, /\bopencode\b/i);
15870
+ }
15871
+ async function assertOpenCodeVersionFloor(executable, options) {
15872
+ const minimumVersion = options?.minimumVersion ?? OPENCODE_MIN_VERSION;
15873
+ const lastMeasuredVersion = options?.lastMeasuredVersion ?? OPENCODE_LAST_MEASURED_VERSION;
15874
+ const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
15875
+ const env = sanitizeChildEnv(options?.env ?? process.env);
15876
+ const stdout = await new Promise((resolve4, reject) => {
15877
+ (0, import_node_child_process7.execFile)(
15878
+ executable,
15879
+ ["--version"],
15880
+ { timeout: timeoutMs, encoding: "utf8", env },
15881
+ (err, out, stderr) => {
15882
+ if (err) {
15883
+ reject(
15884
+ new AcpVersionError(
15885
+ `failed to run ${executable} --version: ${err.message}${stderr ? ` (${stderr.trim()})` : ""}`
15886
+ )
15887
+ );
15888
+ return;
15889
+ }
15890
+ resolve4(out);
15891
+ }
15892
+ );
15893
+ });
15894
+ const version3 = parseOpenCodeVersionOutput(stdout);
15895
+ if (!version3) {
15896
+ throw new AcpVersionParseError(
15897
+ `could not parse opencode version from: ${stdout.trim().slice(0, 200)}`
15898
+ );
15899
+ }
15900
+ assertProviderVersionFloor({
15901
+ provider: "opencode",
15902
+ version: version3,
15903
+ minimumVersion,
15904
+ lastMeasuredVersion,
15905
+ ...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
15906
+ });
15907
+ return version3;
15908
+ }
15909
+ function buildOpenCodeAcpArgs() {
15910
+ return ["acp", "--pure"];
15911
+ }
15912
+ function buildOpenCodeForcedPermissionConfig() {
15913
+ const permission = {};
15914
+ for (const tool of OPENCODE_FORCED_PERMISSION_TOOLS) {
15915
+ permission[tool] = "ask";
15916
+ }
15917
+ return permission;
15918
+ }
15919
+ function buildOpenCodeSafeConfigJson(options) {
15920
+ const body = {
15921
+ $schema: "https://opencode.ai/config.json",
15922
+ permission: buildOpenCodeForcedPermissionConfig()
15923
+ };
15924
+ if (options?.model) {
15925
+ body.model = options.model;
15926
+ }
15927
+ return `${JSON.stringify(body, null, 2)}
15928
+ `;
15929
+ }
15930
+ async function readValidatedOpenCodeAuth(sourceAuthPath, options) {
15931
+ let info;
15932
+ try {
15933
+ info = await (0, import_promises9.lstat)(sourceAuthPath);
15934
+ } catch (error) {
15935
+ if (error.code === "ENOENT") {
15936
+ if (options?.allowMissing) return null;
15937
+ throw new AcpHostError(
15938
+ "opencode_auth_missing",
15939
+ "OpenCode is not signed in; run opencode auth before starting the listener"
15940
+ );
15941
+ }
15942
+ throw error;
15943
+ }
15944
+ if (!info.isFile() || info.isSymbolicLink()) {
15945
+ throw new AcpHostError(
15946
+ "opencode_auth_insecure",
15947
+ "OpenCode auth must be a secure regular file"
15948
+ );
15949
+ }
15950
+ if (typeof process.getuid === "function" && (Number(info.uid) !== process.getuid() || (Number(info.mode) & 511) !== 384)) {
15951
+ throw new AcpHostError(
15952
+ "opencode_auth_insecure",
15953
+ "OpenCode auth must be owned by this user with mode 0600"
15954
+ );
15955
+ }
15956
+ if (Number(info.size) > MAX_OPENCODE_AUTH_BYTES) {
15957
+ throw new AcpHostError(
15958
+ "opencode_auth_too_large",
15959
+ "OpenCode auth file exceeds the listener safety bound"
15960
+ );
15961
+ }
15962
+ const raw = await (0, import_promises9.readFile)(sourceAuthPath);
15963
+ if (raw.byteLength > MAX_OPENCODE_AUTH_BYTES) {
15964
+ throw new AcpHostError(
15965
+ "opencode_auth_too_large",
15966
+ "OpenCode auth file exceeds the listener safety bound"
15967
+ );
15968
+ }
15969
+ try {
15970
+ JSON.parse(raw.toString("utf8"));
15971
+ } catch {
15972
+ throw new AcpHostError(
15973
+ "opencode_auth_malformed",
15974
+ "OpenCode auth file is malformed; run opencode auth again"
15975
+ );
15976
+ }
15977
+ return raw;
15978
+ }
15979
+ function resolveOpenCodeAuthSourcePath(parent = process.env) {
15980
+ const xdgData = parent.XDG_DATA_HOME;
15981
+ if (typeof xdgData === "string" && (0, import_node_path16.isAbsolute)(xdgData)) {
15982
+ return (0, import_node_path16.join)(xdgData, "opencode", "auth.json");
15983
+ }
15984
+ const home = parent.HOME ?? (0, import_node_os7.homedir)();
15985
+ return (0, import_node_path16.join)(home, ".local", "share", "opencode", "auth.json");
15986
+ }
15987
+ async function prepareOpenCodeIsolatedHome(options) {
15988
+ const home = options.home ?? await (0, import_promises9.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), OPENCODE_HOME_PREFIX));
15989
+ if (!(0, import_node_path16.isAbsolute)(home)) {
15990
+ throw new AcpHostError(
15991
+ "isolated_home_invalid",
15992
+ "isolated OpenCode home must be absolute"
15993
+ );
15994
+ }
15995
+ await (0, import_promises9.chmod)(home, 448);
15996
+ try {
15997
+ const xdgConfig = (0, import_node_path16.join)(home, "xdg-config");
15998
+ const xdgData = (0, import_node_path16.join)(home, "xdg-data");
15999
+ const xdgCache = (0, import_node_path16.join)(home, "xdg-cache");
16000
+ const xdgState = (0, import_node_path16.join)(home, "xdg-state");
16001
+ for (const dir of [xdgConfig, xdgData, xdgCache, xdgState]) {
16002
+ await (0, import_promises9.mkdir)(dir, { recursive: true, mode: 448 });
16003
+ await (0, import_promises9.chmod)(dir, 448);
16004
+ }
16005
+ const configDir = (0, import_node_path16.join)(xdgConfig, "opencode");
16006
+ const dataDir = (0, import_node_path16.join)(xdgData, "opencode");
16007
+ await (0, import_promises9.mkdir)(configDir, { recursive: true, mode: 448 });
16008
+ await (0, import_promises9.mkdir)(dataDir, { recursive: true, mode: 448 });
16009
+ await (0, import_promises9.chmod)(configDir, 448);
16010
+ await (0, import_promises9.chmod)(dataDir, 448);
16011
+ const configPath = (0, import_node_path16.join)(configDir, "opencode.json");
16012
+ await (0, import_promises9.writeFile)(
16013
+ configPath,
16014
+ buildOpenCodeSafeConfigJson(
16015
+ options.model ? { model: options.model } : void 0
16016
+ ),
16017
+ { flag: "wx", mode: 384 }
16018
+ );
16019
+ await (0, import_promises9.chmod)(configPath, 384);
16020
+ const sourceAuth = resolveOpenCodeAuthSourcePath(options.env ?? process.env);
16021
+ const authBytes = await readValidatedOpenCodeAuth(sourceAuth, {
16022
+ allowMissing: options.allowMissingAuth === true
16023
+ });
16024
+ if (authBytes) {
16025
+ const destAuth = (0, import_node_path16.join)(dataDir, "auth.json");
16026
+ await (0, import_promises9.writeFile)(destAuth, authBytes, { flag: "wx", mode: 384 });
16027
+ await (0, import_promises9.chmod)(destAuth, 384);
16028
+ }
16029
+ const owner = options.owner ?? buildOpenCodeHomeOwner({ role: "ephemeral" });
16030
+ await writeOpenCodeHomeOwner(home, owner);
16031
+ return home;
16032
+ } catch (error) {
16033
+ if (!options.home) {
16034
+ await (0, import_promises9.rm)(home, { recursive: true, force: true }).catch(() => void 0);
16035
+ }
16036
+ throw error;
16037
+ }
16038
+ }
16039
+ function buildOpenCodeChildEnv(parent, home) {
16040
+ if (!(0, import_node_path16.isAbsolute)(home)) {
16041
+ throw new AcpHostError(
16042
+ "isolated_home_invalid",
16043
+ "isolated OpenCode home must be absolute"
16044
+ );
16045
+ }
16046
+ const base = sanitizeChildEnv(parent);
16047
+ return {
16048
+ ...base,
16049
+ HOME: home,
16050
+ XDG_CONFIG_HOME: (0, import_node_path16.join)(home, "xdg-config"),
16051
+ XDG_DATA_HOME: (0, import_node_path16.join)(home, "xdg-data"),
16052
+ XDG_CACHE_HOME: (0, import_node_path16.join)(home, "xdg-cache"),
16053
+ XDG_STATE_HOME: (0, import_node_path16.join)(home, "xdg-state"),
16054
+ // Measured 1.18.10: private home alone still merges project opencode.json.
16055
+ OPENCODE_DISABLE_PROJECT_CONFIG: "1"
16056
+ };
16057
+ }
16058
+ async function assertOpenCodeEffectiveConfig(options) {
16059
+ const hostile = await (0, import_promises9.mkdtemp)((0, import_node_path16.join)((0, import_node_os7.tmpdir)(), "cswarm-opencode-hostile-"));
16060
+ try {
16061
+ await (0, import_promises9.chmod)(hostile, 448);
16062
+ await (0, import_promises9.writeFile)(
16063
+ (0, import_node_path16.join)(hostile, "opencode.json"),
16064
+ `${JSON.stringify({
16065
+ permission: {
16066
+ bash: "allow",
16067
+ edit: "allow",
16068
+ write: "allow",
16069
+ "*": "allow"
16070
+ }
16071
+ }, null, 2)}
16072
+ `,
16073
+ { mode: 384 }
16074
+ );
16075
+ const stdout = await new Promise((resolve4, reject) => {
16076
+ (0, import_node_child_process7.execFile)(
16077
+ options.executable,
16078
+ ["debug", "config", "--pure"],
16079
+ {
16080
+ timeout: options.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS,
16081
+ encoding: "utf8",
16082
+ env: options.env,
16083
+ cwd: hostile
16084
+ },
16085
+ (err, out, stderr) => {
16086
+ if (err) {
16087
+ reject(
16088
+ new AcpHostError(
16089
+ "opencode_config_probe_failed",
16090
+ `debug config --pure failed: ${err.message}${stderr ? ` (${stderr.trim().slice(0, 200)})` : ""}`
16091
+ )
16092
+ );
16093
+ return;
16094
+ }
16095
+ resolve4(out);
16096
+ }
16097
+ );
16098
+ });
16099
+ let parsed;
16100
+ try {
16101
+ parsed = JSON.parse(stdout);
16102
+ } catch {
16103
+ throw new AcpHostError(
16104
+ "opencode_config_probe_failed",
16105
+ "debug config --pure returned non-JSON"
16106
+ );
16107
+ }
16108
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
16109
+ throw new AcpHostError(
16110
+ "opencode_config_probe_failed",
16111
+ "debug config --pure returned a non-object"
16112
+ );
16113
+ }
16114
+ const permission = parsed.permission;
16115
+ if (!permission || typeof permission !== "object" || Array.isArray(permission)) {
16116
+ throw new AcpHostError(
16117
+ "opencode_config_probe_failed",
16118
+ "debug config --pure missing permission map"
16119
+ );
16120
+ }
16121
+ const map = permission;
16122
+ assertForcedAskPermissionMap(map);
16123
+ return { permission: map };
16124
+ } finally {
16125
+ await (0, import_promises9.rm)(hostile, { recursive: true, force: true }).catch(() => void 0);
16126
+ }
16127
+ }
16128
+ function assertForcedAskPermissionMap(map) {
16129
+ for (const tool of OPENCODE_FORCED_PERMISSION_TOOLS) {
16130
+ const value = map[tool];
16131
+ if (value === "allow") {
16132
+ throw new AcpHostError(
16133
+ "opencode_project_config_active",
16134
+ `effective OpenCode config still allows tool ${tool}; OPENCODE_DISABLE_PROJECT_CONFIG failed`
16135
+ );
16136
+ }
16137
+ if (tool === "*") {
16138
+ if (value !== "ask") {
16139
+ throw new AcpHostError(
16140
+ "opencode_config_probe_failed",
16141
+ "effective OpenCode config missing forced-ask wildcard"
16142
+ );
16143
+ }
16144
+ continue;
16145
+ }
16146
+ const star = map["*"];
16147
+ const effective = value === void 0 || value === null ? star : value;
16148
+ if (effective !== "ask") {
16149
+ throw new AcpHostError(
16150
+ "opencode_config_probe_failed",
16151
+ `effective OpenCode config lacks forced-ask for tool ${tool}`
16152
+ );
16153
+ }
16154
+ }
16155
+ for (const critical of ["bash", "write", "edit", "execute", "*"]) {
16156
+ const value = map[critical];
16157
+ const star = map["*"];
16158
+ const effective = critical === "*" ? value : value === void 0 || value === null ? star : value;
16159
+ if (effective !== "ask") {
16160
+ throw new AcpHostError(
16161
+ "opencode_config_probe_failed",
16162
+ `critical tool ${critical} is not forced-ask`
16163
+ );
16164
+ }
16165
+ }
16166
+ }
16167
+ async function sweepStaleOpenCodeHomes(options) {
16168
+ const maxAgeMs = options?.maxAgeMs ?? STALE_HOME_MAX_AGE_MS;
16169
+ const now = options?.now ?? Date.now();
16170
+ const alive = options?.isAlive ?? isProcessAlive;
16171
+ const root = options?.root ?? (0, import_node_os7.tmpdir)();
16172
+ const selfUid = typeof process.getuid === "function" ? process.getuid() : null;
16173
+ let removed = 0;
16174
+ let entries;
16175
+ try {
16176
+ entries = await (0, import_promises9.readdir)(root);
16177
+ } catch {
16178
+ return 0;
16179
+ }
16180
+ for (const name of entries) {
16181
+ if (!name.startsWith(OPENCODE_HOME_PREFIX)) continue;
16182
+ const full = (0, import_node_path16.join)(root, name);
16183
+ try {
16184
+ const st = await (0, import_promises9.lstat)(full);
16185
+ if (!st.isDirectory() || st.isSymbolicLink()) continue;
16186
+ if (selfUid !== null && typeof st.uid === "number" && st.uid !== selfUid) {
16187
+ continue;
16188
+ }
16189
+ if ((Number(st.mode) & 511) !== 448) {
16190
+ continue;
16191
+ }
16192
+ const owner = await readOpenCodeHomeOwner(full);
16193
+ if (owner) {
16194
+ if (selfUid !== null && owner.uid !== selfUid) continue;
16195
+ if (alive(owner.pid)) {
16196
+ continue;
16197
+ }
16198
+ await (0, import_promises9.rm)(full, { recursive: true, force: true });
16199
+ removed += 1;
16200
+ continue;
16201
+ }
16202
+ if (now - st.mtimeMs < maxAgeMs) continue;
16203
+ await (0, import_promises9.rm)(full, { recursive: true, force: true });
16204
+ removed += 1;
16205
+ } catch {
16206
+ }
16207
+ }
16208
+ return removed;
16209
+ }
16210
+ function waitForChildExit3(child, timeoutMs) {
16211
+ if (child.exitCode !== null || child.signalCode !== null) {
16212
+ return Promise.resolve();
16213
+ }
16214
+ return new Promise((resolve4) => {
16215
+ const timer2 = setTimeout(() => resolve4(), timeoutMs);
16216
+ child.once("exit", () => {
16217
+ clearTimeout(timer2);
16218
+ resolve4();
16219
+ });
16220
+ });
16221
+ }
16222
+ async function terminateOpenCodeChild(child) {
16223
+ if (child.exitCode !== null || child.signalCode !== null) return;
16224
+ try {
16225
+ child.kill("SIGTERM");
16226
+ } catch {
16227
+ }
16228
+ await waitForChildExit3(child, CHILD_EXIT_WAIT_MS3);
16229
+ if (child.exitCode !== null || child.signalCode !== null) return;
16230
+ try {
16231
+ child.kill("SIGKILL");
16232
+ } catch {
16233
+ }
16234
+ await waitForChildExit3(child, CHILD_KILL_WAIT_MS3);
16235
+ if (child.exitCode === null && child.signalCode === null) {
16236
+ throw new AcpHostError(
16237
+ "child_exit_timeout",
16238
+ "OpenCode child did not exit after SIGTERM and SIGKILL"
16239
+ );
16240
+ }
16241
+ }
16242
+ async function openOpenCodeAcpSession(options) {
16243
+ await sweepStaleOpenCodeHomes().catch(() => 0);
16244
+ const pathEnv = (options.env ?? process.env).PATH;
16245
+ const executable = resolveOpenCodeExecutable(
16246
+ options.executable ?? "opencode",
16247
+ typeof pathEnv === "string" ? pathEnv : void 0
16248
+ );
16249
+ let home = options.isolatedHome;
16250
+ let createdHome = false;
16251
+ if (!home) {
16252
+ home = await prepareOpenCodeIsolatedHome({
16253
+ env: options.env ?? process.env,
16254
+ ...options.model ? { model: options.model } : {},
16255
+ ...options.allowMissingAuth === true ? { allowMissingAuth: true } : {}
16256
+ });
16257
+ createdHome = true;
16258
+ }
16259
+ const env = buildOpenCodeChildEnv(options.env ?? process.env, home);
16260
+ let childStarted = false;
16261
+ const disposeHome = async () => {
16262
+ if (createdHome) {
16263
+ try {
16264
+ await (0, import_promises9.rm)(home, { recursive: true, force: true });
16265
+ } catch {
16266
+ await (0, import_promises9.chmod)(home, 448);
16267
+ await (0, import_promises9.rm)(home, { recursive: true, force: true });
16268
+ }
16269
+ }
16270
+ };
16271
+ try {
16272
+ if (!options.skipVersionCheck) {
16273
+ await assertOpenCodeVersionFloor(executable, {
16274
+ env,
16275
+ ...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
16276
+ });
16277
+ }
16278
+ if (!options.skipConfigProbe) {
16279
+ await assertOpenCodeEffectiveConfig({
16280
+ executable,
16281
+ env
16282
+ });
16283
+ }
16284
+ const args = buildOpenCodeAcpArgs();
16285
+ const child = (0, import_node_child_process7.spawn)(executable, args, {
16286
+ stdio: ["pipe", "pipe", "pipe"],
16287
+ env,
16288
+ cwd: options.cwd
16289
+ });
16290
+ childStarted = true;
16291
+ if (!child.stdin || !child.stdout) {
16292
+ await terminateOpenCodeChild(child);
16293
+ await disposeHome();
16294
+ throw new AcpHostError("spawn_failed", "child missing stdio pipes");
16295
+ }
16296
+ const observeStderrTailOnExit = attachStderrTailExitObserver(
16297
+ child,
16298
+ options.onStderrTail
16299
+ );
16300
+ let sessionRef = null;
16301
+ const transport = createBoundTransport({
16302
+ readable: child.stdout,
16303
+ writable: child.stdin,
16304
+ requestTimeoutMs: options.requestTimeoutMs ?? ACP_DEFAULT_REQUEST_TIMEOUT_MS,
16305
+ readableEndGraceMs: STDERR_READABLE_END_GRACE_MS,
16306
+ getSession: () => sessionRef,
16307
+ onChildExit: observeStderrTailOnExit
16308
+ });
16309
+ try {
16310
+ const session = await AcpHostSession.connect({
16311
+ transport,
16312
+ cwd: options.cwd,
16313
+ permissionCallback: options.permissionCallback,
16314
+ events: options.events,
16315
+ requestTimeoutMs: options.requestTimeoutMs,
16316
+ clientName: options.clientName,
16317
+ clientVersion: options.clientVersion,
16318
+ promptsEnabled: options.promptsEnabled
16319
+ });
16320
+ sessionRef = session;
16321
+ let closePromise = null;
16322
+ const close = async () => {
16323
+ if (closePromise) return closePromise;
16324
+ closePromise = (async () => {
16325
+ try {
16326
+ await session.close();
16327
+ } finally {
16328
+ await terminateOpenCodeChild(child);
16329
+ await disposeHome();
16330
+ }
16331
+ })();
16332
+ return closePromise;
16333
+ };
16334
+ return { session, child, executable, args, env, home, close };
16335
+ } catch (err) {
16336
+ transport.close();
16337
+ let termErr = null;
16338
+ try {
16339
+ await terminateOpenCodeChild(child);
16340
+ } catch (e) {
16341
+ termErr = e;
16342
+ }
16343
+ if (termErr) {
16344
+ throw termErr;
16345
+ }
16346
+ await disposeHome();
16347
+ throw err;
16348
+ }
16349
+ } catch (err) {
16350
+ if (!childStarted) {
16351
+ await disposeHome();
16352
+ }
16353
+ throw err;
16354
+ }
16355
+ }
16356
+ var import_node_child_process7, import_node_crypto19, import_node_fs5, import_promises9, import_node_os7, import_node_path16, OPENCODE_HOME_OWNER_FILE, MAX_OPENCODE_AUTH_BYTES, OPENCODE_HOME_PREFIX, CHILD_EXIT_WAIT_MS3, CHILD_KILL_WAIT_MS3, STALE_HOME_MAX_AGE_MS;
16357
+ var init_opencode = __esm({
16358
+ "src/host/opencode.ts"() {
16359
+ "use strict";
16360
+ import_node_child_process7 = require("node:child_process");
16361
+ import_node_crypto19 = require("node:crypto");
16362
+ init_stderr_tail();
16363
+ import_node_fs5 = require("node:fs");
16364
+ import_promises9 = require("node:fs/promises");
16365
+ import_node_os7 = require("node:os");
16366
+ import_node_path16 = require("node:path");
16367
+ init_bounds();
16368
+ init_env();
16369
+ init_session();
16370
+ init_types();
16371
+ init_version();
16372
+ OPENCODE_HOME_OWNER_FILE = ".cswarm-opencode-owner.json";
16373
+ MAX_OPENCODE_AUTH_BYTES = 256 * 1024;
16374
+ OPENCODE_HOME_PREFIX = "cswarm-opencode-home-";
16375
+ CHILD_EXIT_WAIT_MS3 = 3e3;
16376
+ CHILD_KILL_WAIT_MS3 = 1e3;
16377
+ STALE_HOME_MAX_AGE_MS = 60 * 60 * 1e3;
16378
+ }
16379
+ });
16380
+
13502
16381
  // src/cli.ts
13503
16382
  var cli_exports = {};
13504
16383
  __export(cli_exports, {
@@ -13509,6 +16388,7 @@ __export(cli_exports, {
13509
16388
  clampTurnBudgetToCredential: () => clampTurnBudgetToCredential,
13510
16389
  claudeUserPromptHookSnippet: () => claudeUserPromptHookSnippet,
13511
16390
  describeAudience: () => describeAudience,
16391
+ isCliMain: () => isCliMain,
13512
16392
  listenerFailureMessage: () => listenerFailureMessage,
13513
16393
  listenerHostLimits: () => listenerHostLimits,
13514
16394
  listenerMainHostLimits: () => listenerMainHostLimits,
@@ -13527,14 +16407,13 @@ __export(cli_exports, {
13527
16407
  usage: () => usage
13528
16408
  });
13529
16409
  module.exports = __toCommonJS(cli_exports);
13530
- var import_node_crypto19 = require("node:crypto");
13531
- var import_node_module = require("node:module");
13532
- var import_node_child_process5 = require("node:child_process");
13533
- var import_node_fs2 = require("node:fs");
13534
- var import_promises9 = require("node:fs/promises");
13535
- var import_node_os7 = require("node:os");
13536
- var import_node_path13 = require("node:path");
13537
- var import_promises10 = require("node:readline/promises");
16410
+ var import_node_crypto20 = require("node:crypto");
16411
+ var import_node_child_process8 = require("node:child_process");
16412
+ var import_node_fs6 = require("node:fs");
16413
+ var import_promises10 = require("node:fs/promises");
16414
+ var import_node_os8 = require("node:os");
16415
+ var import_node_path17 = require("node:path");
16416
+ var import_promises11 = require("node:readline/promises");
13538
16417
 
13539
16418
  // src/protocol/events.ts
13540
16419
  var SCHEMA_VERSION = 1;
@@ -24827,9 +27706,9 @@ var arraySerializer = function arraySerializer2(xs, serializer, options, typarra
24827
27706
  if (!xs.length)
24828
27707
  return "{}";
24829
27708
  const first = xs[0];
24830
- const delimiter = typarray === 1020 ? ";" : ",";
27709
+ const delimiter3 = typarray === 1020 ? ";" : ",";
24831
27710
  if (Array.isArray(first) && !first.type)
24832
- return "{" + xs.map((x) => arraySerializer2(x, serializer, options, typarray)).join(delimiter) + "}";
27711
+ return "{" + xs.map((x) => arraySerializer2(x, serializer, options, typarray)).join(delimiter3) + "}";
24833
27712
  return "{" + xs.map((x) => {
24834
27713
  if (x === void 0) {
24835
27714
  x = options.transform.undefined;
@@ -24837,7 +27716,7 @@ var arraySerializer = function arraySerializer2(xs, serializer, options, typarra
24837
27716
  throw Errors.generic("UNDEFINED_VALUE", "Undefined values are not allowed");
24838
27717
  }
24839
27718
  return x === null ? "null" : '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : "" + x) + '"';
24840
- }).join(delimiter) + "}";
27719
+ }).join(delimiter3) + "}";
24841
27720
  };
24842
27721
  var arrayParserState = {
24843
27722
  i: 0,
@@ -24852,7 +27731,7 @@ var arrayParser = function arrayParser2(x, parser, typarray) {
24852
27731
  };
24853
27732
  function arrayParserLoop(s, x, parser, typarray) {
24854
27733
  const xs = [];
24855
- const delimiter = typarray === 1020 ? ";" : ",";
27734
+ const delimiter3 = typarray === 1020 ? ";" : ",";
24856
27735
  for (; s.i < x.length; s.i++) {
24857
27736
  s.char = x[s.i];
24858
27737
  if (s.quoted) {
@@ -24876,7 +27755,7 @@ function arrayParserLoop(s, x, parser, typarray) {
24876
27755
  s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i));
24877
27756
  s.last = s.i + 1;
24878
27757
  break;
24879
- } else if (s.char === delimiter && s.p !== "}" && s.p !== '"') {
27758
+ } else if (s.char === delimiter3 && s.p !== "}" && s.p !== '"') {
24880
27759
  xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i));
24881
27760
  s.last = s.i + 1;
24882
27761
  }
@@ -33752,97 +36631,8 @@ async function reportRenderedBroadcasts(target2, token, workspaceId2, signalIds,
33752
36631
  return { attempted: new Set(signalIds).size, reported, failures };
33753
36632
  }
33754
36633
 
33755
- // src/host/types.ts
33756
- var TRANSIENT_ACP_CODES = /* @__PURE__ */ new Set([
33757
- "timeout",
33758
- "child_exit",
33759
- "transport"
33760
- ]);
33761
- var AcpHostError = class extends Error {
33762
- code;
33763
- constructor(code, message) {
33764
- super(message);
33765
- this.name = "AcpHostError";
33766
- this.code = code;
33767
- }
33768
- };
33769
- var AcpVersionError = class extends AcpHostError {
33770
- constructor(message, code = "version_refused") {
33771
- super(code, message);
33772
- this.name = "AcpVersionError";
33773
- }
33774
- };
33775
- var AcpVersionParseError = class extends AcpVersionError {
33776
- constructor(message) {
33777
- super(message, "version_unparseable");
33778
- this.name = "AcpVersionParseError";
33779
- }
33780
- };
33781
- var AcpPermissionCanaryError = class extends AcpHostError {
33782
- constructor(message, reasonCode = null, minimumRequiredVersion = null, peerError = null) {
33783
- super("permission_canary_failed", message);
33784
- this.reasonCode = reasonCode;
33785
- this.minimumRequiredVersion = minimumRequiredVersion;
33786
- this.peerError = peerError;
33787
- this.name = "AcpPermissionCanaryError";
33788
- }
33789
- reasonCode;
33790
- minimumRequiredVersion;
33791
- peerError;
33792
- };
33793
-
33794
- // src/host/version.ts
33795
- var CORE_IDENTIFIER = "(?:0|[1-9]\\d*)";
33796
- var PRERELEASE_IDENTIFIER = "(?:0|[1-9]\\d*|[A-Za-z-][0-9A-Za-z-]*)";
33797
- var BUILD_IDENTIFIER = "[0-9A-Za-z-]+";
33798
- var SEMVER_SOURCE = `${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}\\.${CORE_IDENTIFIER}(?:-${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*)?(?:\\+${BUILD_IDENTIFIER}(?:\\.${BUILD_IDENTIFIER})*)?`;
33799
- var SEMVER_RE = new RegExp(`^${SEMVER_SOURCE}$`);
33800
- function parseSemVer(value) {
33801
- if (!SEMVER_RE.test(value)) return null;
33802
- const withoutBuild = value.split("+", 1)[0];
33803
- const dash = withoutBuild.indexOf("-");
33804
- const coreText = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
33805
- const prereleaseText = dash === -1 ? null : withoutBuild.slice(dash + 1);
33806
- const coreParts = coreText.split(".");
33807
- if (coreParts.length !== 3) return null;
33808
- return {
33809
- core: [BigInt(coreParts[0]), BigInt(coreParts[1]), BigInt(coreParts[2])],
33810
- prerelease: prereleaseText === null ? null : prereleaseText.split(".")
33811
- };
33812
- }
33813
- function compareSemVer(left, right) {
33814
- const a = parseSemVer(left);
33815
- const b2 = parseSemVer(right);
33816
- if (!a || !b2) {
33817
- throw new AcpVersionParseError(
33818
- `cannot compare invalid semantic versions: ${JSON.stringify(left)} and ${JSON.stringify(right)}`
33819
- );
33820
- }
33821
- for (let index = 0; index < 3; index += 1) {
33822
- if (a.core[index] < b2.core[index]) return -1;
33823
- if (a.core[index] > b2.core[index]) return 1;
33824
- }
33825
- if (a.prerelease === null && b2.prerelease === null) return 0;
33826
- if (a.prerelease === null) return 1;
33827
- if (b2.prerelease === null) return -1;
33828
- const length = Math.max(a.prerelease.length, b2.prerelease.length);
33829
- for (let index = 0; index < length; index += 1) {
33830
- const leftPart = a.prerelease[index];
33831
- const rightPart = b2.prerelease[index];
33832
- if (leftPart === void 0) return -1;
33833
- if (rightPart === void 0) return 1;
33834
- if (leftPart === rightPart) continue;
33835
- const leftNumeric = /^\d+$/.test(leftPart);
33836
- const rightNumeric = /^\d+$/.test(rightPart);
33837
- if (leftNumeric && rightNumeric) {
33838
- return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1;
33839
- }
33840
- if (leftNumeric) return -1;
33841
- if (rightNumeric) return 1;
33842
- return leftPart < rightPart ? -1 : 1;
33843
- }
33844
- return 0;
33845
- }
36634
+ // src/cli.ts
36635
+ init_version();
33846
36636
 
33847
36637
  // src/listener/types.ts
33848
36638
  var LISTENER_PROMPT_TIMEOUT_MS = 6e5;
@@ -33874,6 +36664,7 @@ var LISTENER_DELIVERY_HOLD_RELEASE_REMEDIES = {
33874
36664
  };
33875
36665
 
33876
36666
  // src/listener/engine.ts
36667
+ init_types();
33877
36668
  var UUID_RE16 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
33878
36669
  function listenerReplyCommandId(signalId, effectOrdinal = 0) {
33879
36670
  if (!UUID_RE16.test(signalId)) {
@@ -34240,9 +37031,8 @@ var FileListenerEffectStore = class {
34240
37031
 
34241
37032
  // src/listener/runtime.ts
34242
37033
  var import_node_crypto15 = require("node:crypto");
34243
-
34244
- // src/host/bounds.ts
34245
- var ACP_DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
37034
+ init_bounds();
37035
+ init_types();
34246
37036
 
34247
37037
  // src/listener/main-routing.ts
34248
37038
  var import_node_path7 = require("node:path");
@@ -35922,25 +38712,7 @@ var import_node_crypto16 = require("node:crypto");
35922
38712
  var import_node_net = require("node:net");
35923
38713
  var import_promises6 = require("node:fs/promises");
35924
38714
  var import_node_path8 = require("node:path");
35925
-
35926
- // src/host/credential-redaction.ts
35927
- var EXOTIC_SEPARATORS = "\\u00a0\\u1680\\u2000-\\u200d\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\u202f\\u205f\\u3000\\ufeff";
35928
- var SEPARATOR_CLASS_SOURCE = "\\t\\n\\x0b\\f\\r " + EXOTIC_SEPARATORS;
35929
- var ANSI_ESCAPE_GLOBAL_RE2 = new RegExp("\\u001b\\[[0-?]*[ -\\/]*[@-~]", "g");
35930
- var CONTROL_AND_SEPARATOR_STRIP_RE = new RegExp(
35931
- "[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f" + EXOTIC_SEPARATORS + "]",
35932
- "g"
35933
- );
35934
- var SECRET_SHAPE_RE = new RegExp(
35935
- `swm_(?:agt|inv|cap)_[^${SEPARATOR_CLASS_SOURCE}]*|cswarm-wake:[A-Za-z0-9_-]{43}`,
35936
- "i"
35937
- );
35938
- var SECRET_SHAPE_GLOBAL_RE = new RegExp(SECRET_SHAPE_RE.source, "gi");
35939
- function redactCredentialText(value) {
35940
- return value.replace(ANSI_ESCAPE_GLOBAL_RE2, "").replace(CONTROL_AND_SEPARATOR_STRIP_RE, "").replace(SECRET_SHAPE_GLOBAL_RE, "[redacted-credential]");
35941
- }
35942
-
35943
- // src/listener/control.ts
38715
+ init_credential_redaction();
35944
38716
  var UUID_RE20 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
35945
38717
  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-]+)*)?$/;
35946
38718
  var MAX_STATUS_BYTES = 32 * 1024;
@@ -36606,6 +39378,8 @@ async function queryListenerControl(paths, command2, timeoutMs = CONTROL_TIMEOUT
36606
39378
 
36607
39379
  // src/listener/supervisor.ts
36608
39380
  var import_node_crypto17 = require("node:crypto");
39381
+ init_credential_redaction();
39382
+ init_types();
36609
39383
  var UUID_RE21 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
36610
39384
  var LISTENER_RESTART_MAX_ATTEMPTS = 5;
36611
39385
  var LISTENER_RESTART_INITIAL_MS = 1e3;
@@ -38117,48 +40891,7 @@ async function openListenerDeliveryJournal(options) {
38117
40891
  // src/listener/detach.ts
38118
40892
  var import_node_child_process3 = require("node:child_process");
38119
40893
  var import_node_path10 = require("node:path");
38120
-
38121
- // src/host/env.ts
38122
- var ALLOWED_EXACT = /* @__PURE__ */ new Set([
38123
- "PATH",
38124
- "HOME",
38125
- "USER",
38126
- "LOGNAME",
38127
- "SHELL",
38128
- "TMPDIR",
38129
- "TMP",
38130
- "TEMP",
38131
- "LANG",
38132
- "LC_ALL",
38133
- "LC_CTYPE",
38134
- "LC_MESSAGES",
38135
- "LC_COLLATE",
38136
- "LC_TIME",
38137
- "TERM",
38138
- "COLORTERM",
38139
- "NO_COLOR",
38140
- "FORCE_COLOR",
38141
- "XDG_CONFIG_HOME",
38142
- "XDG_DATA_HOME",
38143
- "XDG_CACHE_HOME",
38144
- "XDG_RUNTIME_DIR",
38145
- "XDG_STATE_HOME",
38146
- "GROK_HOME"
38147
- ]);
38148
- var DENY_NAME_RE = /(?:^|_)(?:SWARM|SECRET|TOKEN|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|API_KEY|AUTH|COOKIE)(?:_|$)/i;
38149
- function sanitizeChildEnv(parent = process.env) {
38150
- const out = {};
38151
- for (const [key2, value] of Object.entries(parent)) {
38152
- if (value === void 0) continue;
38153
- if (!ALLOWED_EXACT.has(key2)) continue;
38154
- if (DENY_NAME_RE.test(key2)) continue;
38155
- if (key2.startsWith("SWARM_")) continue;
38156
- out[key2] = value;
38157
- }
38158
- return out;
38159
- }
38160
-
38161
- // src/listener/detach.ts
40894
+ init_env();
38162
40895
  function isNativeAbsolutePath(value, platform = process.platform) {
38163
40896
  return platform === "win32" ? import_node_path10.win32.isAbsolute(value) : import_node_path10.posix.isAbsolute(value);
38164
40897
  }
@@ -39312,18 +42045,7 @@ function renderListenerAttendanceCanary(result, workspaceId2, principalId) {
39312
42045
 
39313
42046
  // src/listener/activity.ts
39314
42047
  var import_node_crypto18 = require("node:crypto");
39315
-
39316
- // src/host/sanitize.ts
39317
- var SECRET_VALUE_RE = /(?:(?:api[_-]?key|token|secret|password|authorization|bearer)\s*[:=]\s*)(["']?)([^\s"'\\]{8,})\1/gi;
39318
- var JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
39319
- function redactString(value) {
39320
- return redactCredentialText(value).replace(SECRET_VALUE_RE, (_m, q) => `redacted=${q}***${q}`).replace(JWT_RE, "[redacted-jwt]");
39321
- }
39322
- function sanitizeText(text) {
39323
- return redactString(text);
39324
- }
39325
-
39326
- // src/listener/activity.ts
42048
+ init_sanitize();
39327
42049
  var ACTIVITY_FRAME_INTERVAL_MS = 750;
39328
42050
  var ACTIVITY_HEARTBEAT_MS = 15e3;
39329
42051
  var ACTIVITY_TOOL_TITLE_MAX = 160;
@@ -41083,20 +43805,50 @@ async function revokeAgentToken(input) {
41083
43805
  };
41084
43806
  }
41085
43807
 
43808
+ // src/listener/claude-canary-classify.ts
43809
+ var CLAUDE_CODE_VERSION_REQUIRED_RE = /\bClaude Code (\d+\.\d+\.\d+) does not support this model; version (\d+\.\d+\.\d+) or newer is required\b/;
43810
+ var CLAUDE_AUTH_FAILURE_RE = /\b(?:authentication failed|failed to authenticate|authentication required|not authenticated|OAuth (?:sign-in|login|token)|OAuth session (?:expired|could not be refreshed)|keychain\/OAuth|please (?:log|sign) in)\b/i;
43811
+ var CLAUDE_CANARY_TIMEOUT_RE = /^ACP request timed out: session\/prompt(?: \(failed \d+ attempts\))?$/;
43812
+ function classifyClaudeCanaryFailure(detail, typedReasonCode, peerError) {
43813
+ const recorded = detail?.trim() ?? "";
43814
+ const peerData = peerError?.data;
43815
+ const peerErrorKind = peerData && typeof peerData === "object" && !Array.isArray(peerData) ? peerData.errorKind : void 0;
43816
+ if (typedReasonCode === "claude_canary_auth_failed" || (typedReasonCode === "rpc_error" || typedReasonCode === null || typedReasonCode === void 0) && (peerError?.code === -32e3 || peerErrorKind === "authentication_failed")) {
43817
+ return { code: "claude_canary_auth_failed", minimumRequiredVersion: null };
43818
+ }
43819
+ const demanded = CLAUDE_CODE_VERSION_REQUIRED_RE.exec(recorded);
43820
+ if (demanded?.[2]) {
43821
+ return {
43822
+ code: "claude_bridge_version_required",
43823
+ minimumRequiredVersion: demanded[2]
43824
+ };
43825
+ }
43826
+ if (typedReasonCode === "claude_canary_timeout" || typedReasonCode === "timeout" || (typedReasonCode === null || typedReasonCode === void 0) && CLAUDE_CANARY_TIMEOUT_RE.test(recorded)) {
43827
+ return { code: "claude_canary_timeout", minimumRequiredVersion: null };
43828
+ }
43829
+ if (typedReasonCode === "claude_bridge_version_required") {
43830
+ return {
43831
+ code: "claude_bridge_version_required",
43832
+ minimumRequiredVersion: demanded?.[2] ?? null
43833
+ };
43834
+ }
43835
+ if ((typedReasonCode === "rpc_error" || typedReasonCode === null || typedReasonCode === void 0) && CLAUDE_AUTH_FAILURE_RE.test(recorded)) {
43836
+ return { code: "claude_canary_auth_failed", minimumRequiredVersion: null };
43837
+ }
43838
+ return { code: "claude_canary_unknown", minimumRequiredVersion: null };
43839
+ }
43840
+
41086
43841
  // src/cli.ts
43842
+ var import_node_url = require("node:url");
41087
43843
  var import_meta = {};
41088
- var requireFromCli = (0, import_node_module.createRequire)(import_meta.url);
41089
43844
  function loadHostClaude() {
41090
- return requireFromCli("./host/claude.js");
43845
+ return Promise.resolve().then(() => (init_claude(), claude_exports));
41091
43846
  }
41092
43847
  function loadHostCodex() {
41093
- return requireFromCli("./host/codex.js");
43848
+ return Promise.resolve().then(() => (init_codex(), codex_exports));
41094
43849
  }
41095
43850
  function loadHostOpenCode() {
41096
- return requireFromCli("./host/opencode.js");
41097
- }
41098
- function loadClaudeListenerModel() {
41099
- return requireFromCli("./listener/claude-model.js");
43851
+ return Promise.resolve().then(() => (init_opencode(), opencode_exports));
41100
43852
  }
41101
43853
  var KNOWN_FLAGS = /* @__PURE__ */ new Set([
41102
43854
  "about",
@@ -41214,12 +43966,12 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
41214
43966
  ]);
41215
43967
  var UUID_RE25 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
41216
43968
  function packageVersion() {
41217
- if ("0.1.62".length > 0) {
41218
- return "0.1.62";
43969
+ if ("0.1.64".length > 0) {
43970
+ return "0.1.64";
41219
43971
  }
41220
43972
  try {
41221
43973
  const value = JSON.parse(
41222
- (0, import_node_fs2.readFileSync)(new URL("../package.json", import_meta.url), "utf8")
43974
+ (0, import_node_fs6.readFileSync)(new URL("../package.json", import_meta.url), "utf8")
41223
43975
  );
41224
43976
  const version3 = value.version;
41225
43977
  if (typeof version3 !== "string") return "unknown";
@@ -41692,7 +44444,7 @@ async function stdinInviteLink() {
41692
44444
  return link;
41693
44445
  }
41694
44446
  async function confirmationLine(prompt) {
41695
- const reader = (0, import_promises10.createInterface)({
44447
+ const reader = (0, import_promises11.createInterface)({
41696
44448
  input: process.stdin,
41697
44449
  output: process.stderr,
41698
44450
  terminal: Boolean(process.stdin.isTTY)
@@ -41829,7 +44581,7 @@ async function runNew(args) {
41829
44581
  assertWorkspaceName(name);
41830
44582
  const cloud = await target(args);
41831
44583
  const human = await humanCredential(args, cloud);
41832
- const proposedId = (0, import_node_crypto19.randomUUID)();
44584
+ const proposedId = (0, import_node_crypto20.randomUUID)();
41833
44585
  let result;
41834
44586
  try {
41835
44587
  result = await new ThinCommandClient(cloud).sendConnect({
@@ -43205,13 +45957,13 @@ function prepareSignalAttachments(localPaths) {
43205
45957
  return localPaths.map((localPath) => {
43206
45958
  let bytes;
43207
45959
  try {
43208
- bytes = (0, import_node_fs2.readFileSync)(localPath);
45960
+ bytes = (0, import_node_fs6.readFileSync)(localPath);
43209
45961
  } catch {
43210
45962
  throw new Error(
43211
45963
  `could not read ${localPath}; check the path and permissions; no upload was started`
43212
45964
  );
43213
45965
  }
43214
- const name = (0, import_node_path13.basename)(localPath);
45966
+ const name = (0, import_node_path17.basename)(localPath);
43215
45967
  if (bytes.byteLength < 1) {
43216
45968
  throw new Error(`${localPath} is empty; no upload was started`);
43217
45969
  }
@@ -43231,8 +45983,8 @@ function prepareSignalAttachments(localPaths) {
43231
45983
  name,
43232
45984
  bytes,
43233
45985
  contentType,
43234
- fileId: (0, import_node_crypto19.randomUUID)(),
43235
- versionId: (0, import_node_crypto19.randomUUID)(),
45986
+ fileId: (0, import_node_crypto20.randomUUID)(),
45987
+ versionId: (0, import_node_crypto20.randomUUID)(),
43236
45988
  createCommandId: newCommandId(),
43237
45989
  commitCommandId: newCommandId()
43238
45990
  };
@@ -43754,7 +46506,7 @@ async function runResume(args) {
43754
46506
  if (/[\u0000-\u001f\u007f-\u009f]/.test(suppliedCredentialPath)) {
43755
46507
  throw new Error("--agent-token-file must not contain control characters");
43756
46508
  }
43757
- const credentialFile = (0, import_node_path13.resolve)(suppliedCredentialPath);
46509
+ const credentialFile = (0, import_node_path17.resolve)(suppliedCredentialPath);
43758
46510
  const cloud = await target(args);
43759
46511
  const workspaceId2 = listenerUuid(
43760
46512
  args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
@@ -44269,7 +47021,7 @@ function listenerPermissionMode(value) {
44269
47021
  function listenerStateDirectory(args) {
44270
47022
  const value = args.optional("state-dir");
44271
47023
  if (value === void 0) return void 0;
44272
- if (!(0, import_node_path13.isAbsolute)(value)) {
47024
+ if (!(0, import_node_path17.isAbsolute)(value)) {
44273
47025
  throw new Error("--state-dir must be an absolute path");
44274
47026
  }
44275
47027
  return value;
@@ -44526,7 +47278,7 @@ function listenerLapseNotices(status, summary) {
44526
47278
  async function listenerProviderInstallEvidence(status) {
44527
47279
  if (status.provider !== "claude") return null;
44528
47280
  try {
44529
- const notice = await loadHostClaude().inspectClaudeBridgeExecutable(
47281
+ const notice = await (await loadHostClaude()).inspectClaudeBridgeExecutable(
44530
47282
  status.providerExecutable ?? "claude-agent-acp",
44531
47283
  { pathEnv: process.env.PATH, env: process.env }
44532
47284
  );
@@ -44914,7 +47666,7 @@ function listenerFailureMessage(code, provider, detail, reasonCode, minimumRequi
44914
47666
  }
44915
47667
  if (code === "permission_canary_failed") {
44916
47668
  if (provider === "claude") {
44917
- const shape = loadClaudeListenerModel().classifyClaudeCanaryFailure(detail, reasonCode);
47669
+ const shape = classifyClaudeCanaryFailure(detail, reasonCode);
44918
47670
  const ran = "the Claude ACP permission canary ran, but no workspace signal prompt was delivered";
44919
47671
  const response = `bridge response [${shape.code}]: ${quotedListenerFailureDetail(detail)}`;
44920
47672
  if (shape.code === "claude_bridge_version_required") {
@@ -44947,13 +47699,13 @@ function listenerFailureMessage(code, provider, detail, reasonCode, minimumRequi
44947
47699
  }
44948
47700
  return `listener failed (${code}); check cswarm listen status before starting another`;
44949
47701
  }
44950
- function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEnv = process.env.PATH) {
47702
+ async function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEnv = process.env.PATH) {
44951
47703
  try {
44952
- return loadHostClaude().resolveClaudeExecutable(executable, pathEnv);
47704
+ return (await loadHostClaude()).resolveClaudeExecutable(executable, pathEnv);
44953
47705
  } catch (error) {
44954
47706
  const code = error.code;
44955
47707
  if (typeof code === "string") {
44956
- if ((0, import_node_path13.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
47708
+ if ((0, import_node_path17.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
44957
47709
  const detail = error instanceof Error ? error.message : code;
44958
47710
  throw new Error(
44959
47711
  `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`
@@ -44964,13 +47716,13 @@ function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEn
44964
47716
  throw error;
44965
47717
  }
44966
47718
  }
44967
- function resolveDetachedCodexExecutable(executable = "codex-acp", pathEnv = process.env.PATH) {
47719
+ async function resolveDetachedCodexExecutable(executable = "codex-acp", pathEnv = process.env.PATH) {
44968
47720
  try {
44969
- return loadHostCodex().resolveCodexExecutable(executable, pathEnv);
47721
+ return (await loadHostCodex()).resolveCodexExecutable(executable, pathEnv);
44970
47722
  } catch (error) {
44971
47723
  const code = error.code;
44972
47724
  if (typeof code === "string") {
44973
- if ((0, import_node_path13.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
47725
+ if ((0, import_node_path17.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
44974
47726
  const detail = error instanceof Error ? error.message : code;
44975
47727
  throw new Error(
44976
47728
  `could not use --codex-executable: ${detail}; install the current bridge with npm install -g @agentclientprotocol/codex-acp@latest if this path should be replaced`
@@ -45392,7 +48144,7 @@ async function runListenStart(args) {
45392
48144
  assertDurableListenerCredential(agent);
45393
48145
  const principalId = agent.principalId;
45394
48146
  const cwd = args.optional("cwd") ?? process.cwd();
45395
- if (!(0, import_node_path13.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
48147
+ if (!(0, import_node_path17.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
45396
48148
  const permissionMode = listenerPermissionMode(args.optional("permissions"));
45397
48149
  const stateDirectory2 = listenerStateDirectory(args);
45398
48150
  const paths = listenerPaths({
@@ -45439,7 +48191,7 @@ async function runListenStart(args) {
45439
48191
  });
45440
48192
  } else {
45441
48193
  const entrypoint = process.argv[1];
45442
- if (!entrypoint || !(0, import_node_path13.isAbsolute)(entrypoint)) {
48194
+ if (!entrypoint || !(0, import_node_path17.isAbsolute)(entrypoint)) {
45443
48195
  throw new Error("cannot locate the cswarm executable for detached start");
45444
48196
  }
45445
48197
  const artifact = JSON.stringify(agentCredentialArtifact({
@@ -45449,14 +48201,14 @@ async function runListenStart(args) {
45449
48201
  token: agent.token,
45450
48202
  expiresAt: agent.expiresAt
45451
48203
  }));
45452
- const opencodeExecutable = provider === "opencode" && args.optional("opencode-executable") !== void 0 ? loadHostOpenCode().resolveOpenCodeExecutable(args.required("opencode-executable")) : void 0;
48204
+ const opencodeExecutable = provider === "opencode" && args.optional("opencode-executable") !== void 0 ? (await loadHostOpenCode()).resolveOpenCodeExecutable(args.required("opencode-executable")) : void 0;
45453
48205
  let claudeExecutable;
45454
48206
  if (provider === "claude" && args.optional("claude-executable") !== void 0) {
45455
- claudeExecutable = resolveDetachedClaudeExecutable(args.required("claude-executable"));
48207
+ claudeExecutable = await resolveDetachedClaudeExecutable(args.required("claude-executable"));
45456
48208
  }
45457
48209
  let codexExecutable;
45458
48210
  if (provider === "codex" && args.optional("codex-executable") !== void 0) {
45459
- codexExecutable = resolveDetachedCodexExecutable(args.required("codex-executable"));
48211
+ codexExecutable = await resolveDetachedCodexExecutable(args.required("codex-executable"));
45460
48212
  }
45461
48213
  const startedAtFloorMs = Date.now();
45462
48214
  const child = await spawnDetachedListener({
@@ -45606,7 +48358,7 @@ async function runListenSupervisor(args) {
45606
48358
  const agent = await agentCredential(args, { implicitStdin: true });
45607
48359
  assertDurableListenerCredential(agent, principalId);
45608
48360
  const cwd = args.required("cwd");
45609
- if (!(0, import_node_path13.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
48361
+ if (!(0, import_node_path17.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
45610
48362
  const status = await runConfiguredListener({
45611
48363
  cloud,
45612
48364
  workspaceId: workspaceId2,
@@ -45919,7 +48671,7 @@ local ${local.state} server-live ${server.is_live} server-session ${server.sessi
45919
48671
  const customContextPath = args.optional("session-context");
45920
48672
  if (customContextPath !== void 0) {
45921
48673
  const root = defaultSessionRootDirectory();
45922
- if (!(0, import_node_path13.resolve)(customContextPath).startsWith(`${root}${import_node_path13.sep}`)) {
48674
+ if (!(0, import_node_path17.resolve)(customContextPath).startsWith(`${root}${import_node_path17.sep}`)) {
45923
48675
  throw new SessionContextError(
45924
48676
  "session_context_outside_default_tree",
45925
48677
  `--session-context must lie under ${root} so listen start and hook check can find it; omit the flag to use the default path`
@@ -45933,7 +48685,7 @@ local ${local.state} server-live ${server.is_live} server-session ${server.sessi
45933
48685
  );
45934
48686
  const agent = await agentCredential(args);
45935
48687
  const tokenFile = args.optional("agent-token-file");
45936
- if (tokenFile === void 0 || !(0, import_node_path13.isAbsolute)(tokenFile)) {
48688
+ if (tokenFile === void 0 || !(0, import_node_path17.isAbsolute)(tokenFile)) {
45937
48689
  throw new Error(
45938
48690
  "session start needs --agent-token-file <absolute-path> so the context can reference the sole token file"
45939
48691
  );
@@ -45943,7 +48695,7 @@ local ${local.state} server-live ${server.is_live} server-session ${server.sessi
45943
48695
  target: cloud,
45944
48696
  workspaceId: selectedWorkspace,
45945
48697
  credential: agent.token,
45946
- tokenFile: (0, import_node_path13.resolve)(tokenFile),
48698
+ tokenFile: (0, import_node_path17.resolve)(tokenFile),
45947
48699
  tokenPrincipalId: agent.principalId,
45948
48700
  mode: mode3,
45949
48701
  provider,
@@ -46029,8 +48781,8 @@ async function listenerHookSurfacePresent(instanceDirectory, cwd, principalId) {
46029
48781
  if (surface.exists) return true;
46030
48782
  const repositoryRoot = gitRepositoryRoot(cwd) ?? cwd;
46031
48783
  const settingsPaths = /* @__PURE__ */ new Set([
46032
- (0, import_node_path13.join)(repositoryRoot, CLAUDE_PROJECT_SETTINGS_IGNORE_LINE),
46033
- (0, import_node_path13.join)(repositoryRoot, CLAUDE_REPO_SETTINGS_IGNORE_LINE),
48784
+ (0, import_node_path17.join)(repositoryRoot, CLAUDE_PROJECT_SETTINGS_IGNORE_LINE),
48785
+ (0, import_node_path17.join)(repositoryRoot, CLAUDE_REPO_SETTINGS_IGNORE_LINE),
46034
48786
  userClaudeSettingsTarget().path
46035
48787
  ]);
46036
48788
  for (const path of settingsPaths) {
@@ -46096,19 +48848,19 @@ function claudeUserPromptHookSnippet(principalId) {
46096
48848
  var CLAUDE_PROJECT_SETTINGS_IGNORE_LINE = ".claude/settings.local.json";
46097
48849
  var CLAUDE_REPO_SETTINGS_IGNORE_LINE = ".claude/settings.json";
46098
48850
  function claudeUserScopeWarning(settingsPath) {
46099
- return `Warning: --user scope writes settings to ${(0, import_node_path13.dirname)(settingsPath)} and applies to every Claude Code session that reads that directory.`;
48851
+ return `Warning: --user scope writes settings to ${(0, import_node_path17.dirname)(settingsPath)} and applies to every Claude Code session that reads that directory.`;
46100
48852
  }
46101
48853
  function userClaudeSettingsTarget() {
46102
48854
  const configured = process.env.CLAUDE_CONFIG_DIR;
46103
- const directory = configured && configured.length > 0 ? (0, import_node_path13.resolve)(configured) : (0, import_node_path13.join)((0, import_node_os7.homedir)(), ".claude");
48855
+ const directory = configured && configured.length > 0 ? (0, import_node_path17.resolve)(configured) : (0, import_node_path17.join)((0, import_node_os8.homedir)(), ".claude");
46104
48856
  return {
46105
- path: (0, import_node_path13.join)(directory, "settings.json"),
48857
+ path: (0, import_node_path17.join)(directory, "settings.json"),
46106
48858
  scope: "user",
46107
48859
  projectRoot: null
46108
48860
  };
46109
48861
  }
46110
48862
  function gitRepositoryRoot(cwd) {
46111
- const result = (0, import_node_child_process5.spawnSync)(
48863
+ const result = (0, import_node_child_process8.spawnSync)(
46112
48864
  "git",
46113
48865
  ["-C", cwd, "rev-parse", "--show-toplevel"],
46114
48866
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
@@ -46118,7 +48870,7 @@ function gitRepositoryRoot(cwd) {
46118
48870
  }
46119
48871
  if (result.status !== 0) return null;
46120
48872
  const root = result.stdout.trim();
46121
- if (!(0, import_node_path13.isAbsolute)(root)) {
48873
+ if (!(0, import_node_path17.isAbsolute)(root)) {
46122
48874
  throw new Error("hook could not resolve an absolute repository root");
46123
48875
  }
46124
48876
  return root;
@@ -46126,14 +48878,14 @@ function gitRepositoryRoot(cwd) {
46126
48878
  function projectClaudeSettingsTarget(scope, ignoreLine) {
46127
48879
  const root = gitRepositoryRoot(process.cwd());
46128
48880
  const base = root ?? process.cwd();
46129
- const path = (0, import_node_path13.join)(base, ignoreLine);
48881
+ const path = (0, import_node_path17.join)(base, ignoreLine);
46130
48882
  if (root === null) return { path, scope, projectRoot: base };
46131
- const tracked = (0, import_node_child_process5.spawnSync)(
48883
+ const tracked = (0, import_node_child_process8.spawnSync)(
46132
48884
  "git",
46133
48885
  ["-C", root, "ls-files", "--error-unmatch", "--", ignoreLine],
46134
48886
  { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
46135
48887
  );
46136
- const ignored = (0, import_node_child_process5.spawnSync)(
48888
+ const ignored = (0, import_node_child_process8.spawnSync)(
46137
48889
  "git",
46138
48890
  ["-C", root, "check-ignore", "--quiet", "--", ignoreLine],
46139
48891
  { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
@@ -46143,7 +48895,7 @@ function projectClaudeSettingsTarget(scope, ignoreLine) {
46143
48895
  }
46144
48896
  if (tracked.status === 0 || ignored.status !== 0) {
46145
48897
  throw new Error(
46146
- `Refusing to write ${path}: repository Claude settings could be staged and shared with every checkout. ` + (tracked.status === 0 ? "It is already tracked; remove it from Git tracking first. " : "") + `Add this exact line to ${(0, import_node_path13.join)(root, ".gitignore")}: ${ignoreLine}`
48898
+ `Refusing to write ${path}: repository Claude settings could be staged and shared with every checkout. ` + (tracked.status === 0 ? "It is already tracked; remove it from Git tracking first. " : "") + `Add this exact line to ${(0, import_node_path17.join)(root, ".gitignore")}: ${ignoreLine}`
46147
48899
  );
46148
48900
  }
46149
48901
  return { path, scope, projectRoot: root };
@@ -46158,7 +48910,7 @@ function claudeSettingsTarget(args) {
46158
48910
  function readClaudeSettings(path) {
46159
48911
  let raw;
46160
48912
  try {
46161
- raw = (0, import_node_fs2.readFileSync)(path, "utf8");
48913
+ raw = (0, import_node_fs6.readFileSync)(path, "utf8");
46162
48914
  } catch (error) {
46163
48915
  if (error.code === "ENOENT") return {};
46164
48916
  throw error;
@@ -46358,8 +49110,8 @@ async function runHook(args) {
46358
49110
  process.stdout.write(`${claudeUserScopeWarning(path)}
46359
49111
  `);
46360
49112
  }
46361
- (0, import_node_fs2.mkdirSync)((0, import_node_path13.dirname)(path), { recursive: true });
46362
- (0, import_node_fs2.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
49113
+ (0, import_node_fs6.mkdirSync)((0, import_node_path17.dirname)(path), { recursive: true });
49114
+ (0, import_node_fs6.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
46363
49115
  `, {
46364
49116
  encoding: "utf8",
46365
49117
  mode: 384
@@ -46445,8 +49197,8 @@ async function uploadNamedFile(context, name, bytes, options = {}) {
46445
49197
  credential: context.selected.bearer,
46446
49198
  fetcher: context.selected.fetcher
46447
49199
  };
46448
- const fileId = (0, import_node_crypto19.randomUUID)();
46449
- const versionId = (0, import_node_crypto19.randomUUID)();
49200
+ const fileId = (0, import_node_crypto20.randomUUID)();
49201
+ const versionId = (0, import_node_crypto20.randomUUID)();
46450
49202
  const createCommandId = newCommandId();
46451
49203
  const commitCommandId = newCommandId();
46452
49204
  const created = await onceRetried(
@@ -46476,11 +49228,11 @@ async function runFilePut(args) {
46476
49228
  const context = await fileContext(args, ["name"], 3);
46477
49229
  let bytes;
46478
49230
  try {
46479
- bytes = (0, import_node_fs2.readFileSync)(localPath);
49231
+ bytes = (0, import_node_fs6.readFileSync)(localPath);
46480
49232
  } catch {
46481
49233
  throw new Error(`could not read ${localPath}; check the path and permissions`);
46482
49234
  }
46483
- const name = args.optional("name") ?? (0, import_node_path13.basename)(localPath);
49235
+ const name = args.optional("name") ?? (0, import_node_path17.basename)(localPath);
46484
49236
  const committed = await uploadNamedFile(context, name, bytes);
46485
49237
  if (args.has("json")) {
46486
49238
  process.stdout.write(`${JSON.stringify(committed, null, 2)}
@@ -46545,12 +49297,12 @@ async function runFileGet(args) {
46545
49297
  fetcher: context.selected.fetcher
46546
49298
  };
46547
49299
  const grant = await fileDownloadUrl(send, { fileId, versionN });
46548
- const destination = args.optional("out") ?? (0, import_node_path13.basename)(grant.name);
49300
+ const destination = args.optional("out") ?? (0, import_node_path17.basename)(grant.name);
46549
49301
  const bytes = await onceRetried(
46550
49302
  (attempt) => getObject(context.cloud, grant.download_path, fetch, attempt),
46551
49303
  {}
46552
49304
  );
46553
- writeDestination(destination, bytes, args.has("force"), import_node_fs2.writeFileSync);
49305
+ writeDestination(destination, bytes, args.has("force"), import_node_fs6.writeFileSync);
46554
49306
  if (args.has("json")) {
46555
49307
  process.stdout.write(
46556
49308
  `${JSON.stringify(
@@ -46757,7 +49509,7 @@ async function runBrainPut(args) {
46757
49509
  let bytes;
46758
49510
  if (localPath) {
46759
49511
  try {
46760
- bytes = (0, import_node_fs2.readFileSync)(localPath);
49512
+ bytes = (0, import_node_fs6.readFileSync)(localPath);
46761
49513
  } catch {
46762
49514
  throw new Error(`could not read ${localPath}; check the path and permissions`);
46763
49515
  }
@@ -47065,7 +49817,7 @@ async function runDogfood(args) {
47065
49817
  const { selectedWorkspace, bearer } = await commandWorkspaceAndCredential(args, cloud);
47066
49818
  const client = new ThinCommandClient(cloud);
47067
49819
  const route = stream(args);
47068
- const taskId = args.optional("task-id") ?? (0, import_node_crypto19.randomUUID)();
49820
+ const taskId = args.optional("task-id") ?? (0, import_node_crypto20.randomUUID)();
47069
49821
  const ttl = Number(args.optional("ttl-ms") ?? "3600000");
47070
49822
  if (!Number.isSafeInteger(ttl) || ttl <= 0 || ttl > 144e5) {
47071
49823
  throw new Error("--ttl-ms must be an integer in 1..14400000");
@@ -47128,10 +49880,10 @@ async function runSeed(args) {
47128
49880
  throw new Error("DATABASE_URL is required for the fixture bridge");
47129
49881
  }
47130
49882
  const tokenOut = process.env.SEED_TOKEN_OUT;
47131
- if (!tokenOut || !(0, import_node_path13.isAbsolute)(tokenOut)) {
49883
+ if (!tokenOut || !(0, import_node_path17.isAbsolute)(tokenOut)) {
47132
49884
  throw new Error("SEED_TOKEN_OUT must be an absolute path");
47133
49885
  }
47134
- const tokenFile = await (0, import_promises9.open)(tokenOut, "wx", 384).catch((error) => {
49886
+ const tokenFile = await (0, import_promises10.open)(tokenOut, "wx", 384).catch((error) => {
47135
49887
  if (error.code === "EEXIST") {
47136
49888
  throw new Error("SEED_TOKEN_OUT already exists; refusing to overwrite it");
47137
49889
  }
@@ -47170,7 +49922,7 @@ async function runSeed(args) {
47170
49922
  tokenWritten = true;
47171
49923
  }
47172
49924
  await tokenFile.close();
47173
- if (!tokenWritten) await (0, import_promises9.unlink)(tokenOut);
49925
+ if (!tokenWritten) await (0, import_promises10.unlink)(tokenOut);
47174
49926
  process.stdout.write(`${JSON.stringify({
47175
49927
  userId: result.userId,
47176
49928
  membershipRole: result.membershipRole,
@@ -47183,7 +49935,7 @@ async function runSeed(args) {
47183
49935
  `);
47184
49936
  } catch (error) {
47185
49937
  await tokenFile.close().catch(() => void 0);
47186
- if (!tokenWritten) await (0, import_promises9.unlink)(tokenOut).catch(() => void 0);
49938
+ if (!tokenWritten) await (0, import_promises10.unlink)(tokenOut).catch(() => void 0);
47187
49939
  throw error;
47188
49940
  }
47189
49941
  }
@@ -47391,54 +50143,69 @@ function exitCodeFor(error) {
47391
50143
  function safeParagraph(message) {
47392
50144
  return message.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ").slice(0, 2e3);
47393
50145
  }
47394
- main().catch((error) => {
47395
- if (process.argv[2] === "hook" && process.argv[3] === "check") {
47396
- process.exitCode = 0;
47397
- return;
50146
+ function isCliMain() {
50147
+ if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
50148
+ return true;
47398
50149
  }
47399
- if (error instanceof RenewalReauthorisationRequired || error instanceof RenewalRevoked || error instanceof RenewalSuspended) {
47400
- process.stderr.write(`${safeParagraph(error.message)}
47401
- `);
47402
- process.exitCode = 1;
47403
- return;
50150
+ if (!process.argv[1]) return false;
50151
+ try {
50152
+ const script = (0, import_node_fs6.realpathSync)(process.argv[1]);
50153
+ const modulePath = (0, import_node_fs6.realpathSync)((0, import_node_url.fileURLToPath)(import_meta.url));
50154
+ return script === modulePath;
50155
+ } catch {
50156
+ return false;
47404
50157
  }
47405
- if (error instanceof WorkspaceCliError) {
47406
- const structured = error.structured();
47407
- const verb = process.argv[2];
47408
- const json = process.argv.includes("--json") && (verb === "status" || verb === "workspaces" || verb === "use" || verb === "working-on" || verb === "note" || verb === "ask" || verb === "reply" || verb === "receipt" || verb === "feed" || verb === "inbox" || verb === "file" || verb === "brain");
47409
- if (json) {
47410
- process.stdout.write(`${JSON.stringify(structured, null, 2)}
50158
+ }
50159
+ if (isCliMain()) {
50160
+ main().catch((error) => {
50161
+ if (process.argv[2] === "hook" && process.argv[3] === "check") {
50162
+ process.exitCode = 0;
50163
+ return;
50164
+ }
50165
+ if (error instanceof RenewalReauthorisationRequired || error instanceof RenewalRevoked || error instanceof RenewalSuspended) {
50166
+ process.stderr.write(`${safeParagraph(error.message)}
47411
50167
  `);
47412
- } else {
47413
- process.stderr.write(`cswarm: ${error.message}
50168
+ process.exitCode = 1;
50169
+ return;
50170
+ }
50171
+ if (error instanceof WorkspaceCliError) {
50172
+ const structured = error.structured();
50173
+ const verb = process.argv[2];
50174
+ const json = process.argv.includes("--json") && (verb === "status" || verb === "workspaces" || verb === "use" || verb === "working-on" || verb === "note" || verb === "ask" || verb === "reply" || verb === "receipt" || verb === "feed" || verb === "inbox" || verb === "file" || verb === "brain");
50175
+ if (json) {
50176
+ process.stdout.write(`${JSON.stringify(structured, null, 2)}
50177
+ `);
50178
+ } else {
50179
+ process.stderr.write(`cswarm: ${error.message}
47414
50180
  `);
47415
- const projects = structured.projects;
47416
- if (Array.isArray(projects) && projects.length > 0) {
47417
- process.stderr.write("Available workspaces:\n");
47418
- for (const project of projects) {
47419
- if (!project || typeof project !== "object") continue;
47420
- const row = project;
47421
- process.stderr.write(
47422
- `- ${String(row.name)} (${String(row.workspace_id)}) \u2014 ${String(row.role)}
50181
+ const projects = structured.projects;
50182
+ if (Array.isArray(projects) && projects.length > 0) {
50183
+ process.stderr.write("Available workspaces:\n");
50184
+ for (const project of projects) {
50185
+ if (!project || typeof project !== "object") continue;
50186
+ const row = project;
50187
+ process.stderr.write(
50188
+ `- ${String(row.name)} (${String(row.workspace_id)}) \u2014 ${String(row.role)}
47423
50189
  `
47424
- );
50190
+ );
50191
+ }
47425
50192
  }
47426
50193
  }
50194
+ process.exitCode = 1;
50195
+ return;
47427
50196
  }
47428
- process.exitCode = 1;
47429
- return;
47430
- }
47431
- if (error instanceof UsageError) {
47432
- process.stderr.write(`cswarm: ${safeError(error)}
50197
+ if (error instanceof UsageError) {
50198
+ process.stderr.write(`cswarm: ${safeError(error)}
47433
50199
  ${usage()}
47434
50200
  `);
47435
- process.exitCode = 1;
47436
- return;
47437
- }
47438
- process.stderr.write(`cswarm: ${safeError(error)}
50201
+ process.exitCode = 1;
50202
+ return;
50203
+ }
50204
+ process.stderr.write(`cswarm: ${safeError(error)}
47439
50205
  `);
47440
- process.exitCode = exitCodeFor(error);
47441
- });
50206
+ process.exitCode = exitCodeFor(error);
50207
+ });
50208
+ }
47442
50209
  // Annotate the CommonJS export names for ESM import in node:
47443
50210
  0 && (module.exports = {
47444
50211
  CHANNEL_SUBCOMMAND_NAMES,
@@ -47448,6 +50215,7 @@ ${usage()}
47448
50215
  clampTurnBudgetToCredential,
47449
50216
  claudeUserPromptHookSnippet,
47450
50217
  describeAudience,
50218
+ isCliMain,
47451
50219
  listenerFailureMessage,
47452
50220
  listenerHostLimits,
47453
50221
  listenerMainHostLimits,