commonswarm 0.1.38 → 0.1.40
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 +276 -80
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -13520,7 +13520,7 @@ __export(cli_exports, {
|
|
|
13520
13520
|
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer
|
|
13521
13521
|
});
|
|
13522
13522
|
module.exports = __toCommonJS(cli_exports);
|
|
13523
|
-
var
|
|
13523
|
+
var import_node_crypto20 = require("node:crypto");
|
|
13524
13524
|
var import_node_fs7 = require("node:fs");
|
|
13525
13525
|
var import_promises11 = require("node:fs/promises");
|
|
13526
13526
|
var import_node_path20 = require("node:path");
|
|
@@ -33451,6 +33451,7 @@ var FileListenerEffectStore = class {
|
|
|
33451
33451
|
};
|
|
33452
33452
|
|
|
33453
33453
|
// src/listener/grok-model.ts
|
|
33454
|
+
var import_node_crypto14 = require("node:crypto");
|
|
33454
33455
|
var import_promises5 = require("node:fs/promises");
|
|
33455
33456
|
var import_node_os7 = require("node:os");
|
|
33456
33457
|
var import_node_path11 = require("node:path");
|
|
@@ -33695,7 +33696,7 @@ var GrokListenerModel = class {
|
|
|
33695
33696
|
clientName: "cswarm-listener"
|
|
33696
33697
|
});
|
|
33697
33698
|
try {
|
|
33698
|
-
await
|
|
33699
|
+
await this.enablePromptsAfterGrokCanary(handle);
|
|
33699
33700
|
this.workerCanary = false;
|
|
33700
33701
|
this.worker = handle;
|
|
33701
33702
|
return handle;
|
|
@@ -33704,6 +33705,37 @@ var GrokListenerModel = class {
|
|
|
33704
33705
|
throw error;
|
|
33705
33706
|
}
|
|
33706
33707
|
}
|
|
33708
|
+
/** Force Grok's measured shell permission path without changing worker cwd. */
|
|
33709
|
+
async enablePromptsAfterGrokCanary(handle) {
|
|
33710
|
+
const sentinelPath = (0, import_node_path11.join)(
|
|
33711
|
+
(0, import_node_os7.tmpdir)(),
|
|
33712
|
+
`cswarm-grok-permission-canary-${process.pid}-${(0, import_node_crypto14.randomUUID)()}`
|
|
33713
|
+
);
|
|
33714
|
+
let canaryError;
|
|
33715
|
+
try {
|
|
33716
|
+
await handle.session.enablePromptsAfterCanary({
|
|
33717
|
+
timeoutMs: ACP_CANARY_TIMEOUT_MS,
|
|
33718
|
+
probeText: `Use a shell command to create ${sentinelPath} with content CSWARM_CANARY_NOOP. You must use the shell. Do nothing else.`,
|
|
33719
|
+
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
33720
|
+
});
|
|
33721
|
+
} catch (error) {
|
|
33722
|
+
canaryError = error;
|
|
33723
|
+
}
|
|
33724
|
+
let sentinelCreated = false;
|
|
33725
|
+
try {
|
|
33726
|
+
await (0, import_promises5.lstat)(sentinelPath);
|
|
33727
|
+
sentinelCreated = true;
|
|
33728
|
+
await (0, import_promises5.unlink)(sentinelPath);
|
|
33729
|
+
} catch (error) {
|
|
33730
|
+
if (error.code !== "ENOENT") throw error;
|
|
33731
|
+
}
|
|
33732
|
+
if (sentinelCreated) {
|
|
33733
|
+
throw new AcpPermissionCanaryError(
|
|
33734
|
+
"Grok bridge wrote the permission canary sentinel; permission denial did not block the write"
|
|
33735
|
+
);
|
|
33736
|
+
}
|
|
33737
|
+
if (canaryError !== void 0) throw canaryError;
|
|
33738
|
+
}
|
|
33707
33739
|
/** Validate the operator's login artifact without copying or replacing its home. */
|
|
33708
33740
|
async validateLocalAuth() {
|
|
33709
33741
|
if (this.options.open) return;
|
|
@@ -33760,7 +33792,7 @@ var GrokListenerModel = class {
|
|
|
33760
33792
|
};
|
|
33761
33793
|
|
|
33762
33794
|
// src/listener/opencode-model.ts
|
|
33763
|
-
var
|
|
33795
|
+
var import_node_crypto15 = require("node:crypto");
|
|
33764
33796
|
var import_promises6 = require("node:fs/promises");
|
|
33765
33797
|
var import_node_path12 = require("node:path");
|
|
33766
33798
|
function asError(error) {
|
|
@@ -33786,7 +33818,7 @@ var OpenCodeListenerModel = class {
|
|
|
33786
33818
|
prepareWorkerCwd;
|
|
33787
33819
|
permissionMode;
|
|
33788
33820
|
pendingOpenWaitMs;
|
|
33789
|
-
instanceId = (0,
|
|
33821
|
+
instanceId = (0, import_node_crypto15.randomUUID)();
|
|
33790
33822
|
worker = null;
|
|
33791
33823
|
workerHome = null;
|
|
33792
33824
|
/** Worker homes retained after failed close or unsettled open. */
|
|
@@ -34148,7 +34180,13 @@ var OpenCodeListenerModel = class {
|
|
|
34148
34180
|
if (this.closed || this.cancelled || generation !== this.openGeneration) {
|
|
34149
34181
|
await this.performSingleOwnerCleanup(handle, home, this.instanceId, pending);
|
|
34150
34182
|
}
|
|
34151
|
-
|
|
34183
|
+
if (this.options.onCanaryAttempt) {
|
|
34184
|
+
await handle.session.enablePromptsAfterCanary({
|
|
34185
|
+
onAttempt: this.options.onCanaryAttempt
|
|
34186
|
+
});
|
|
34187
|
+
} else {
|
|
34188
|
+
await handle.session.enablePromptsAfterCanary();
|
|
34189
|
+
}
|
|
34152
34190
|
if (this.closed || this.cancelled || generation !== this.openGeneration) {
|
|
34153
34191
|
await this.performSingleOwnerCleanup(handle, home, this.instanceId, pending);
|
|
34154
34192
|
}
|
|
@@ -34193,7 +34231,7 @@ var OpenCodeListenerModel = class {
|
|
|
34193
34231
|
};
|
|
34194
34232
|
|
|
34195
34233
|
// src/listener/claude-model.ts
|
|
34196
|
-
var
|
|
34234
|
+
var import_node_crypto16 = require("node:crypto");
|
|
34197
34235
|
var import_promises7 = require("node:fs/promises");
|
|
34198
34236
|
var import_node_os8 = require("node:os");
|
|
34199
34237
|
var import_node_path13 = require("node:path");
|
|
@@ -34336,13 +34374,14 @@ var ClaudeListenerModel = class {
|
|
|
34336
34374
|
async enablePromptsAfterClaudeCanary(handle) {
|
|
34337
34375
|
const sentinelPath = (0, import_node_path13.join)(
|
|
34338
34376
|
(0, import_node_os8.tmpdir)(),
|
|
34339
|
-
`cswarm-claude-permission-canary-${process.pid}-${(0,
|
|
34377
|
+
`cswarm-claude-permission-canary-${process.pid}-${(0, import_node_crypto16.randomUUID)()}`
|
|
34340
34378
|
);
|
|
34341
34379
|
let sentinelCreated = false;
|
|
34342
34380
|
try {
|
|
34343
34381
|
await handle.session.enablePromptsAfterCanary({
|
|
34344
34382
|
timeoutMs: ACP_CANARY_TIMEOUT_MS,
|
|
34345
|
-
probeText: `Create the file ${sentinelPath} using the Write tool with content CSWARM_CANARY_NOOP. You must use the Write tool. Do nothing else
|
|
34383
|
+
probeText: `Create the file ${sentinelPath} using the Write tool with content CSWARM_CANARY_NOOP. You must use the Write tool. Do nothing else.`,
|
|
34384
|
+
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
34346
34385
|
});
|
|
34347
34386
|
} finally {
|
|
34348
34387
|
try {
|
|
@@ -34362,7 +34401,7 @@ var ClaudeListenerModel = class {
|
|
|
34362
34401
|
};
|
|
34363
34402
|
|
|
34364
34403
|
// src/listener/codex-model.ts
|
|
34365
|
-
var
|
|
34404
|
+
var import_node_crypto17 = require("node:crypto");
|
|
34366
34405
|
var import_promises8 = require("node:fs/promises");
|
|
34367
34406
|
var import_node_os9 = require("node:os");
|
|
34368
34407
|
var import_node_path14 = require("node:path");
|
|
@@ -34505,13 +34544,14 @@ var CodexListenerModel = class {
|
|
|
34505
34544
|
async enablePromptsAfterCodexCanary(handle) {
|
|
34506
34545
|
const sentinelPath = (0, import_node_path14.join)(
|
|
34507
34546
|
(0, import_node_os9.tmpdir)(),
|
|
34508
|
-
`cswarm-codex-permission-canary-${process.pid}-${(0,
|
|
34547
|
+
`cswarm-codex-permission-canary-${process.pid}-${(0, import_node_crypto17.randomUUID)()}`
|
|
34509
34548
|
);
|
|
34510
34549
|
let sentinelCreated = false;
|
|
34511
34550
|
try {
|
|
34512
34551
|
await handle.session.enablePromptsAfterCanary({
|
|
34513
34552
|
timeoutMs: ACP_CANARY_TIMEOUT_MS,
|
|
34514
|
-
probeText: `Use a shell command to create ${sentinelPath} with content CSWARM_CANARY_NOOP. You must use the shell. Do nothing else
|
|
34553
|
+
probeText: `Use a shell command to create ${sentinelPath} with content CSWARM_CANARY_NOOP. You must use the shell. Do nothing else.`,
|
|
34554
|
+
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
34515
34555
|
});
|
|
34516
34556
|
} finally {
|
|
34517
34557
|
try {
|
|
@@ -34531,7 +34571,7 @@ var CodexListenerModel = class {
|
|
|
34531
34571
|
};
|
|
34532
34572
|
|
|
34533
34573
|
// src/listener/runtime.ts
|
|
34534
|
-
var
|
|
34574
|
+
var import_node_crypto18 = require("node:crypto");
|
|
34535
34575
|
|
|
34536
34576
|
// src/cloud/delivery.ts
|
|
34537
34577
|
var UUID_RE12 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -35487,7 +35527,7 @@ function sameEffectSignal(record, signal) {
|
|
|
35487
35527
|
return record.signalId === signal.id.toLowerCase() && record.signalKind === signal.kind && record.askBody === signal.body && record.askUntil === signal.until && record.senderOwnerRelation === (signal.sender_owner_relation ?? "unknown");
|
|
35488
35528
|
}
|
|
35489
35529
|
function immutableSignalFingerprint(signalId, signalKind2, body, until, senderOwnerRelation) {
|
|
35490
|
-
return (0,
|
|
35530
|
+
return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify([
|
|
35491
35531
|
signalId,
|
|
35492
35532
|
signalKind2,
|
|
35493
35533
|
body,
|
|
@@ -36477,6 +36517,7 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
36477
36517
|
"stoppedAt",
|
|
36478
36518
|
"lastSignalId",
|
|
36479
36519
|
"lastErrorCode",
|
|
36520
|
+
"lastErrorDetail",
|
|
36480
36521
|
"providerVersion",
|
|
36481
36522
|
"providerLastMeasuredVersion",
|
|
36482
36523
|
"lastWorkerStderrTail",
|
|
@@ -36538,7 +36579,7 @@ function parseStatus(raw) {
|
|
|
36538
36579
|
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE15.test(candidate);
|
|
36539
36580
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
36540
36581
|
const nullableTimestamp2 = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
36541
|
-
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE15.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE15.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE15.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path16.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp2(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp2(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp2(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
|
|
36582
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE15.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE15.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE15.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastErrorDetail === void 0 || row.lastErrorDetail === null || typeof row.lastErrorDetail === "string" && row.lastErrorDetail.length > 0 && row.lastErrorDetail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastErrorDetail)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path16.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp2(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp2(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp2(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
|
|
36542
36583
|
throw new Error("stored listener status is malformed");
|
|
36543
36584
|
}
|
|
36544
36585
|
const routeMode = row.routeMode ?? "worker";
|
|
@@ -36554,6 +36595,7 @@ function parseStatus(raw) {
|
|
|
36554
36595
|
lastTerminalDeliveryFailureAt: row.lastTerminalDeliveryFailureAt ?? null,
|
|
36555
36596
|
lastClaimAt: row.lastClaimAt ?? null,
|
|
36556
36597
|
lastAckAt: row.lastAckAt ?? null,
|
|
36598
|
+
lastErrorDetail: row.lastErrorDetail ?? null,
|
|
36557
36599
|
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
|
|
36558
36600
|
providerVersion: row.providerVersion ?? null,
|
|
36559
36601
|
providerLastMeasuredVersion: row.providerLastMeasuredVersion ?? null,
|
|
@@ -36571,6 +36613,9 @@ async function writeListenerStatus(paths, status) {
|
|
|
36571
36613
|
throw new Error("listener status is missing delivery metadata fields");
|
|
36572
36614
|
}
|
|
36573
36615
|
}
|
|
36616
|
+
if (!("lastErrorDetail" in parsed)) {
|
|
36617
|
+
throw new Error("listener status is missing local error detail metadata");
|
|
36618
|
+
}
|
|
36574
36619
|
parseStatus(serialized);
|
|
36575
36620
|
await writeSecureJsonFile(paths.statusPath, serialized);
|
|
36576
36621
|
}
|
|
@@ -36588,6 +36633,9 @@ async function appendListenerEvent(paths, event) {
|
|
|
36588
36633
|
"status",
|
|
36589
36634
|
"failure_code",
|
|
36590
36635
|
"attempt",
|
|
36636
|
+
"total",
|
|
36637
|
+
"passed",
|
|
36638
|
+
"reason",
|
|
36591
36639
|
"delay_ms",
|
|
36592
36640
|
"index",
|
|
36593
36641
|
"delivery_mode",
|
|
@@ -36624,6 +36672,15 @@ async function appendListenerEvent(paths, event) {
|
|
|
36624
36672
|
if (!allowed.has(key2)) {
|
|
36625
36673
|
throw new Error(`listener event field is not allowed: ${key2}`);
|
|
36626
36674
|
}
|
|
36675
|
+
if ((key2 === "attempt" || key2 === "total") && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 1)) {
|
|
36676
|
+
throw new Error("listener event attempt count is not allowed");
|
|
36677
|
+
}
|
|
36678
|
+
if (key2 === "passed" && typeof value !== "boolean") {
|
|
36679
|
+
throw new Error("listener event canary result is not allowed");
|
|
36680
|
+
}
|
|
36681
|
+
if (key2 === "reason" && !(value === null || typeof value === "string" && value.length > 0)) {
|
|
36682
|
+
throw new Error("listener event canary reason is not allowed");
|
|
36683
|
+
}
|
|
36627
36684
|
if (key2 === "delivery_mode" && !(value === null || typeof value === "string" && deliveryModes.has(value))) {
|
|
36628
36685
|
throw new Error("listener event delivery mode is not allowed");
|
|
36629
36686
|
}
|
|
@@ -36658,6 +36715,9 @@ async function appendListenerEvent(paths, event) {
|
|
|
36658
36715
|
throw new Error("listener event contains unsafe text");
|
|
36659
36716
|
}
|
|
36660
36717
|
}
|
|
36718
|
+
if (event.event === "listener_canary_attempt" && (typeof event.attempt !== "number" || typeof event.total !== "number" || event.attempt > event.total || typeof event.passed !== "boolean" || !(event.reason === null || typeof event.reason === "string"))) {
|
|
36719
|
+
throw new Error("listener canary attempt event is incomplete");
|
|
36720
|
+
}
|
|
36661
36721
|
await ensureSecureStateDirectory(paths.instanceDirectory);
|
|
36662
36722
|
const serialized = `${JSON.stringify(event)}
|
|
36663
36723
|
`;
|
|
@@ -36887,7 +36947,7 @@ async function queryListenerControl(paths, command2, timeoutMs = CONTROL_TIMEOUT
|
|
|
36887
36947
|
}
|
|
36888
36948
|
|
|
36889
36949
|
// src/listener/supervisor.ts
|
|
36890
|
-
var
|
|
36950
|
+
var import_node_crypto19 = require("node:crypto");
|
|
36891
36951
|
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;
|
|
36892
36952
|
var LISTENER_RESTART_MAX_ATTEMPTS = 5;
|
|
36893
36953
|
var LISTENER_RESTART_INITIAL_MS = 1e3;
|
|
@@ -36931,6 +36991,17 @@ function safeErrorCode(error) {
|
|
|
36931
36991
|
const name = error.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "_");
|
|
36932
36992
|
return name.slice(0, 96) || "listener_error";
|
|
36933
36993
|
}
|
|
36994
|
+
function localDiagnostic(message, maxChars) {
|
|
36995
|
+
const redacted = message.replace(
|
|
36996
|
+
/swm_(?:agt|inv|cap)_[^\s"'\\]*/gi,
|
|
36997
|
+
"[redacted]"
|
|
36998
|
+
).trim();
|
|
36999
|
+
if (redacted.length === 0) return null;
|
|
37000
|
+
return redacted.slice(0, maxChars);
|
|
37001
|
+
}
|
|
37002
|
+
function safeErrorDetail(error) {
|
|
37003
|
+
return localDiagnostic(error.message, 2048);
|
|
37004
|
+
}
|
|
36934
37005
|
var TAIL_SERIALIZED_BUDGET_BYTES = 3e3;
|
|
36935
37006
|
function fitWorkerStderrTailForLog(tail) {
|
|
36936
37007
|
let fitted = tail.trim();
|
|
@@ -36952,7 +37023,7 @@ async function runListenerSupervisor(options) {
|
|
|
36952
37023
|
const now = options.now ?? Date.now;
|
|
36953
37024
|
const startedAt = iso2(now);
|
|
36954
37025
|
const controller = new AbortController();
|
|
36955
|
-
const proposedInstanceId = (0,
|
|
37026
|
+
const proposedInstanceId = (0, import_node_crypto19.randomUUID)();
|
|
36956
37027
|
let status = {
|
|
36957
37028
|
version: 1,
|
|
36958
37029
|
instanceId: proposedInstanceId,
|
|
@@ -36969,6 +37040,7 @@ async function runListenerSupervisor(options) {
|
|
|
36969
37040
|
stoppedAt: null,
|
|
36970
37041
|
lastSignalId: null,
|
|
36971
37042
|
lastErrorCode: null,
|
|
37043
|
+
lastErrorDetail: null,
|
|
36972
37044
|
providerVersion: null,
|
|
36973
37045
|
providerLastMeasuredVersion: null,
|
|
36974
37046
|
lastWorkerStderrTail: null,
|
|
@@ -37044,6 +37116,7 @@ async function runListenerSupervisor(options) {
|
|
|
37044
37116
|
transition("ready", {
|
|
37045
37117
|
readyAt: event.ts,
|
|
37046
37118
|
lastErrorCode: null,
|
|
37119
|
+
lastErrorDetail: null,
|
|
37047
37120
|
lastWorkerStderrTail: null,
|
|
37048
37121
|
providerVersion: versionNotice?.runningVersion ?? null,
|
|
37049
37122
|
providerLastMeasuredVersion: versionNotice?.lastMeasuredVersion ?? null
|
|
@@ -37051,6 +37124,17 @@ async function runListenerSupervisor(options) {
|
|
|
37051
37124
|
log({ ts: event.ts, event: "listener_ready" });
|
|
37052
37125
|
return;
|
|
37053
37126
|
}
|
|
37127
|
+
if (event.type === "canary_attempt") {
|
|
37128
|
+
log({
|
|
37129
|
+
ts: event.ts,
|
|
37130
|
+
event: "listener_canary_attempt",
|
|
37131
|
+
attempt: event.attempt,
|
|
37132
|
+
total: event.total,
|
|
37133
|
+
passed: event.passed,
|
|
37134
|
+
reason: event.reason === null ? null : localDiagnostic(event.reason, 128)
|
|
37135
|
+
});
|
|
37136
|
+
return;
|
|
37137
|
+
}
|
|
37054
37138
|
if (event.type === "effect") {
|
|
37055
37139
|
status = {
|
|
37056
37140
|
...status,
|
|
@@ -37244,6 +37328,7 @@ async function runListenerSupervisor(options) {
|
|
|
37244
37328
|
transition("starting", {
|
|
37245
37329
|
readyAt: null,
|
|
37246
37330
|
lastErrorCode: restartCode,
|
|
37331
|
+
lastErrorDetail: safeErrorDetail(stop.error),
|
|
37247
37332
|
lastWorkerStderrTail: restartStderrTail,
|
|
37248
37333
|
providerVersion: null,
|
|
37249
37334
|
providerLastMeasuredVersion: null
|
|
@@ -37259,6 +37344,7 @@ async function runListenerSupervisor(options) {
|
|
|
37259
37344
|
transition("stopped", {
|
|
37260
37345
|
stoppedAt,
|
|
37261
37346
|
lastErrorCode: null,
|
|
37347
|
+
lastErrorDetail: null,
|
|
37262
37348
|
lastWorkerStderrTail: null
|
|
37263
37349
|
});
|
|
37264
37350
|
log({ ts: stoppedAt, event: "listener_stopped" });
|
|
@@ -37268,6 +37354,7 @@ async function runListenerSupervisor(options) {
|
|
|
37268
37354
|
transition("failed", {
|
|
37269
37355
|
stoppedAt,
|
|
37270
37356
|
lastErrorCode: code,
|
|
37357
|
+
lastErrorDetail: safeErrorDetail(stop.error),
|
|
37271
37358
|
lastWorkerStderrTail: failedStderrTail
|
|
37272
37359
|
});
|
|
37273
37360
|
log({
|
|
@@ -37289,6 +37376,9 @@ async function runListenerSupervisor(options) {
|
|
|
37289
37376
|
transition("failed", {
|
|
37290
37377
|
stoppedAt,
|
|
37291
37378
|
lastErrorCode: code,
|
|
37379
|
+
lastErrorDetail: safeErrorDetail(
|
|
37380
|
+
error instanceof Error ? error : new Error(String(error))
|
|
37381
|
+
),
|
|
37292
37382
|
lastWorkerStderrTail: failedStderrTail
|
|
37293
37383
|
});
|
|
37294
37384
|
log({
|
|
@@ -38204,6 +38294,7 @@ var HOOK_CHECK_TIMEOUT_MS = 3e3;
|
|
|
38204
38294
|
var HOOK_DEFAULT_COOLDOWN_SECONDS = 30;
|
|
38205
38295
|
var HOOK_SURFACED_IDS_MAX = 1024;
|
|
38206
38296
|
var HOOK_BODY_PREVIEW_CHARS = 240;
|
|
38297
|
+
var HOOK_MULTI_PRINCIPAL_GUIDANCE = "This host runs multiple agents. The CommonSwarm hook needs --principal-id. Reinstall it for this agent: cswarm hook install claude --principal-id <uuid> --write";
|
|
38207
38298
|
function exactKeys2(row, keys) {
|
|
38208
38299
|
const expected = new Set(keys);
|
|
38209
38300
|
return Object.keys(row).length === expected.size && Object.keys(row).every((key2) => expected.has(key2));
|
|
@@ -38433,7 +38524,7 @@ async function listenerIsLive(context) {
|
|
|
38433
38524
|
return false;
|
|
38434
38525
|
}
|
|
38435
38526
|
}
|
|
38436
|
-
async function
|
|
38527
|
+
async function discoverStoredStatusContexts(stateDirectory2) {
|
|
38437
38528
|
let entries;
|
|
38438
38529
|
try {
|
|
38439
38530
|
entries = await (0, import_promises10.readdir)(stateDirectory2, { withFileTypes: true });
|
|
@@ -38450,11 +38541,46 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38450
38541
|
entry.name,
|
|
38451
38542
|
instanceDirectory
|
|
38452
38543
|
);
|
|
38453
|
-
|
|
38454
|
-
if (statusIsLive === false) {
|
|
38544
|
+
if (storedStatus !== null) {
|
|
38455
38545
|
contexts.push({
|
|
38456
38546
|
instanceDirectory,
|
|
38457
38547
|
paths: storedStatus.paths,
|
|
38548
|
+
status: storedStatus.status
|
|
38549
|
+
});
|
|
38550
|
+
}
|
|
38551
|
+
}
|
|
38552
|
+
return contexts;
|
|
38553
|
+
}
|
|
38554
|
+
async function discoverListenerHookPrincipalIds(stateDirectory2 = defaultListenerStateDirectory()) {
|
|
38555
|
+
if (!(0, import_node_path19.isAbsolute)(stateDirectory2)) return [];
|
|
38556
|
+
const stored = await discoverStoredStatusContexts(stateDirectory2);
|
|
38557
|
+
return [...new Set(stored.map((context) => context.status.principalId))].sort();
|
|
38558
|
+
}
|
|
38559
|
+
async function discoverContexts(stateDirectory2, principalIds, isListenerLive = listenerIsLive) {
|
|
38560
|
+
const storedContexts = await discoverStoredStatusContexts(stateDirectory2);
|
|
38561
|
+
const availablePrincipals = new Set(
|
|
38562
|
+
storedContexts.map((context) => context.status.principalId)
|
|
38563
|
+
);
|
|
38564
|
+
let selectedPrincipals;
|
|
38565
|
+
if (principalIds === void 0) {
|
|
38566
|
+
if (availablePrincipals.size > 1) {
|
|
38567
|
+
return { contexts: [], requiresPrincipalScope: true };
|
|
38568
|
+
}
|
|
38569
|
+
selectedPrincipals = availablePrincipals;
|
|
38570
|
+
} else {
|
|
38571
|
+
if (principalIds.some((principalId) => !UUID_RE18.test(principalId))) {
|
|
38572
|
+
return { contexts: [], requiresPrincipalScope: false };
|
|
38573
|
+
}
|
|
38574
|
+
selectedPrincipals = new Set(principalIds.map((principalId) => principalId.toLowerCase()));
|
|
38575
|
+
}
|
|
38576
|
+
const contexts = [];
|
|
38577
|
+
for (const storedStatus of storedContexts) {
|
|
38578
|
+
if (!selectedPrincipals.has(storedStatus.status.principalId)) continue;
|
|
38579
|
+
const statusIsLive = await isListenerLive(storedStatus);
|
|
38580
|
+
if (statusIsLive === false) {
|
|
38581
|
+
contexts.push({
|
|
38582
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38583
|
+
paths: storedStatus.paths,
|
|
38458
38584
|
status: storedStatus.status,
|
|
38459
38585
|
listenerLive: false,
|
|
38460
38586
|
credential: null,
|
|
@@ -38463,22 +38589,21 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38463
38589
|
continue;
|
|
38464
38590
|
}
|
|
38465
38591
|
await deleteSecureJsonFile(
|
|
38466
|
-
(0, import_node_path19.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
38592
|
+
(0, import_node_path19.join)(storedStatus.instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
38467
38593
|
).catch(() => void 0);
|
|
38468
38594
|
try {
|
|
38469
|
-
const credential = await readListenerCredentialState(instanceDirectory);
|
|
38595
|
+
const credential = await readListenerCredentialState(storedStatus.instanceDirectory);
|
|
38470
38596
|
contexts.push({
|
|
38471
|
-
instanceDirectory,
|
|
38472
|
-
paths: storedStatus
|
|
38473
|
-
status: storedStatus
|
|
38597
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38598
|
+
paths: storedStatus.paths,
|
|
38599
|
+
status: storedStatus.status,
|
|
38474
38600
|
listenerLive: statusIsLive,
|
|
38475
38601
|
credential,
|
|
38476
|
-
credentialReadFailed: credential === null
|
|
38602
|
+
credentialReadFailed: credential === null
|
|
38477
38603
|
});
|
|
38478
38604
|
} catch {
|
|
38479
|
-
if (storedStatus === null) continue;
|
|
38480
38605
|
contexts.push({
|
|
38481
|
-
instanceDirectory,
|
|
38606
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38482
38607
|
paths: storedStatus.paths,
|
|
38483
38608
|
status: storedStatus.status,
|
|
38484
38609
|
listenerLive: statusIsLive,
|
|
@@ -38487,7 +38612,7 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38487
38612
|
});
|
|
38488
38613
|
}
|
|
38489
38614
|
}
|
|
38490
|
-
return contexts;
|
|
38615
|
+
return { contexts, requiresPrincipalScope: false };
|
|
38491
38616
|
}
|
|
38492
38617
|
function entryFromSignal(signal, principalId, directory, now) {
|
|
38493
38618
|
const senderName = signal.from_kind === "agent" ? directory?.agents.find((agent) => agent.principal_id === signal.from)?.name ?? null : directory?.members.find((member) => member.user_id === signal.from)?.display_name ?? null;
|
|
@@ -38620,10 +38745,16 @@ async function checkListenerHooks(options) {
|
|
|
38620
38745
|
if (!Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400) {
|
|
38621
38746
|
return "";
|
|
38622
38747
|
}
|
|
38623
|
-
const
|
|
38748
|
+
const discovery = await discoverContexts(
|
|
38624
38749
|
stateDirectory2,
|
|
38750
|
+
options.principalIds,
|
|
38625
38751
|
options.isListenerLive ?? listenerIsLive
|
|
38626
38752
|
);
|
|
38753
|
+
if (discovery.requiresPrincipalScope) {
|
|
38754
|
+
await (options.write ?? (() => void 0))(HOOK_MULTI_PRINCIPAL_GUIDANCE);
|
|
38755
|
+
return HOOK_MULTI_PRINCIPAL_GUIDANCE;
|
|
38756
|
+
}
|
|
38757
|
+
const contexts = discovery.contexts;
|
|
38627
38758
|
if (contexts.length === 0) return "";
|
|
38628
38759
|
const networkAllowed = contexts.some((context) => context.listenerLive !== false) ? await reserveCheck(
|
|
38629
38760
|
stateDirectory2,
|
|
@@ -38849,8 +38980,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
38849
38980
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
38850
38981
|
];
|
|
38851
38982
|
function packageVersion() {
|
|
38852
|
-
if ("0.1.
|
|
38853
|
-
return "0.1.
|
|
38983
|
+
if ("0.1.40".length > 0) {
|
|
38984
|
+
return "0.1.40";
|
|
38854
38985
|
}
|
|
38855
38986
|
try {
|
|
38856
38987
|
const value = JSON.parse(
|
|
@@ -38986,8 +39117,8 @@ Usage:
|
|
|
38986
39117
|
cswarm listen start ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--foreground] [--json]
|
|
38987
39118
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
38988
39119
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
38989
|
-
cswarm hook check [--cooldown <seconds>]
|
|
38990
|
-
cswarm hook install claude [--write]
|
|
39120
|
+
cswarm hook check [--principal-id <uuid> ...] [--cooldown <seconds>]
|
|
39121
|
+
cswarm hook install claude [--principal-id <uuid>] [--write]
|
|
38991
39122
|
cswarm hook uninstall claude --write
|
|
38992
39123
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
38993
39124
|
cswarm new --name "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
@@ -39033,7 +39164,7 @@ Credential selection for command/dogfood:
|
|
|
39033
39164
|
command, dogfood
|
|
39034
39165
|
task protocol commands -- either form
|
|
39035
39166
|
listen start persists durable state, rotates -- needs expires_at
|
|
39036
|
-
hook check reads the listener's owned 0600 credential state;
|
|
39167
|
+
hook check reads only the selected listener's owned 0600 credential state;
|
|
39037
39168
|
never accepts or prints a credential
|
|
39038
39169
|
hook install/uninstall
|
|
39039
39170
|
edits only local Claude Code settings; no credential
|
|
@@ -39065,10 +39196,11 @@ listen start --route worker|main|split chooses where directed messages go. worke
|
|
|
39065
39196
|
is the unchanged default. main queues every ask or note for the interactive session.
|
|
39066
39197
|
split queues messages whose body is longer than --defer-over <chars>; the bound is
|
|
39067
39198
|
1..10000 and an equal-length message stays on the worker path. Run cswarm hook check
|
|
39068
|
-
to surface queued messages.
|
|
39069
|
-
|
|
39070
|
-
|
|
39071
|
-
|
|
39199
|
+
--principal-id <uuid> to surface that agent's queued messages. A bare check works only
|
|
39200
|
+
when the state directory holds one principal. hook check has its own 3s ceiling, exits 0
|
|
39201
|
+
on every outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
39202
|
+
hook install claude prints principal-scoped UserPromptSubmit JSON by default and changes
|
|
39203
|
+
the project's .claude/settings.json only with --write; uninstall also requires --write.
|
|
39072
39204
|
|
|
39073
39205
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
39074
39206
|
stored human login. Agent self-surrender of a token uses --agent-token-file or --agent-token-stdin and never takes the secret on argv. Invite-link accept signs in when needed, then accepts and
|
|
@@ -39449,7 +39581,7 @@ async function runNew(args) {
|
|
|
39449
39581
|
assertWorkspaceName(name);
|
|
39450
39582
|
const cloud = await target(args);
|
|
39451
39583
|
const human = await humanCredential(args, cloud);
|
|
39452
|
-
const proposedId = (0,
|
|
39584
|
+
const proposedId = (0, import_node_crypto20.randomUUID)();
|
|
39453
39585
|
let result;
|
|
39454
39586
|
try {
|
|
39455
39587
|
result = await new ThinCommandClient(cloud).sendConnect({
|
|
@@ -41637,6 +41769,11 @@ function renderListenerStatus(status) {
|
|
|
41637
41769
|
status.lastSignalId ? `Last handled signal: ${status.lastSignalId}.` : "No signal has been handled yet.",
|
|
41638
41770
|
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener error is recorded."
|
|
41639
41771
|
];
|
|
41772
|
+
if (status.lastErrorDetail) {
|
|
41773
|
+
const [first, ...rest] = status.lastErrorDetail.split("\n");
|
|
41774
|
+
lines.push(`Last error detail (local only): ${first}`);
|
|
41775
|
+
for (const line of rest) lines.push(` ${line}`);
|
|
41776
|
+
}
|
|
41640
41777
|
if (status.lastWorkerStderrTail) {
|
|
41641
41778
|
const tailLines = status.lastWorkerStderrTail.split("\n").filter((line) => line.trim().length > 0);
|
|
41642
41779
|
lines.push("Worker stderr (local log only):");
|
|
@@ -41766,6 +41903,9 @@ function listenerFailureMessage(code, provider) {
|
|
|
41766
41903
|
if (provider === "codex") {
|
|
41767
41904
|
return "the Codex bridge did not complete the read-only ACP permission canary; no workspace signal prompt was delivered. Confirm ChatGPT/Codex sign-in, then retry";
|
|
41768
41905
|
}
|
|
41906
|
+
if (provider === "grok") {
|
|
41907
|
+
return "the Grok bridge did not complete the ACP permission canary; no workspace signal prompt was delivered. The local cswarm listen status output includes the final error detail; read it, then retry";
|
|
41908
|
+
}
|
|
41769
41909
|
return "the host did not prove that CommonSwarm controls ACP tool permissions; no model prompt was delivered";
|
|
41770
41910
|
}
|
|
41771
41911
|
if (code === "process_exit") {
|
|
@@ -41918,13 +42058,14 @@ async function runConfiguredListener(options) {
|
|
|
41918
42058
|
lastWorkerStderrTail = tail.length > 0 ? tail : null;
|
|
41919
42059
|
};
|
|
41920
42060
|
};
|
|
41921
|
-
const newModel = () => {
|
|
42061
|
+
const newModel = (onCanaryAttempt) => {
|
|
41922
42062
|
providerVersionNotice = null;
|
|
41923
42063
|
return options.provider === "opencode" ? new OpenCodeListenerModel({
|
|
41924
42064
|
cwd: options.cwd,
|
|
41925
42065
|
permissionMode: options.permissionMode,
|
|
41926
42066
|
promptTimeoutMs: resolveTurnBudgetMs,
|
|
41927
42067
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
42068
|
+
onCanaryAttempt,
|
|
41928
42069
|
onVersionNotice,
|
|
41929
42070
|
...options.model ? { model: options.model } : {},
|
|
41930
42071
|
...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
@@ -41933,6 +42074,7 @@ async function runConfiguredListener(options) {
|
|
|
41933
42074
|
permissionMode: options.permissionMode,
|
|
41934
42075
|
promptTimeoutMs: resolveTurnBudgetMs,
|
|
41935
42076
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
42077
|
+
onCanaryAttempt,
|
|
41936
42078
|
onVersionNotice,
|
|
41937
42079
|
...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
41938
42080
|
}) : options.provider === "codex" ? new CodexListenerModel({
|
|
@@ -41940,6 +42082,7 @@ async function runConfiguredListener(options) {
|
|
|
41940
42082
|
permissionMode: options.permissionMode,
|
|
41941
42083
|
promptTimeoutMs: resolveTurnBudgetMs,
|
|
41942
42084
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
42085
|
+
onCanaryAttempt,
|
|
41943
42086
|
onVersionNotice,
|
|
41944
42087
|
...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
|
|
41945
42088
|
}) : new GrokListenerModel({
|
|
@@ -41947,6 +42090,7 @@ async function runConfiguredListener(options) {
|
|
|
41947
42090
|
permissionMode: options.permissionMode,
|
|
41948
42091
|
promptTimeoutMs: resolveTurnBudgetMs,
|
|
41949
42092
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
42093
|
+
onCanaryAttempt,
|
|
41950
42094
|
onVersionNotice,
|
|
41951
42095
|
...options.model ? { model: options.model } : {},
|
|
41952
42096
|
...options.effort ? { effort: options.effort } : {},
|
|
@@ -42000,13 +42144,23 @@ async function runConfiguredListener(options) {
|
|
|
42000
42144
|
"listener delivery journal was not selected for this instance"
|
|
42001
42145
|
);
|
|
42002
42146
|
}
|
|
42147
|
+
const onCanaryAttempt = (attempt, total, result) => {
|
|
42148
|
+
onEvent({
|
|
42149
|
+
type: "canary_attempt",
|
|
42150
|
+
attempt,
|
|
42151
|
+
total,
|
|
42152
|
+
passed: result.passed,
|
|
42153
|
+
reason: result.reason ?? null,
|
|
42154
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
42155
|
+
});
|
|
42156
|
+
};
|
|
42003
42157
|
return await runListenerRuntime({
|
|
42004
42158
|
target: options.cloud,
|
|
42005
42159
|
workspaceId: options.workspaceId,
|
|
42006
42160
|
principalId: options.principalId,
|
|
42007
42161
|
credentialSession,
|
|
42008
42162
|
store: effectStore,
|
|
42009
|
-
model: newModel(),
|
|
42163
|
+
model: newModel(onCanaryAttempt),
|
|
42010
42164
|
signal,
|
|
42011
42165
|
onEvent,
|
|
42012
42166
|
declareModel: listenerModelLabel(options.provider),
|
|
@@ -42327,7 +42481,13 @@ async function runListen(args) {
|
|
|
42327
42481
|
throw new UsageError("listen requires start, status, or stop");
|
|
42328
42482
|
}
|
|
42329
42483
|
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
42330
|
-
function
|
|
42484
|
+
function scopedClaudeHookCommand(principalId) {
|
|
42485
|
+
return `${CLAUDE_HOOK_COMMAND} --principal-id ${listenerUuid(principalId, "principal-id")}`;
|
|
42486
|
+
}
|
|
42487
|
+
function isCommonSwarmClaudeHook(value) {
|
|
42488
|
+
return typeof value === "string" && (value === CLAUDE_HOOK_COMMAND || /^cswarm hook check --principal-id [0-9a-f-]{36}$/.test(value));
|
|
42489
|
+
}
|
|
42490
|
+
function claudeUserPromptHookSnippet(principalId) {
|
|
42331
42491
|
return {
|
|
42332
42492
|
hooks: {
|
|
42333
42493
|
UserPromptSubmit: [
|
|
@@ -42335,7 +42495,7 @@ function claudeUserPromptHookSnippet() {
|
|
|
42335
42495
|
hooks: [
|
|
42336
42496
|
{
|
|
42337
42497
|
type: "command",
|
|
42338
|
-
command:
|
|
42498
|
+
command: scopedClaudeHookCommand(principalId)
|
|
42339
42499
|
}
|
|
42340
42500
|
]
|
|
42341
42501
|
}
|
|
@@ -42360,21 +42520,40 @@ function readProjectSettings(path) {
|
|
|
42360
42520
|
}
|
|
42361
42521
|
return value;
|
|
42362
42522
|
}
|
|
42363
|
-
function installClaudeHook(settings) {
|
|
42523
|
+
function installClaudeHook(settings, principalId) {
|
|
42364
42524
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? { ...settings.hooks } : {};
|
|
42365
42525
|
const current = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
|
|
42366
|
-
const
|
|
42367
|
-
|
|
42368
|
-
|
|
42369
|
-
|
|
42370
|
-
|
|
42371
|
-
|
|
42372
|
-
|
|
42373
|
-
|
|
42374
|
-
const
|
|
42375
|
-
|
|
42526
|
+
const command2 = scopedClaudeHookCommand(principalId);
|
|
42527
|
+
let installed = false;
|
|
42528
|
+
const groups = [];
|
|
42529
|
+
for (const group of current) {
|
|
42530
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) {
|
|
42531
|
+
groups.push(group);
|
|
42532
|
+
continue;
|
|
42533
|
+
}
|
|
42534
|
+
const row = { ...group };
|
|
42535
|
+
if (!Array.isArray(row.hooks)) {
|
|
42536
|
+
groups.push(group);
|
|
42537
|
+
continue;
|
|
42538
|
+
}
|
|
42539
|
+
const commands = [];
|
|
42540
|
+
for (const hook of row.hooks) {
|
|
42541
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && isCommonSwarmClaudeHook(hook.command)) {
|
|
42542
|
+
if (!installed) {
|
|
42543
|
+
commands.push({ ...hook, command: command2 });
|
|
42544
|
+
installed = true;
|
|
42545
|
+
}
|
|
42546
|
+
continue;
|
|
42547
|
+
}
|
|
42548
|
+
commands.push(hook);
|
|
42549
|
+
}
|
|
42550
|
+
if (commands.length > 0) groups.push({ ...row, hooks: commands });
|
|
42551
|
+
}
|
|
42552
|
+
if (!installed) {
|
|
42553
|
+
const snippetHooks = claudeUserPromptHookSnippet(principalId).hooks.UserPromptSubmit;
|
|
42554
|
+
groups.push(snippetHooks[0]);
|
|
42376
42555
|
}
|
|
42377
|
-
hooks.UserPromptSubmit =
|
|
42556
|
+
hooks.UserPromptSubmit = groups;
|
|
42378
42557
|
return { ...settings, hooks };
|
|
42379
42558
|
}
|
|
42380
42559
|
function uninstallClaudeHook(settings) {
|
|
@@ -42394,7 +42573,7 @@ function uninstallClaudeHook(settings) {
|
|
|
42394
42573
|
groups.push(group);
|
|
42395
42574
|
continue;
|
|
42396
42575
|
}
|
|
42397
|
-
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command
|
|
42576
|
+
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && isCommonSwarmClaudeHook(hook.command)));
|
|
42398
42577
|
if (row.hooks.length > 0) groups.push(row);
|
|
42399
42578
|
}
|
|
42400
42579
|
if (groups.length > 0) hooks.UserPromptSubmit = groups;
|
|
@@ -42406,10 +42585,29 @@ function uninstallClaudeHook(settings) {
|
|
|
42406
42585
|
}
|
|
42407
42586
|
return { ...settings, hooks };
|
|
42408
42587
|
}
|
|
42588
|
+
async function hookInstallPrincipalId(args) {
|
|
42589
|
+
const explicit = args.optional("principal-id");
|
|
42590
|
+
if (explicit !== void 0) return listenerUuid(explicit, "principal-id");
|
|
42591
|
+
const principalIds = await discoverListenerHookPrincipalIds(
|
|
42592
|
+
defaultListenerStateDirectory()
|
|
42593
|
+
);
|
|
42594
|
+
if (principalIds.length === 1) return principalIds[0];
|
|
42595
|
+
if (principalIds.length > 1) {
|
|
42596
|
+
throw new Error(
|
|
42597
|
+
"hook install claude found multiple agents on this host. Choose this agent explicitly: cswarm hook install claude --principal-id <uuid> [--write]"
|
|
42598
|
+
);
|
|
42599
|
+
}
|
|
42600
|
+
throw new Error(
|
|
42601
|
+
"hook install claude could not find a listener principal. Name this agent explicitly: cswarm hook install claude --principal-id <uuid> [--write]"
|
|
42602
|
+
);
|
|
42603
|
+
}
|
|
42409
42604
|
async function runHook(args) {
|
|
42410
42605
|
const command2 = args.positionals[1];
|
|
42411
42606
|
if (command2 === "check") {
|
|
42412
|
-
args.assertShape(["cooldown"], 2);
|
|
42607
|
+
args.assertShape(["cooldown", "principal-id"], 2);
|
|
42608
|
+
const rawPrincipalIds = args.all("principal-id");
|
|
42609
|
+
if (rawPrincipalIds.some((principalId2) => !UUID_RE19.test(principalId2))) return;
|
|
42610
|
+
const principalIds = rawPrincipalIds.map((principalId2) => principalId2.toLowerCase());
|
|
42413
42611
|
const rawCooldown = args.optional("cooldown");
|
|
42414
42612
|
const cooldownSeconds = rawCooldown === void 0 ? void 0 : Number(rawCooldown);
|
|
42415
42613
|
if (cooldownSeconds !== void 0 && (!/^\d+$/.test(rawCooldown) || !Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400)) {
|
|
@@ -42419,43 +42617,41 @@ async function runHook(args) {
|
|
|
42419
42617
|
process.exit(0);
|
|
42420
42618
|
}, 3e3);
|
|
42421
42619
|
hardExit.unref();
|
|
42422
|
-
|
|
42423
|
-
|
|
42424
|
-
|
|
42425
|
-
|
|
42426
|
-
|
|
42427
|
-
|
|
42620
|
+
await runListenerHookCheck({
|
|
42621
|
+
...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
|
|
42622
|
+
...principalIds.length === 0 ? {} : { principalIds },
|
|
42623
|
+
write: async (output) => {
|
|
42624
|
+
await new Promise((resolve, reject) => {
|
|
42625
|
+
process.stdout.write(`${output}
|
|
42428
42626
|
`, (error) => {
|
|
42429
|
-
|
|
42430
|
-
|
|
42431
|
-
});
|
|
42627
|
+
if (error) reject(error);
|
|
42628
|
+
else resolve();
|
|
42432
42629
|
});
|
|
42433
|
-
}
|
|
42434
|
-
}
|
|
42435
|
-
|
|
42436
|
-
|
|
42437
|
-
clearTimeout(hardExit);
|
|
42438
|
-
}
|
|
42630
|
+
});
|
|
42631
|
+
}
|
|
42632
|
+
});
|
|
42633
|
+
return;
|
|
42439
42634
|
}
|
|
42440
42635
|
if (command2 !== "install" && command2 !== "uninstall") {
|
|
42441
42636
|
throw new UsageError("hook requires check, install, or uninstall");
|
|
42442
42637
|
}
|
|
42443
|
-
args.assertShape(["write"], 3);
|
|
42638
|
+
args.assertShape(command2 === "install" ? ["write", "principal-id"] : ["write"], 3);
|
|
42444
42639
|
if (args.positionals[2] !== "claude") {
|
|
42445
42640
|
throw new Error("hook install/uninstall currently supports claude");
|
|
42446
42641
|
}
|
|
42447
42642
|
if (command2 === "uninstall" && !args.has("write")) {
|
|
42448
42643
|
throw new Error("hook uninstall claude requires --write");
|
|
42449
42644
|
}
|
|
42450
|
-
const
|
|
42451
|
-
|
|
42645
|
+
const principalId = command2 === "install" ? await hookInstallPrincipalId(args) : null;
|
|
42646
|
+
const snippet = principalId === null ? null : claudeUserPromptHookSnippet(principalId);
|
|
42647
|
+
if (command2 === "install" && !args.has("write")) {
|
|
42452
42648
|
process.stdout.write(`${JSON.stringify(snippet, null, 2)}
|
|
42453
42649
|
`);
|
|
42454
42650
|
return;
|
|
42455
42651
|
}
|
|
42456
42652
|
const path = projectClaudeSettingsPath();
|
|
42457
42653
|
const settings = readProjectSettings(path);
|
|
42458
|
-
const updated = command2 === "install" ? installClaudeHook(settings) : uninstallClaudeHook(settings);
|
|
42654
|
+
const updated = command2 === "install" ? installClaudeHook(settings, principalId) : uninstallClaudeHook(settings);
|
|
42459
42655
|
(0, import_node_fs7.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
|
|
42460
42656
|
(0, import_node_fs7.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
|
|
42461
42657
|
`, {
|
|
@@ -42463,7 +42659,7 @@ async function runHook(args) {
|
|
|
42463
42659
|
mode: 384
|
|
42464
42660
|
});
|
|
42465
42661
|
process.stdout.write(
|
|
42466
|
-
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${
|
|
42662
|
+
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${scopedClaudeHookCommand(principalId)}
|
|
42467
42663
|
` : `Removed the CommonSwarm UserPromptSubmit hook from ${path}. Other settings were kept.
|
|
42468
42664
|
`
|
|
42469
42665
|
);
|
|
@@ -42541,8 +42737,8 @@ async function runFilePut(args) {
|
|
|
42541
42737
|
workspaceId: context.selected.selectedWorkspace,
|
|
42542
42738
|
credential: context.selected.bearer
|
|
42543
42739
|
};
|
|
42544
|
-
const fileId = (0,
|
|
42545
|
-
const versionId = (0,
|
|
42740
|
+
const fileId = (0, import_node_crypto20.randomUUID)();
|
|
42741
|
+
const versionId = (0, import_node_crypto20.randomUUID)();
|
|
42546
42742
|
const createCommandId = newCommandId();
|
|
42547
42743
|
const commitCommandId = newCommandId();
|
|
42548
42744
|
const created = await onceRetried(
|
|
@@ -42779,7 +42975,7 @@ async function runDogfood(args) {
|
|
|
42779
42975
|
const { selectedWorkspace, bearer } = await commandWorkspaceAndCredential(args, cloud);
|
|
42780
42976
|
const client = new ThinCommandClient(cloud);
|
|
42781
42977
|
const route = stream(args);
|
|
42782
|
-
const taskId = args.optional("task-id") ?? (0,
|
|
42978
|
+
const taskId = args.optional("task-id") ?? (0, import_node_crypto20.randomUUID)();
|
|
42783
42979
|
const ttl = Number(args.optional("ttl-ms") ?? "3600000");
|
|
42784
42980
|
if (!Number.isSafeInteger(ttl) || ttl <= 0 || ttl > 144e5) {
|
|
42785
42981
|
throw new Error("--ttl-ms must be an integer in 1..14400000");
|