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.
- package/cswarm.cjs +3048 -280
- 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:
|
|
4385
|
-
const events = [close, error, leave,
|
|
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
|
|
13531
|
-
var
|
|
13532
|
-
var
|
|
13533
|
-
var
|
|
13534
|
-
var
|
|
13535
|
-
var
|
|
13536
|
-
var
|
|
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
|
|
27709
|
+
const delimiter3 = typarray === 1020 ? ";" : ",";
|
|
24831
27710
|
if (Array.isArray(first) && !first.type)
|
|
24832
|
-
return "{" + xs.map((x) => arraySerializer2(x, serializer, options, typarray)).join(
|
|
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(
|
|
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
|
|
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 ===
|
|
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/
|
|
33756
|
-
|
|
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
|
-
|
|
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
|
|
43845
|
+
return Promise.resolve().then(() => (init_claude(), claude_exports));
|
|
41091
43846
|
}
|
|
41092
43847
|
function loadHostCodex() {
|
|
41093
|
-
return
|
|
43848
|
+
return Promise.resolve().then(() => (init_codex(), codex_exports));
|
|
41094
43849
|
}
|
|
41095
43850
|
function loadHostOpenCode() {
|
|
41096
|
-
return
|
|
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.
|
|
41218
|
-
return "0.1.
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
43235
|
-
versionId: (0,
|
|
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,
|
|
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,
|
|
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 =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
46033
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
48881
|
+
const path = (0, import_node_path17.join)(base, ignoreLine);
|
|
46130
48882
|
if (root === null) return { path, scope, projectRoot: base };
|
|
46131
|
-
const tracked = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
46362
|
-
(0,
|
|
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,
|
|
46449
|
-
const versionId = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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"),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
47395
|
-
if (
|
|
47396
|
-
|
|
47397
|
-
return;
|
|
50146
|
+
function isCliMain() {
|
|
50147
|
+
if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
|
|
50148
|
+
return true;
|
|
47398
50149
|
}
|
|
47399
|
-
if (
|
|
47400
|
-
|
|
47401
|
-
|
|
47402
|
-
|
|
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
|
-
|
|
47406
|
-
|
|
47407
|
-
|
|
47408
|
-
|
|
47409
|
-
|
|
47410
|
-
|
|
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
|
-
|
|
47413
|
-
|
|
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
|
-
|
|
47416
|
-
|
|
47417
|
-
|
|
47418
|
-
|
|
47419
|
-
|
|
47420
|
-
|
|
47421
|
-
|
|
47422
|
-
|
|
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
|
-
|
|
47429
|
-
|
|
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
|
-
|
|
47436
|
-
|
|
47437
|
-
|
|
47438
|
-
|
|
50201
|
+
process.exitCode = 1;
|
|
50202
|
+
return;
|
|
50203
|
+
}
|
|
50204
|
+
process.stderr.write(`cswarm: ${safeError(error)}
|
|
47439
50205
|
`);
|
|
47440
|
-
|
|
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,
|