commonswarm 0.1.23 → 0.1.26
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 +1313 -129
- 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: join17 } = constants_1.CHANNEL_EVENTS;
|
|
4385
|
+
const events = [close, error, leave, join17];
|
|
4386
4386
|
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
|
4387
4387
|
}
|
|
4388
4388
|
/** @internal */
|
|
@@ -13505,11 +13505,14 @@ __export(cli_exports, {
|
|
|
13505
13505
|
EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
|
|
13506
13506
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
13507
13507
|
clampTurnBudgetToCredential: () => clampTurnBudgetToCredential,
|
|
13508
|
+
claudeUserPromptHookSnippet: () => claudeUserPromptHookSnippet,
|
|
13508
13509
|
describeAudience: () => describeAudience,
|
|
13509
13510
|
listenerFailureMessage: () => listenerFailureMessage,
|
|
13510
13511
|
listenerHostLimits: () => listenerHostLimits,
|
|
13511
13512
|
listenerPermissionMode: () => listenerPermissionMode,
|
|
13513
|
+
listenerRouteConfiguration: () => listenerRouteConfiguration,
|
|
13512
13514
|
listenerStatusJson: () => listenerStatusJson,
|
|
13515
|
+
renderListenerStatus: () => renderListenerStatus,
|
|
13513
13516
|
renderRoster: () => renderRoster,
|
|
13514
13517
|
replyRefusalHint: () => replyRefusalHint,
|
|
13515
13518
|
resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
|
|
@@ -13519,9 +13522,9 @@ __export(cli_exports, {
|
|
|
13519
13522
|
module.exports = __toCommonJS(cli_exports);
|
|
13520
13523
|
var import_node_crypto19 = require("node:crypto");
|
|
13521
13524
|
var import_node_fs7 = require("node:fs");
|
|
13522
|
-
var
|
|
13523
|
-
var
|
|
13524
|
-
var
|
|
13525
|
+
var import_promises11 = require("node:fs/promises");
|
|
13526
|
+
var import_node_path19 = require("node:path");
|
|
13527
|
+
var import_promises12 = require("node:readline/promises");
|
|
13525
13528
|
|
|
13526
13529
|
// src/cloud/auth.ts
|
|
13527
13530
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -23273,10 +23276,14 @@ function parseProfile(raw) {
|
|
|
23273
23276
|
}
|
|
23274
23277
|
return value;
|
|
23275
23278
|
}
|
|
23276
|
-
async function withFileLock(stateDirectory2, lockName, work) {
|
|
23279
|
+
async function withFileLock(stateDirectory2, lockName, work, options = {}) {
|
|
23277
23280
|
await secureDirectory(stateDirectory2);
|
|
23278
23281
|
const lockPath = (0, import_node_path.join)(stateDirectory2, `${lockName}.lock`);
|
|
23279
|
-
const
|
|
23282
|
+
const timeoutMs = options.timeoutMs ?? LOCK_TIMEOUT_MS;
|
|
23283
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > LOCK_TIMEOUT_MS) {
|
|
23284
|
+
throw new Error("credential refresh lock timeout is invalid");
|
|
23285
|
+
}
|
|
23286
|
+
const deadline = Date.now() + timeoutMs;
|
|
23280
23287
|
let handle = null;
|
|
23281
23288
|
while (handle === null) {
|
|
23282
23289
|
try {
|
|
@@ -32310,7 +32317,8 @@ var STATES = /* @__PURE__ */ new Set([
|
|
|
32310
32317
|
"done",
|
|
32311
32318
|
"expired",
|
|
32312
32319
|
"failed",
|
|
32313
|
-
"observed"
|
|
32320
|
+
"observed",
|
|
32321
|
+
"routed_main"
|
|
32314
32322
|
]);
|
|
32315
32323
|
var RELATIONS = /* @__PURE__ */ new Set(["same_owner", "cross_owner", "unknown"]);
|
|
32316
32324
|
var SIGNAL_KINDS2 = /* @__PURE__ */ new Set(["ask", "note"]);
|
|
@@ -32373,7 +32381,7 @@ function parseListenerEffectRecord(raw, expectedId) {
|
|
|
32373
32381
|
}
|
|
32374
32382
|
if (row.version === 1) {
|
|
32375
32383
|
rejectUnknownKeys(row, V1_EFFECT_KEYS);
|
|
32376
|
-
if ("signalKind" in row || row.state === "observed") {
|
|
32384
|
+
if ("signalKind" in row || row.state === "observed" || row.state === "routed_main") {
|
|
32377
32385
|
throw new Error("stored listener effect is malformed");
|
|
32378
32386
|
}
|
|
32379
32387
|
return upcastV1Ask(row);
|
|
@@ -32416,6 +32424,10 @@ function parseV2Record(row) {
|
|
|
32416
32424
|
if (typeof row.commandId !== "string" || row.commandId !== "" || row.state !== "observed" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
|
|
32417
32425
|
throw new Error("stored listener effect is malformed");
|
|
32418
32426
|
}
|
|
32427
|
+
} else if (row.state === "routed_main") {
|
|
32428
|
+
if (row.commandId !== "" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
|
|
32429
|
+
throw new Error("stored listener effect is malformed");
|
|
32430
|
+
}
|
|
32419
32431
|
} else {
|
|
32420
32432
|
if (typeof row.commandId !== "string" || !COMMAND_ID_RE2.test(row.commandId) || row.state === "observed") {
|
|
32421
32433
|
throw new Error("stored listener effect is malformed");
|
|
@@ -32478,6 +32490,14 @@ function newObservedNoteRecord(input) {
|
|
|
32478
32490
|
updatedAt: input.updatedAt
|
|
32479
32491
|
};
|
|
32480
32492
|
}
|
|
32493
|
+
function newRoutedMainAskRecord(input) {
|
|
32494
|
+
const base = newObservedNoteRecord(input);
|
|
32495
|
+
return {
|
|
32496
|
+
...base,
|
|
32497
|
+
signalKind: "ask",
|
|
32498
|
+
state: "routed_main"
|
|
32499
|
+
};
|
|
32500
|
+
}
|
|
32481
32501
|
function rejectWrite() {
|
|
32482
32502
|
throw new Error("listener effect write rejected");
|
|
32483
32503
|
}
|
|
@@ -34207,6 +34227,188 @@ var DeliveryCommandClient = class {
|
|
|
34207
34227
|
}
|
|
34208
34228
|
};
|
|
34209
34229
|
|
|
34230
|
+
// src/listener/main-routing.ts
|
|
34231
|
+
var import_node_path14 = require("node:path");
|
|
34232
|
+
var UUID_RE11 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
34233
|
+
var MAX_QUEUE_BYTES = 1024 * 1024;
|
|
34234
|
+
var QUEUE_FILE = "pending-for-main.json";
|
|
34235
|
+
var QUEUE_LOCK = "pending-for-main";
|
|
34236
|
+
var LISTENER_MAIN_QUEUE_MAX = 200;
|
|
34237
|
+
var LISTENER_DEFER_OVER_MIN = 1;
|
|
34238
|
+
var LISTENER_DEFER_OVER_MAX = 1e4;
|
|
34239
|
+
function decideListenerRoute(route, threshold, bodyLength) {
|
|
34240
|
+
if (!Number.isSafeInteger(bodyLength) || bodyLength < 0) {
|
|
34241
|
+
throw new Error("listener route body length must be a non-negative integer");
|
|
34242
|
+
}
|
|
34243
|
+
if (route === "worker") {
|
|
34244
|
+
if (threshold !== null) {
|
|
34245
|
+
throw new Error("worker route cannot have a split threshold");
|
|
34246
|
+
}
|
|
34247
|
+
return "worker";
|
|
34248
|
+
}
|
|
34249
|
+
if (route === "main") {
|
|
34250
|
+
if (threshold !== null) {
|
|
34251
|
+
throw new Error("main route cannot have a split threshold");
|
|
34252
|
+
}
|
|
34253
|
+
return "main";
|
|
34254
|
+
}
|
|
34255
|
+
if (route !== "split") {
|
|
34256
|
+
throw new Error("listener route mode is invalid");
|
|
34257
|
+
}
|
|
34258
|
+
if (threshold === null || !Number.isSafeInteger(threshold) || threshold < LISTENER_DEFER_OVER_MIN || threshold > LISTENER_DEFER_OVER_MAX) {
|
|
34259
|
+
throw new Error("split route threshold is invalid");
|
|
34260
|
+
}
|
|
34261
|
+
return bodyLength > threshold ? "main" : "worker";
|
|
34262
|
+
}
|
|
34263
|
+
function checkedTimestamp2(value) {
|
|
34264
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
34265
|
+
}
|
|
34266
|
+
function parseEntry(value) {
|
|
34267
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34268
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34269
|
+
}
|
|
34270
|
+
const row = value;
|
|
34271
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
34272
|
+
"signalId",
|
|
34273
|
+
"workspaceId",
|
|
34274
|
+
"principalId",
|
|
34275
|
+
"fromId",
|
|
34276
|
+
"fromKind",
|
|
34277
|
+
"kind",
|
|
34278
|
+
"senderName",
|
|
34279
|
+
"body",
|
|
34280
|
+
"createdAt",
|
|
34281
|
+
"queuedAt"
|
|
34282
|
+
]);
|
|
34283
|
+
if (Object.keys(row).some((key2) => !allowed.has(key2))) {
|
|
34284
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34285
|
+
}
|
|
34286
|
+
if (typeof row.signalId !== "string" || !UUID_RE11.test(row.signalId) || typeof row.workspaceId !== "string" || !UUID_RE11.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE11.test(row.principalId) || typeof row.fromId !== "string" || !UUID_RE11.test(row.fromId) || row.fromKind !== "user" && row.fromKind !== "agent" || !(row.kind === void 0 || row.kind === "ask" || row.kind === "note") || !(row.senderName === null || typeof row.senderName === "string" && row.senderName.length <= 200) || typeof row.body !== "string" || row.body.length < 1 || row.body.length > 2e3 || !checkedTimestamp2(row.createdAt) || !checkedTimestamp2(row.queuedAt)) {
|
|
34287
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34288
|
+
}
|
|
34289
|
+
return {
|
|
34290
|
+
signalId: row.signalId.toLowerCase(),
|
|
34291
|
+
workspaceId: row.workspaceId.toLowerCase(),
|
|
34292
|
+
principalId: row.principalId.toLowerCase(),
|
|
34293
|
+
fromId: row.fromId.toLowerCase(),
|
|
34294
|
+
fromKind: row.fromKind,
|
|
34295
|
+
...row.kind === "ask" || row.kind === "note" ? { kind: row.kind } : {},
|
|
34296
|
+
senderName: row.senderName,
|
|
34297
|
+
body: row.body,
|
|
34298
|
+
createdAt: row.createdAt,
|
|
34299
|
+
queuedAt: row.queuedAt
|
|
34300
|
+
};
|
|
34301
|
+
}
|
|
34302
|
+
function parseFile(raw) {
|
|
34303
|
+
let value;
|
|
34304
|
+
try {
|
|
34305
|
+
value = JSON.parse(raw);
|
|
34306
|
+
} catch {
|
|
34307
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34308
|
+
}
|
|
34309
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34310
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34311
|
+
}
|
|
34312
|
+
const row = value;
|
|
34313
|
+
if (Object.keys(row).some(
|
|
34314
|
+
(key2) => key2 !== "version" && key2 !== "entries" && key2 !== "droppedCount"
|
|
34315
|
+
) || row.version !== 1 || !Array.isArray(row.entries) || row.entries.length > LISTENER_MAIN_QUEUE_MAX || !(row.droppedCount === void 0 || typeof row.droppedCount === "number" && Number.isSafeInteger(row.droppedCount) && row.droppedCount >= 0)) {
|
|
34316
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34317
|
+
}
|
|
34318
|
+
const entries = row.entries.map(parseEntry);
|
|
34319
|
+
if (new Set(entries.map((entry) => entry.signalId)).size !== entries.length) {
|
|
34320
|
+
throw new Error("stored pending-for-main queue repeats a signal");
|
|
34321
|
+
}
|
|
34322
|
+
return { version: 1, entries, droppedCount: row.droppedCount ?? 0 };
|
|
34323
|
+
}
|
|
34324
|
+
var FilePendingMainQueue = class {
|
|
34325
|
+
path;
|
|
34326
|
+
directory;
|
|
34327
|
+
constructor(instanceDirectory) {
|
|
34328
|
+
if (!(0, import_node_path14.isAbsolute)(instanceDirectory)) {
|
|
34329
|
+
throw new Error("pending-for-main directory must be absolute");
|
|
34330
|
+
}
|
|
34331
|
+
this.directory = instanceDirectory;
|
|
34332
|
+
this.path = (0, import_node_path14.join)(instanceDirectory, QUEUE_FILE);
|
|
34333
|
+
}
|
|
34334
|
+
async readUnlocked() {
|
|
34335
|
+
const raw = await readSecureJsonFile(this.path, MAX_QUEUE_BYTES);
|
|
34336
|
+
return raw === null ? { version: 1, entries: [], droppedCount: 0 } : parseFile(raw);
|
|
34337
|
+
}
|
|
34338
|
+
async writeUnlocked(file) {
|
|
34339
|
+
const canonical = parseFile(JSON.stringify(file));
|
|
34340
|
+
await writeSecureJsonFile(this.path, JSON.stringify(canonical));
|
|
34341
|
+
}
|
|
34342
|
+
async read() {
|
|
34343
|
+
return [...(await this.readUnlocked()).entries];
|
|
34344
|
+
}
|
|
34345
|
+
async count() {
|
|
34346
|
+
return (await this.readUnlocked()).entries.length;
|
|
34347
|
+
}
|
|
34348
|
+
async stats() {
|
|
34349
|
+
const file = await this.readUnlocked();
|
|
34350
|
+
return { count: file.entries.length, droppedCount: file.droppedCount };
|
|
34351
|
+
}
|
|
34352
|
+
async enqueue(entry) {
|
|
34353
|
+
const checked = parseEntry(entry);
|
|
34354
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
34355
|
+
const file = await this.readUnlocked();
|
|
34356
|
+
if (file.entries.some((item) => item.signalId === checked.signalId)) {
|
|
34357
|
+
return {
|
|
34358
|
+
count: file.entries.length,
|
|
34359
|
+
added: false,
|
|
34360
|
+
droppedOldest: false,
|
|
34361
|
+
droppedCount: file.droppedCount
|
|
34362
|
+
};
|
|
34363
|
+
}
|
|
34364
|
+
file.entries.push(checked);
|
|
34365
|
+
const droppedOldest = file.entries.length > LISTENER_MAIN_QUEUE_MAX;
|
|
34366
|
+
if (droppedOldest) {
|
|
34367
|
+
file.entries.shift();
|
|
34368
|
+
file.droppedCount += 1;
|
|
34369
|
+
}
|
|
34370
|
+
await this.writeUnlocked(file);
|
|
34371
|
+
return {
|
|
34372
|
+
count: file.entries.length,
|
|
34373
|
+
added: true,
|
|
34374
|
+
droppedOldest,
|
|
34375
|
+
droppedCount: file.droppedCount
|
|
34376
|
+
};
|
|
34377
|
+
});
|
|
34378
|
+
}
|
|
34379
|
+
async remove(signalIds, lockTimeoutMs) {
|
|
34380
|
+
if (signalIds.size === 0) return await this.count();
|
|
34381
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
34382
|
+
const file = await this.readUnlocked();
|
|
34383
|
+
const entries = file.entries.filter((entry) => !signalIds.has(entry.signalId));
|
|
34384
|
+
if (entries.length !== file.entries.length) {
|
|
34385
|
+
await this.writeUnlocked({
|
|
34386
|
+
version: 1,
|
|
34387
|
+
entries,
|
|
34388
|
+
droppedCount: file.droppedCount
|
|
34389
|
+
});
|
|
34390
|
+
}
|
|
34391
|
+
return entries.length;
|
|
34392
|
+
}, lockTimeoutMs === void 0 ? {} : { timeoutMs: lockTimeoutMs });
|
|
34393
|
+
}
|
|
34394
|
+
};
|
|
34395
|
+
function pendingMainEntry(signal, principalId, provenance, now) {
|
|
34396
|
+
if (signal.kind !== "ask") {
|
|
34397
|
+
throw new Error("only directed asks can enter the pending-for-main queue");
|
|
34398
|
+
}
|
|
34399
|
+
return parseEntry({
|
|
34400
|
+
signalId: signal.id,
|
|
34401
|
+
workspaceId: signal.workspace_id,
|
|
34402
|
+
principalId,
|
|
34403
|
+
fromId: signal.from,
|
|
34404
|
+
fromKind: signal.from_kind,
|
|
34405
|
+
senderName: provenance.senderName,
|
|
34406
|
+
body: signal.body,
|
|
34407
|
+
createdAt: signal.created_at,
|
|
34408
|
+
queuedAt: new Date(now).toISOString()
|
|
34409
|
+
});
|
|
34410
|
+
}
|
|
34411
|
+
|
|
34210
34412
|
// src/listener/runtime.ts
|
|
34211
34413
|
var LISTENER_PAGE_LIMIT = 100;
|
|
34212
34414
|
var LISTENER_IDLE_POLL_MS = 2e3;
|
|
@@ -34217,7 +34419,7 @@ var LISTENER_REPLY_ONLY_MINIMUM_MS = SIGNAL_REQUEST_TIMEOUT_MS + LISTENER_ACK_ON
|
|
|
34217
34419
|
var LISTENER_PROMPT_START_MINIMUM_MS = SIGNAL_READ_TIMEOUT_MS + ACP_DEFAULT_REQUEST_TIMEOUT_MS + LISTENER_REPLY_ONLY_MINIMUM_MS;
|
|
34218
34420
|
var LISTENER_DELIVERY_RETRY_INITIAL_MS = 500;
|
|
34219
34421
|
var LISTENER_DELIVERY_RETRY_MAX_MS = 3e4;
|
|
34220
|
-
var
|
|
34422
|
+
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;
|
|
34221
34423
|
var ListenerCapabilityError = class extends Error {
|
|
34222
34424
|
code;
|
|
34223
34425
|
constructor(code, message) {
|
|
@@ -34296,6 +34498,9 @@ function ackForTerminalEffect(record, now) {
|
|
|
34296
34498
|
if (record.state === "observed" && record.signalKind === "note") {
|
|
34297
34499
|
return { outcome: "observed", lastErrorCode: null };
|
|
34298
34500
|
}
|
|
34501
|
+
if (record.state === "routed_main" && record.signalKind === "ask") {
|
|
34502
|
+
return { outcome: "observed", lastErrorCode: null };
|
|
34503
|
+
}
|
|
34299
34504
|
if (record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now()) {
|
|
34300
34505
|
return { outcome: "expired", lastErrorCode: null };
|
|
34301
34506
|
}
|
|
@@ -34315,7 +34520,7 @@ function ackForTerminalEffect(record, now) {
|
|
|
34315
34520
|
return { outcome: "failed_terminal", lastErrorCode: "local_effect_failed" };
|
|
34316
34521
|
}
|
|
34317
34522
|
function isAckableTerminalEffect(record, now) {
|
|
34318
|
-
return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
|
|
34523
|
+
return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "routed_main" && record.signalKind === "ask" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
|
|
34319
34524
|
}
|
|
34320
34525
|
function effectPhaseBudget(record) {
|
|
34321
34526
|
if (record === null || record.state === "received" || record.state === "prompting") {
|
|
@@ -34478,6 +34683,8 @@ async function runListenerRuntime(options) {
|
|
|
34478
34683
|
const random = options.random ?? Math.random;
|
|
34479
34684
|
const pageLimit = options.pageLimit ?? LISTENER_PAGE_LIMIT;
|
|
34480
34685
|
const pollMs = options.pollMs ?? LISTENER_IDLE_POLL_MS;
|
|
34686
|
+
const routeMode = options.routeMode ?? "worker";
|
|
34687
|
+
const deferOverChars = options.deferOverChars ?? null;
|
|
34481
34688
|
const abort = options.signal;
|
|
34482
34689
|
const hasInstanceId = options.listenerInstanceId !== void 0;
|
|
34483
34690
|
const hasJournal = options.deliveryJournal !== void 0;
|
|
@@ -34487,7 +34694,7 @@ async function runListenerRuntime(options) {
|
|
|
34487
34694
|
new Error("listener instance id and delivery journal must be configured together")
|
|
34488
34695
|
);
|
|
34489
34696
|
}
|
|
34490
|
-
if (hasInstanceId && !
|
|
34697
|
+
if (hasInstanceId && !UUID_RE12.test(options.listenerInstanceId)) {
|
|
34491
34698
|
return await closeBeforeStart(
|
|
34492
34699
|
options.model,
|
|
34493
34700
|
new Error("listener instance id must be a UUID")
|
|
@@ -34499,6 +34706,14 @@ async function runListenerRuntime(options) {
|
|
|
34499
34706
|
new Error("an injected delivery client requires durable delivery configuration")
|
|
34500
34707
|
);
|
|
34501
34708
|
}
|
|
34709
|
+
try {
|
|
34710
|
+
decideListenerRoute(routeMode, deferOverChars, 0);
|
|
34711
|
+
if (routeMode !== "worker" && options.pendingMainQueue === void 0) {
|
|
34712
|
+
throw new Error("main listener routing requires a pending queue");
|
|
34713
|
+
}
|
|
34714
|
+
} catch (error) {
|
|
34715
|
+
return await closeBeforeStart(options.model, asError2(error));
|
|
34716
|
+
}
|
|
34502
34717
|
let initialJournal = null;
|
|
34503
34718
|
if (hasJournal) {
|
|
34504
34719
|
try {
|
|
@@ -34551,6 +34766,52 @@ async function runListenerRuntime(options) {
|
|
|
34551
34766
|
...options.resolveSenderProvenance === void 0 ? {} : { resolveSenderProvenance: options.resolveSenderProvenance },
|
|
34552
34767
|
isCredentialFailure: isCredentialLoss
|
|
34553
34768
|
});
|
|
34769
|
+
const routeAskToMain = async (signal) => {
|
|
34770
|
+
let provenance = {
|
|
34771
|
+
senderName: null,
|
|
34772
|
+
operatorId: null,
|
|
34773
|
+
operatorName: null
|
|
34774
|
+
};
|
|
34775
|
+
if (options.resolveSenderProvenance) {
|
|
34776
|
+
try {
|
|
34777
|
+
provenance = await options.resolveSenderProvenance(signal, {
|
|
34778
|
+
...abort ? { signal: abort } : {},
|
|
34779
|
+
deadlineMs: now() + SIGNAL_READ_TIMEOUT_MS
|
|
34780
|
+
});
|
|
34781
|
+
} catch {
|
|
34782
|
+
}
|
|
34783
|
+
}
|
|
34784
|
+
const queued = await options.pendingMainQueue.enqueue(
|
|
34785
|
+
pendingMainEntry(signal, options.principalId, provenance, now())
|
|
34786
|
+
);
|
|
34787
|
+
options.onEvent?.({
|
|
34788
|
+
type: "main_queue",
|
|
34789
|
+
signalId: signal.id,
|
|
34790
|
+
pendingCount: queued.count,
|
|
34791
|
+
droppedOldest: queued.droppedOldest,
|
|
34792
|
+
droppedCount: queued.droppedCount,
|
|
34793
|
+
ts: eventTime(now)
|
|
34794
|
+
});
|
|
34795
|
+
const existing = await options.store.read(signal.id);
|
|
34796
|
+
if (existing !== null) {
|
|
34797
|
+
if (!sameEffectSignal(existing, signal) || existing.state !== "routed_main") {
|
|
34798
|
+
throw new Error("stored listener effect does not match the main-routed ask");
|
|
34799
|
+
}
|
|
34800
|
+
return existing;
|
|
34801
|
+
}
|
|
34802
|
+
await options.store.write(newRoutedMainAskRecord({
|
|
34803
|
+
signalId: signal.id,
|
|
34804
|
+
body: signal.body,
|
|
34805
|
+
until: signal.until,
|
|
34806
|
+
senderOwnerRelation: signal.sender_owner_relation ?? "unknown",
|
|
34807
|
+
updatedAt: eventTime(now)
|
|
34808
|
+
}));
|
|
34809
|
+
const persisted = await options.store.read(signal.id);
|
|
34810
|
+
if (persisted === null || !sameEffectSignal(persisted, signal) || persisted.state !== "routed_main") {
|
|
34811
|
+
throw new Error("main-routed listener effect could not be verified");
|
|
34812
|
+
}
|
|
34813
|
+
return persisted;
|
|
34814
|
+
};
|
|
34554
34815
|
let malformedWarnings = 0;
|
|
34555
34816
|
const readPage = options.readPage ?? (async (input) => await readAgentSignalPage(
|
|
34556
34817
|
options.target,
|
|
@@ -34716,11 +34977,13 @@ async function runListenerRuntime(options) {
|
|
|
34716
34977
|
break;
|
|
34717
34978
|
}
|
|
34718
34979
|
if (!ready) {
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
|
|
34980
|
+
if (routeMode !== "main") {
|
|
34981
|
+
try {
|
|
34982
|
+
await options.model.start();
|
|
34983
|
+
} catch (error) {
|
|
34984
|
+
stop = { reason: "fatal", error: asError2(error) };
|
|
34985
|
+
break;
|
|
34986
|
+
}
|
|
34724
34987
|
}
|
|
34725
34988
|
ready = true;
|
|
34726
34989
|
options.onEvent?.({
|
|
@@ -35022,56 +35285,81 @@ async function runListenerRuntime(options) {
|
|
|
35022
35285
|
ts: eventTime(now)
|
|
35023
35286
|
});
|
|
35024
35287
|
} else if (signal.kind === "ask") {
|
|
35025
|
-
|
|
35026
|
-
|
|
35027
|
-
|
|
35028
|
-
|
|
35029
|
-
|
|
35030
|
-
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
35038
|
-
|
|
35039
|
-
|
|
35040
|
-
|
|
35041
|
-
stop = { reason: "cancelled" };
|
|
35042
|
-
break;
|
|
35043
|
-
}
|
|
35044
|
-
if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
|
|
35045
|
-
await journal.clearActive(eventTime(now));
|
|
35046
|
-
after = null;
|
|
35047
|
-
}
|
|
35048
|
-
break;
|
|
35049
|
-
}
|
|
35050
|
-
const processed = await engine.process(signal);
|
|
35051
|
-
const effect = "record" in processed ? processed.record : null;
|
|
35288
|
+
const decision = decideListenerRoute(
|
|
35289
|
+
routeMode,
|
|
35290
|
+
deferOverChars,
|
|
35291
|
+
signal.body.length
|
|
35292
|
+
);
|
|
35293
|
+
options.onEvent?.({
|
|
35294
|
+
type: "routing_decision",
|
|
35295
|
+
signalId: signal.id,
|
|
35296
|
+
routeMode,
|
|
35297
|
+
decision,
|
|
35298
|
+
threshold: deferOverChars,
|
|
35299
|
+
bodyLength: signal.body.length,
|
|
35300
|
+
ts: eventTime(now)
|
|
35301
|
+
});
|
|
35302
|
+
if (decision === "main") {
|
|
35303
|
+
terminal = await routeAskToMain(signal);
|
|
35052
35304
|
options.onEvent?.({
|
|
35053
35305
|
type: "effect",
|
|
35054
35306
|
signalId: signal.id,
|
|
35055
|
-
status:
|
|
35056
|
-
failureCode:
|
|
35307
|
+
status: "routed_main",
|
|
35308
|
+
failureCode: null,
|
|
35057
35309
|
ts: eventTime(now)
|
|
35058
35310
|
});
|
|
35059
|
-
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
|
|
35063
|
-
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
)
|
|
35068
|
-
|
|
35069
|
-
|
|
35311
|
+
} else {
|
|
35312
|
+
let processAttempt = 0;
|
|
35313
|
+
while (terminal === null) {
|
|
35314
|
+
const before = await options.store.read(signal.id);
|
|
35315
|
+
if (before !== null && !sameEffectSignal(before, signal)) {
|
|
35316
|
+
throw new Error("stored listener effect does not match the authoritative delivery");
|
|
35317
|
+
}
|
|
35318
|
+
const requiredBudget = effectPhaseBudget(before);
|
|
35319
|
+
if (leasedUntilMs <= now() + requiredBudget) {
|
|
35320
|
+
await sleep2(
|
|
35321
|
+
Math.max(
|
|
35322
|
+
0,
|
|
35323
|
+
leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS - now()
|
|
35324
|
+
),
|
|
35325
|
+
abort
|
|
35326
|
+
);
|
|
35327
|
+
if (abort?.aborted) {
|
|
35328
|
+
stop = { reason: "cancelled" };
|
|
35329
|
+
break;
|
|
35330
|
+
}
|
|
35331
|
+
if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
|
|
35332
|
+
await journal.clearActive(eventTime(now));
|
|
35333
|
+
after = null;
|
|
35334
|
+
}
|
|
35070
35335
|
break;
|
|
35071
35336
|
}
|
|
35072
|
-
|
|
35337
|
+
const processed = await engine.process(signal);
|
|
35338
|
+
const effect = "record" in processed ? processed.record : null;
|
|
35339
|
+
options.onEvent?.({
|
|
35340
|
+
type: "effect",
|
|
35341
|
+
signalId: signal.id,
|
|
35342
|
+
status: processed.status,
|
|
35343
|
+
failureCode: effect?.failureCode ?? null,
|
|
35344
|
+
ts: eventTime(now)
|
|
35345
|
+
});
|
|
35346
|
+
if (processed.status === "ignored") {
|
|
35347
|
+
throw new Error("claimed delivery was ignored by the listener engine");
|
|
35348
|
+
}
|
|
35349
|
+
if (processed.status === "retry_pending") {
|
|
35350
|
+
processAttempt += 1;
|
|
35351
|
+
await sleep2(
|
|
35352
|
+
deliveryRetryDelay(processAttempt, null, random),
|
|
35353
|
+
abort
|
|
35354
|
+
);
|
|
35355
|
+
if (abort?.aborted) {
|
|
35356
|
+
stop = { reason: "cancelled" };
|
|
35357
|
+
break;
|
|
35358
|
+
}
|
|
35359
|
+
continue;
|
|
35360
|
+
}
|
|
35361
|
+
terminal = processed.record;
|
|
35073
35362
|
}
|
|
35074
|
-
terminal = processed.record;
|
|
35075
35363
|
}
|
|
35076
35364
|
} else {
|
|
35077
35365
|
throw new Error("claimed delivery has an unsupported signal kind");
|
|
@@ -35144,6 +35432,31 @@ async function runListenerRuntime(options) {
|
|
|
35144
35432
|
let result;
|
|
35145
35433
|
try {
|
|
35146
35434
|
await readOrReplaceUnreadableEffect(options.store, signal, now);
|
|
35435
|
+
const decision = decideListenerRoute(
|
|
35436
|
+
routeMode,
|
|
35437
|
+
deferOverChars,
|
|
35438
|
+
signal.body.length
|
|
35439
|
+
);
|
|
35440
|
+
options.onEvent?.({
|
|
35441
|
+
type: "routing_decision",
|
|
35442
|
+
signalId: signal.id,
|
|
35443
|
+
routeMode,
|
|
35444
|
+
decision,
|
|
35445
|
+
threshold: deferOverChars,
|
|
35446
|
+
bodyLength: signal.body.length,
|
|
35447
|
+
ts: eventTime(now)
|
|
35448
|
+
});
|
|
35449
|
+
if (decision === "main") {
|
|
35450
|
+
const record2 = await routeAskToMain(signal);
|
|
35451
|
+
options.onEvent?.({
|
|
35452
|
+
type: "effect",
|
|
35453
|
+
signalId: signal.id,
|
|
35454
|
+
status: "routed_main",
|
|
35455
|
+
failureCode: null,
|
|
35456
|
+
ts: eventTime(now)
|
|
35457
|
+
});
|
|
35458
|
+
continue;
|
|
35459
|
+
}
|
|
35147
35460
|
result = await engine.process(signal);
|
|
35148
35461
|
} catch (error) {
|
|
35149
35462
|
if (abort?.aborted) {
|
|
@@ -35203,8 +35516,8 @@ async function runListenerRuntime(options) {
|
|
|
35203
35516
|
// src/listener/control.ts
|
|
35204
35517
|
var import_node_net = require("node:net");
|
|
35205
35518
|
var import_promises9 = require("node:fs/promises");
|
|
35206
|
-
var
|
|
35207
|
-
var
|
|
35519
|
+
var import_node_path15 = require("node:path");
|
|
35520
|
+
var UUID_RE13 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35208
35521
|
var MAX_STATUS_BYTES = 16 * 1024;
|
|
35209
35522
|
var MAX_CONTROL_BYTES = 8 * 1024;
|
|
35210
35523
|
var CONTROL_TIMEOUT_MS = 2e3;
|
|
@@ -35218,19 +35531,19 @@ var ListenerAlreadyRunningError = class extends Error {
|
|
|
35218
35531
|
};
|
|
35219
35532
|
function listenerPaths(options) {
|
|
35220
35533
|
const root = options.stateDirectory ?? defaultListenerStateDirectory();
|
|
35221
|
-
if (!(0,
|
|
35534
|
+
if (!(0, import_node_path15.isAbsolute)(root)) {
|
|
35222
35535
|
throw new Error("listener state directory must be absolute");
|
|
35223
35536
|
}
|
|
35224
35537
|
const key2 = listenerInstanceKey(options);
|
|
35225
|
-
const instanceDirectory = (0,
|
|
35538
|
+
const instanceDirectory = (0, import_node_path15.join)(root, key2);
|
|
35226
35539
|
const uid2 = typeof process.getuid === "function" ? process.getuid() : process.pid;
|
|
35227
|
-
const controlDirectory = process.platform === "win32" ? "" : (0,
|
|
35228
|
-
const socketPath = process.platform === "win32" ? `\\\\.\\pipe\\cswarm-${key2}` : (0,
|
|
35540
|
+
const controlDirectory = process.platform === "win32" ? "" : (0, import_node_path15.join)("/tmp", `cswarm-control-${uid2}`);
|
|
35541
|
+
const socketPath = process.platform === "win32" ? `\\\\.\\pipe\\cswarm-${key2}` : (0, import_node_path15.join)(controlDirectory, `${key2.slice(0, 32)}.sock`);
|
|
35229
35542
|
return {
|
|
35230
35543
|
key: key2,
|
|
35231
35544
|
instanceDirectory,
|
|
35232
|
-
statusPath: (0,
|
|
35233
|
-
logPath: (0,
|
|
35545
|
+
statusPath: (0, import_node_path15.join)(instanceDirectory, "status.json"),
|
|
35546
|
+
logPath: (0, import_node_path15.join)(instanceDirectory, "events.ndjson"),
|
|
35234
35547
|
socketPath
|
|
35235
35548
|
};
|
|
35236
35549
|
}
|
|
@@ -35258,7 +35571,11 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
35258
35571
|
"lastTerminalDeliveryFailureCount",
|
|
35259
35572
|
"lastTerminalDeliveryFailureAt",
|
|
35260
35573
|
"lastClaimAt",
|
|
35261
|
-
"lastAckAt"
|
|
35574
|
+
"lastAckAt",
|
|
35575
|
+
"routeMode",
|
|
35576
|
+
"deferOverChars",
|
|
35577
|
+
"pendingForMainCount",
|
|
35578
|
+
"droppedForMainCount"
|
|
35262
35579
|
]);
|
|
35263
35580
|
var STATUS_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
35264
35581
|
"leaseId",
|
|
@@ -35303,12 +35620,17 @@ function parseStatus(raw) {
|
|
|
35303
35620
|
throw new Error("stored listener status is malformed");
|
|
35304
35621
|
}
|
|
35305
35622
|
}
|
|
35306
|
-
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" &&
|
|
35623
|
+
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE13.test(candidate);
|
|
35307
35624
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
35308
35625
|
const nullableTimestamp = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
35309
|
-
if (row.version !== 1 || typeof row.instanceId !== "string" || !
|
|
35626
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE13.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE13.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE13.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path15.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
|
|
35310
35627
|
throw new Error("stored listener status is malformed");
|
|
35311
35628
|
}
|
|
35629
|
+
const routeMode = row.routeMode ?? "worker";
|
|
35630
|
+
const deferOverChars = row.deferOverChars ?? null;
|
|
35631
|
+
if (routeMode === "split" && deferOverChars === null || routeMode !== "split" && deferOverChars !== null) {
|
|
35632
|
+
throw new Error("stored listener status routing fields are malformed");
|
|
35633
|
+
}
|
|
35312
35634
|
return {
|
|
35313
35635
|
...row,
|
|
35314
35636
|
deliveryMode: row.deliveryMode ?? null,
|
|
@@ -35317,7 +35639,11 @@ function parseStatus(raw) {
|
|
|
35317
35639
|
lastTerminalDeliveryFailureAt: row.lastTerminalDeliveryFailureAt ?? null,
|
|
35318
35640
|
lastClaimAt: row.lastClaimAt ?? null,
|
|
35319
35641
|
lastAckAt: row.lastAckAt ?? null,
|
|
35320
|
-
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null
|
|
35642
|
+
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
|
|
35643
|
+
routeMode,
|
|
35644
|
+
deferOverChars,
|
|
35645
|
+
pendingForMainCount: row.pendingForMainCount ?? 0,
|
|
35646
|
+
droppedForMainCount: row.droppedForMainCount ?? 0
|
|
35321
35647
|
};
|
|
35322
35648
|
}
|
|
35323
35649
|
async function writeListenerStatus(paths, status) {
|
|
@@ -35359,7 +35685,13 @@ async function appendListenerEvent(paths, event) {
|
|
|
35359
35685
|
// bounded by the supervisor, and the prompt-turn budget behind a timeout.
|
|
35360
35686
|
// Local log only — this file never feeds a server payload.
|
|
35361
35687
|
"worker_stderr_tail",
|
|
35362
|
-
"turn_budget_ms"
|
|
35688
|
+
"turn_budget_ms",
|
|
35689
|
+
"route_mode",
|
|
35690
|
+
"route_decision",
|
|
35691
|
+
"defer_over_chars",
|
|
35692
|
+
"body_length",
|
|
35693
|
+
"pending_main_count",
|
|
35694
|
+
"dropped_count"
|
|
35363
35695
|
]);
|
|
35364
35696
|
const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
|
|
35365
35697
|
const deliveryOutcomes = /* @__PURE__ */ new Set([
|
|
@@ -35368,6 +35700,8 @@ async function appendListenerEvent(paths, event) {
|
|
|
35368
35700
|
"expired",
|
|
35369
35701
|
"failed_terminal"
|
|
35370
35702
|
]);
|
|
35703
|
+
const routeModes = /* @__PURE__ */ new Set(["worker", "main", "split"]);
|
|
35704
|
+
const routeDecisions = /* @__PURE__ */ new Set(["worker", "main"]);
|
|
35371
35705
|
for (const [key2, value] of Object.entries(event)) {
|
|
35372
35706
|
if (!allowed.has(key2)) {
|
|
35373
35707
|
throw new Error(`listener event field is not allowed: ${key2}`);
|
|
@@ -35381,6 +35715,18 @@ async function appendListenerEvent(paths, event) {
|
|
|
35381
35715
|
if (key2 === "outcome" && !(value === null || typeof value === "string" && deliveryOutcomes.has(value))) {
|
|
35382
35716
|
throw new Error("listener event outcome is not allowed");
|
|
35383
35717
|
}
|
|
35718
|
+
if (key2 === "route_mode" && !(typeof value === "string" && routeModes.has(value))) {
|
|
35719
|
+
throw new Error("listener event route mode is not allowed");
|
|
35720
|
+
}
|
|
35721
|
+
if (key2 === "route_decision" && !(typeof value === "string" && routeDecisions.has(value))) {
|
|
35722
|
+
throw new Error("listener event route decision is not allowed");
|
|
35723
|
+
}
|
|
35724
|
+
if (key2 === "defer_over_chars" && !(value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e4)) {
|
|
35725
|
+
throw new Error("listener event split threshold is not allowed");
|
|
35726
|
+
}
|
|
35727
|
+
if ((key2 === "body_length" || key2 === "pending_main_count" || key2 === "dropped_count") && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
|
|
35728
|
+
throw new Error("listener event main-route count is not allowed");
|
|
35729
|
+
}
|
|
35384
35730
|
if (key2 === "worker_stderr_tail" && !(typeof value === "string" && value.length > 0 && value.length <= 2048)) {
|
|
35385
35731
|
throw new Error("listener event stderr tail is not allowed");
|
|
35386
35732
|
}
|
|
@@ -35448,7 +35794,7 @@ function writeResponse(socket, response) {
|
|
|
35448
35794
|
}
|
|
35449
35795
|
async function startupLock(paths) {
|
|
35450
35796
|
await ensureSecureStateDirectory(paths.instanceDirectory);
|
|
35451
|
-
const lockPath = (0,
|
|
35797
|
+
const lockPath = (0, import_node_path15.join)(paths.instanceDirectory, "starting.lock");
|
|
35452
35798
|
const deadline = Date.now() + START_LOCK_WAIT_MS;
|
|
35453
35799
|
while (Date.now() < deadline) {
|
|
35454
35800
|
let handle;
|
|
@@ -35489,7 +35835,7 @@ async function startupLock(paths) {
|
|
|
35489
35835
|
async function prepareSocket(paths) {
|
|
35490
35836
|
if (process.platform !== "win32") {
|
|
35491
35837
|
const uid2 = typeof process.getuid === "function" ? process.getuid() : process.pid;
|
|
35492
|
-
const directory = (0,
|
|
35838
|
+
const directory = (0, import_node_path15.join)("/tmp", `cswarm-control-${uid2}`);
|
|
35493
35839
|
await ensureSecureStateDirectory(directory);
|
|
35494
35840
|
}
|
|
35495
35841
|
try {
|
|
@@ -35624,7 +35970,7 @@ async function queryListenerControl(paths, command2, timeoutMs = CONTROL_TIMEOUT
|
|
|
35624
35970
|
|
|
35625
35971
|
// src/listener/supervisor.ts
|
|
35626
35972
|
var import_node_crypto18 = require("node:crypto");
|
|
35627
|
-
var
|
|
35973
|
+
var UUID_RE14 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35628
35974
|
var LISTENER_RESTART_MAX_ATTEMPTS = 5;
|
|
35629
35975
|
var LISTENER_RESTART_INITIAL_MS = 1e3;
|
|
35630
35976
|
var LISTENER_RESTART_MAX_MS = 6e4;
|
|
@@ -35712,6 +36058,10 @@ async function runListenerSupervisor(options) {
|
|
|
35712
36058
|
lastTerminalDeliveryFailureAt: null,
|
|
35713
36059
|
lastClaimAt: null,
|
|
35714
36060
|
lastAckAt: null,
|
|
36061
|
+
routeMode: options.routeMode ?? "worker",
|
|
36062
|
+
deferOverChars: options.deferOverChars ?? null,
|
|
36063
|
+
pendingForMainCount: 0,
|
|
36064
|
+
droppedForMainCount: 0,
|
|
35715
36065
|
logPath: options.paths.logPath
|
|
35716
36066
|
};
|
|
35717
36067
|
let writes = Promise.resolve();
|
|
@@ -35747,7 +36097,7 @@ async function runListenerSupervisor(options) {
|
|
|
35747
36097
|
// before the socket can answer, before any status/event persistence.
|
|
35748
36098
|
initialize: prepare ? async () => {
|
|
35749
36099
|
const selected = await prepare(proposedInstanceId);
|
|
35750
|
-
if (!selected || typeof selected !== "object" || typeof selected.instanceId !== "string" || !
|
|
36100
|
+
if (!selected || typeof selected !== "object" || typeof selected.instanceId !== "string" || !UUID_RE14.test(selected.instanceId)) {
|
|
35751
36101
|
throw new Error("listener prepare returned an invalid instance id");
|
|
35752
36102
|
}
|
|
35753
36103
|
status = { ...status, instanceId: selected.instanceId };
|
|
@@ -35897,6 +36247,36 @@ async function runListenerSupervisor(options) {
|
|
|
35897
36247
|
});
|
|
35898
36248
|
return;
|
|
35899
36249
|
}
|
|
36250
|
+
if (event.type === "routing_decision") {
|
|
36251
|
+
log({
|
|
36252
|
+
ts: event.ts,
|
|
36253
|
+
event: "listener_routing_decision",
|
|
36254
|
+
signal_id: event.signalId,
|
|
36255
|
+
route_mode: event.routeMode,
|
|
36256
|
+
route_decision: event.decision,
|
|
36257
|
+
defer_over_chars: event.threshold,
|
|
36258
|
+
body_length: event.bodyLength
|
|
36259
|
+
});
|
|
36260
|
+
return;
|
|
36261
|
+
}
|
|
36262
|
+
if (event.type === "main_queue") {
|
|
36263
|
+
status = {
|
|
36264
|
+
...status,
|
|
36265
|
+
pendingForMainCount: event.pendingCount,
|
|
36266
|
+
droppedForMainCount: event.droppedCount,
|
|
36267
|
+
lastSignalId: event.signalId,
|
|
36268
|
+
updatedAt: event.ts
|
|
36269
|
+
};
|
|
36270
|
+
persist();
|
|
36271
|
+
log({
|
|
36272
|
+
ts: event.ts,
|
|
36273
|
+
event: event.droppedOldest ? "listener_main_queue_oldest_dropped" : "listener_main_queue",
|
|
36274
|
+
signal_id: event.signalId,
|
|
36275
|
+
pending_main_count: event.pendingCount,
|
|
36276
|
+
dropped_count: event.droppedOldest ? 1 : 0
|
|
36277
|
+
});
|
|
36278
|
+
return;
|
|
36279
|
+
}
|
|
35900
36280
|
const unknown = event;
|
|
35901
36281
|
log({
|
|
35902
36282
|
ts: typeof unknown.ts === "string" && Number.isFinite(Date.parse(unknown.ts)) ? unknown.ts : iso2(now),
|
|
@@ -36068,9 +36448,9 @@ async function waitForListenerReady(paths, options = {}) {
|
|
|
36068
36448
|
}
|
|
36069
36449
|
|
|
36070
36450
|
// src/listener/delivery-journal.ts
|
|
36071
|
-
var
|
|
36451
|
+
var import_node_path16 = require("node:path");
|
|
36072
36452
|
var import_node_util2 = require("node:util");
|
|
36073
|
-
var
|
|
36453
|
+
var UUID_RE15 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
36074
36454
|
var COMMAND_ID_RE3 = /^[A-Za-z0-9_-]{8,72}$/;
|
|
36075
36455
|
var SIGNAL_FINGERPRINT_RE = /^[0-9a-f]{64}$/;
|
|
36076
36456
|
var MAX_JOURNAL_BYTES = 8192;
|
|
@@ -36164,7 +36544,7 @@ var ALLOWED_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
36164
36544
|
"credential_unavailable"
|
|
36165
36545
|
]);
|
|
36166
36546
|
function claimCommandId(listenerInstanceId, claimOrdinal) {
|
|
36167
|
-
if (!
|
|
36547
|
+
if (!UUID_RE15.test(listenerInstanceId)) {
|
|
36168
36548
|
throw new Error("stored delivery journal is malformed");
|
|
36169
36549
|
}
|
|
36170
36550
|
if (!Number.isSafeInteger(claimOrdinal) || claimOrdinal < 0) {
|
|
@@ -36179,7 +36559,7 @@ function claimCommandId(listenerInstanceId, claimOrdinal) {
|
|
|
36179
36559
|
return id;
|
|
36180
36560
|
}
|
|
36181
36561
|
function ackCommandId(leaseId) {
|
|
36182
|
-
if (!
|
|
36562
|
+
if (!UUID_RE15.test(leaseId)) {
|
|
36183
36563
|
throw new Error("stored delivery journal is malformed");
|
|
36184
36564
|
}
|
|
36185
36565
|
const cleanLease = leaseId.toLowerCase().replace(/-/g, "");
|
|
@@ -36262,19 +36642,19 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
36262
36642
|
if (row.version !== 1) {
|
|
36263
36643
|
throw new Error("stored delivery journal is malformed");
|
|
36264
36644
|
}
|
|
36265
|
-
if (typeof row.workspaceId !== "string" || !
|
|
36645
|
+
if (typeof row.workspaceId !== "string" || !UUID_RE15.test(row.workspaceId) || row.workspaceId !== row.workspaceId.toLowerCase()) {
|
|
36266
36646
|
throw new Error("stored delivery journal is malformed");
|
|
36267
36647
|
}
|
|
36268
36648
|
if (expectedWorkspaceId && row.workspaceId !== expectedWorkspaceId.toLowerCase()) {
|
|
36269
36649
|
throw new Error("stored delivery journal is malformed");
|
|
36270
36650
|
}
|
|
36271
|
-
if (typeof row.principalId !== "string" || !
|
|
36651
|
+
if (typeof row.principalId !== "string" || !UUID_RE15.test(row.principalId) || row.principalId !== row.principalId.toLowerCase()) {
|
|
36272
36652
|
throw new Error("stored delivery journal is malformed");
|
|
36273
36653
|
}
|
|
36274
36654
|
if (expectedPrincipalId && row.principalId !== expectedPrincipalId.toLowerCase()) {
|
|
36275
36655
|
throw new Error("stored delivery journal is malformed");
|
|
36276
36656
|
}
|
|
36277
|
-
if (typeof row.listenerInstanceId !== "string" || !
|
|
36657
|
+
if (typeof row.listenerInstanceId !== "string" || !UUID_RE15.test(row.listenerInstanceId) || row.listenerInstanceId !== row.listenerInstanceId.toLowerCase()) {
|
|
36278
36658
|
throw new Error("stored delivery journal is malformed");
|
|
36279
36659
|
}
|
|
36280
36660
|
if (!Number.isSafeInteger(row.nextClaimOrdinal) || row.nextClaimOrdinal < 0) {
|
|
@@ -36338,10 +36718,10 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
36338
36718
|
if (active.claimLastAttemptAt === null) {
|
|
36339
36719
|
throw new Error("stored delivery journal is malformed");
|
|
36340
36720
|
}
|
|
36341
|
-
if (typeof active.signalId !== "string" || !
|
|
36721
|
+
if (typeof active.signalId !== "string" || !UUID_RE15.test(active.signalId) || active.signalId !== active.signalId.toLowerCase()) {
|
|
36342
36722
|
throw new Error("stored delivery journal is malformed");
|
|
36343
36723
|
}
|
|
36344
|
-
if (typeof active.leaseId !== "string" || !
|
|
36724
|
+
if (typeof active.leaseId !== "string" || !UUID_RE15.test(active.leaseId) || active.leaseId !== active.leaseId.toLowerCase()) {
|
|
36345
36725
|
throw new Error("stored delivery journal is malformed");
|
|
36346
36726
|
}
|
|
36347
36727
|
if (!isValidIsoTimestamp(active.leasedUntil) || Date.parse(active.leasedUntil) <= Date.parse(active.claimCreatedAt)) {
|
|
@@ -36413,7 +36793,7 @@ var FileListenerDeliveryJournal = class {
|
|
|
36413
36793
|
["profileId", "workspaceId", "principalId"],
|
|
36414
36794
|
"delivery journal configuration rejected"
|
|
36415
36795
|
);
|
|
36416
|
-
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !
|
|
36796
|
+
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !UUID_RE15.test(options.workspaceId) || typeof options.principalId !== "string" || !UUID_RE15.test(options.principalId)) {
|
|
36417
36797
|
throw new Error("delivery journal configuration rejected");
|
|
36418
36798
|
}
|
|
36419
36799
|
if (options.stateDirectory !== void 0) {
|
|
@@ -36428,11 +36808,11 @@ var FileListenerDeliveryJournal = class {
|
|
|
36428
36808
|
stateDirectory: options.stateDirectory
|
|
36429
36809
|
});
|
|
36430
36810
|
const root = this.options.stateDirectory ?? defaultListenerStateDirectory();
|
|
36431
|
-
if (!(0,
|
|
36811
|
+
if (!(0, import_node_path16.isAbsolute)(root)) {
|
|
36432
36812
|
throw new Error("delivery journal configuration rejected");
|
|
36433
36813
|
}
|
|
36434
|
-
this.instanceDirectory = (0,
|
|
36435
|
-
this.journalPath = (0,
|
|
36814
|
+
this.instanceDirectory = (0, import_node_path16.join)(root, listenerInstanceKey(this.options));
|
|
36815
|
+
this.journalPath = (0, import_node_path16.join)(this.instanceDirectory, "delivery-journal.json");
|
|
36436
36816
|
}
|
|
36437
36817
|
async readRecordUnlocked() {
|
|
36438
36818
|
let raw;
|
|
@@ -36534,7 +36914,7 @@ var FileListenerDeliveryJournal = class {
|
|
|
36534
36914
|
["signalId", "leaseId", "leasedUntil"],
|
|
36535
36915
|
"delivery journal mutation rejected"
|
|
36536
36916
|
);
|
|
36537
|
-
if (typeof input.signalId !== "string" || !
|
|
36917
|
+
if (typeof input.signalId !== "string" || !UUID_RE15.test(input.signalId) || typeof input.leaseId !== "string" || !UUID_RE15.test(input.leaseId) || !isValidIsoTimestamp(input.leasedUntil) || input.signalFingerprint !== void 0 && (typeof input.signalFingerprint !== "string" || !SIGNAL_FINGERPRINT_RE.test(input.signalFingerprint))) {
|
|
36538
36918
|
throw new Error("delivery journal mutation rejected");
|
|
36539
36919
|
}
|
|
36540
36920
|
const canonicalSignalId = input.signalId.toLowerCase();
|
|
@@ -36666,7 +37046,7 @@ async function openListenerDeliveryJournal(options) {
|
|
|
36666
37046
|
["profileId", "workspaceId", "principalId", "proposedListenerInstanceId"],
|
|
36667
37047
|
"delivery journal configuration rejected"
|
|
36668
37048
|
);
|
|
36669
|
-
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !
|
|
37049
|
+
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !UUID_RE15.test(options.workspaceId) || typeof options.principalId !== "string" || !UUID_RE15.test(options.principalId) || typeof options.proposedListenerInstanceId !== "string" || !UUID_RE15.test(options.proposedListenerInstanceId)) {
|
|
36670
37050
|
throw new Error("delivery journal configuration rejected");
|
|
36671
37051
|
}
|
|
36672
37052
|
if (options.stateDirectory !== void 0) {
|
|
@@ -36761,9 +37141,9 @@ async function openListenerDeliveryJournal(options) {
|
|
|
36761
37141
|
|
|
36762
37142
|
// src/listener/detach.ts
|
|
36763
37143
|
var import_node_child_process7 = require("node:child_process");
|
|
36764
|
-
var
|
|
37144
|
+
var import_node_path17 = require("node:path");
|
|
36765
37145
|
function isNativeAbsolutePath(value, platform = process.platform) {
|
|
36766
|
-
return platform === "win32" ?
|
|
37146
|
+
return platform === "win32" ? import_node_path17.win32.isAbsolute(value) : import_node_path17.posix.isAbsolute(value);
|
|
36767
37147
|
}
|
|
36768
37148
|
function listenerNodeExecArgv(values2) {
|
|
36769
37149
|
const safe = [];
|
|
@@ -36843,7 +37223,9 @@ function buildListenerChildArgs(spec) {
|
|
|
36843
37223
|
...provider === "codex" && codexExe ? ["--codex-executable", codexExe] : [],
|
|
36844
37224
|
...spec.model ? ["--model", spec.model] : [],
|
|
36845
37225
|
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : [],
|
|
36846
|
-
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : []
|
|
37226
|
+
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : [],
|
|
37227
|
+
...spec.route && spec.route !== "worker" ? ["--route", spec.route] : [],
|
|
37228
|
+
...spec.deferOver !== void 0 ? ["--defer-over", String(spec.deferOver)] : []
|
|
36847
37229
|
];
|
|
36848
37230
|
}
|
|
36849
37231
|
async function spawnDetachedListener(options) {
|
|
@@ -36876,6 +37258,507 @@ async function spawnDetachedListener(options) {
|
|
|
36876
37258
|
return child;
|
|
36877
37259
|
}
|
|
36878
37260
|
|
|
37261
|
+
// src/listener/hook.ts
|
|
37262
|
+
var import_promises10 = require("node:fs/promises");
|
|
37263
|
+
var import_node_path18 = require("node:path");
|
|
37264
|
+
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;
|
|
37265
|
+
var TOKEN_RE = /^swm_agt_[A-Za-z0-9_-]{43}$/;
|
|
37266
|
+
var INSTANCE_KEY_RE = /^[0-9a-f]{64}$/;
|
|
37267
|
+
var MAX_HOOK_CREDENTIAL_BYTES = 8 * 1024;
|
|
37268
|
+
var MAX_HOOK_SURFACE_BYTES = 128 * 1024;
|
|
37269
|
+
var MAX_GLOBAL_STATE_BYTES = 4 * 1024;
|
|
37270
|
+
var LISTENER_CREDENTIAL_FILE = "listener-credential.json";
|
|
37271
|
+
var RETIRED_HOOK_CREDENTIAL_FILE = "hook-credential.json";
|
|
37272
|
+
var HOOK_SURFACE_FILE = "hook-surface.json";
|
|
37273
|
+
var GLOBAL_STATE_FILE = "hook-check.json";
|
|
37274
|
+
var HOOK_SURFACE_LOCK = "hook-surface";
|
|
37275
|
+
var GLOBAL_STATE_LOCK = "hook-check";
|
|
37276
|
+
var HOOK_LOCK_TIMEOUT_MS = 250;
|
|
37277
|
+
var HOOK_CHECK_TIMEOUT_MS = 3e3;
|
|
37278
|
+
var HOOK_DEFAULT_COOLDOWN_SECONDS = 30;
|
|
37279
|
+
var HOOK_SURFACED_IDS_MAX = 1024;
|
|
37280
|
+
var HOOK_BODY_PREVIEW_CHARS = 240;
|
|
37281
|
+
function exactKeys2(row, keys) {
|
|
37282
|
+
const expected = new Set(keys);
|
|
37283
|
+
return Object.keys(row).length === expected.size && Object.keys(row).every((key2) => expected.has(key2));
|
|
37284
|
+
}
|
|
37285
|
+
function parseListenerCredential(raw) {
|
|
37286
|
+
let value;
|
|
37287
|
+
try {
|
|
37288
|
+
value = JSON.parse(raw);
|
|
37289
|
+
} catch {
|
|
37290
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37291
|
+
}
|
|
37292
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37293
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37294
|
+
}
|
|
37295
|
+
const row = value;
|
|
37296
|
+
if (!exactKeys2(row, [
|
|
37297
|
+
"version",
|
|
37298
|
+
"profileId",
|
|
37299
|
+
"targetUrl",
|
|
37300
|
+
"anonKey",
|
|
37301
|
+
"workspaceId",
|
|
37302
|
+
"principalId",
|
|
37303
|
+
"credential",
|
|
37304
|
+
"updatedAt"
|
|
37305
|
+
]) || row.version !== 1 || typeof row.profileId !== "string" || !/^[0-9a-f]{24}$/.test(row.profileId) || typeof row.targetUrl !== "string" || typeof row.anonKey !== "string" || row.anonKey.length < 1 || row.anonKey.length > 4096 || typeof row.workspaceId !== "string" || !UUID_RE16.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE16.test(row.principalId) || typeof row.credential !== "string" || !TOKEN_RE.test(row.credential) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt))) {
|
|
37306
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37307
|
+
}
|
|
37308
|
+
const target2 = cloudTarget(row.targetUrl, row.anonKey);
|
|
37309
|
+
if (target2.profileId !== row.profileId) {
|
|
37310
|
+
throw new Error("stored listener hook credential target does not match its profile");
|
|
37311
|
+
}
|
|
37312
|
+
return {
|
|
37313
|
+
version: 1,
|
|
37314
|
+
profileId: row.profileId,
|
|
37315
|
+
targetUrl: target2.url,
|
|
37316
|
+
anonKey: target2.anonKey,
|
|
37317
|
+
workspaceId: row.workspaceId.toLowerCase(),
|
|
37318
|
+
principalId: row.principalId.toLowerCase(),
|
|
37319
|
+
credential: row.credential,
|
|
37320
|
+
updatedAt: row.updatedAt
|
|
37321
|
+
};
|
|
37322
|
+
}
|
|
37323
|
+
async function writeListenerCredentialState(instanceDirectory, input) {
|
|
37324
|
+
if (!(0, import_node_path18.isAbsolute)(instanceDirectory)) {
|
|
37325
|
+
throw new Error("listener hook state directory must be absolute");
|
|
37326
|
+
}
|
|
37327
|
+
const record = parseListenerCredential(JSON.stringify({
|
|
37328
|
+
version: 1,
|
|
37329
|
+
profileId: input.target.profileId,
|
|
37330
|
+
targetUrl: input.target.url,
|
|
37331
|
+
anonKey: input.target.anonKey,
|
|
37332
|
+
workspaceId: input.workspaceId,
|
|
37333
|
+
principalId: input.principalId,
|
|
37334
|
+
credential: input.credential,
|
|
37335
|
+
updatedAt: new Date(input.now ?? Date.now()).toISOString()
|
|
37336
|
+
}));
|
|
37337
|
+
await writeSecureJsonFile(
|
|
37338
|
+
(0, import_node_path18.join)(instanceDirectory, LISTENER_CREDENTIAL_FILE),
|
|
37339
|
+
JSON.stringify(record)
|
|
37340
|
+
);
|
|
37341
|
+
await deleteSecureJsonFile(
|
|
37342
|
+
(0, import_node_path18.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
37343
|
+
).catch(() => void 0);
|
|
37344
|
+
}
|
|
37345
|
+
async function readListenerCredentialState(instanceDirectory) {
|
|
37346
|
+
const raw = await readSecureJsonFile(
|
|
37347
|
+
(0, import_node_path18.join)(instanceDirectory, LISTENER_CREDENTIAL_FILE),
|
|
37348
|
+
MAX_HOOK_CREDENTIAL_BYTES
|
|
37349
|
+
);
|
|
37350
|
+
return raw === null ? null : parseListenerCredential(raw);
|
|
37351
|
+
}
|
|
37352
|
+
function parseSurface(raw) {
|
|
37353
|
+
let value;
|
|
37354
|
+
try {
|
|
37355
|
+
value = JSON.parse(raw);
|
|
37356
|
+
} catch {
|
|
37357
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37358
|
+
}
|
|
37359
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37360
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37361
|
+
}
|
|
37362
|
+
const row = value;
|
|
37363
|
+
if (Object.keys(row).some(
|
|
37364
|
+
(key2) => key2 !== "version" && key2 !== "surfacedSignalIds" && key2 !== "reportedDroppedCount" && key2 !== "credentialFailureReported"
|
|
37365
|
+
) || row.version !== 1 || !Array.isArray(row.surfacedSignalIds) || row.surfacedSignalIds.length > HOOK_SURFACED_IDS_MAX || row.surfacedSignalIds.some((id) => typeof id !== "string" || !UUID_RE16.test(id)) || !(row.reportedDroppedCount === void 0 || typeof row.reportedDroppedCount === "number" && Number.isSafeInteger(row.reportedDroppedCount) && row.reportedDroppedCount >= 0) || !(row.credentialFailureReported === void 0 || typeof row.credentialFailureReported === "boolean")) {
|
|
37366
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37367
|
+
}
|
|
37368
|
+
const ids = row.surfacedSignalIds.map((id) => String(id).toLowerCase());
|
|
37369
|
+
if (new Set(ids).size !== ids.length) {
|
|
37370
|
+
throw new Error("stored listener hook surface state repeats a signal");
|
|
37371
|
+
}
|
|
37372
|
+
return {
|
|
37373
|
+
version: 1,
|
|
37374
|
+
surfacedSignalIds: ids,
|
|
37375
|
+
reportedDroppedCount: typeof row.reportedDroppedCount === "number" ? row.reportedDroppedCount : 0,
|
|
37376
|
+
credentialFailureReported: row.credentialFailureReported === true
|
|
37377
|
+
};
|
|
37378
|
+
}
|
|
37379
|
+
var FileHookSurfaceStore = class {
|
|
37380
|
+
constructor(instanceDirectory) {
|
|
37381
|
+
this.instanceDirectory = instanceDirectory;
|
|
37382
|
+
if (!(0, import_node_path18.isAbsolute)(instanceDirectory)) {
|
|
37383
|
+
throw new Error("listener hook surface directory must be absolute");
|
|
37384
|
+
}
|
|
37385
|
+
this.path = (0, import_node_path18.join)(instanceDirectory, HOOK_SURFACE_FILE);
|
|
37386
|
+
}
|
|
37387
|
+
instanceDirectory;
|
|
37388
|
+
path;
|
|
37389
|
+
async stage(items, droppedCount) {
|
|
37390
|
+
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
37391
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
37392
|
+
const state = raw === null ? {
|
|
37393
|
+
version: 1,
|
|
37394
|
+
surfacedSignalIds: [],
|
|
37395
|
+
reportedDroppedCount: 0,
|
|
37396
|
+
credentialFailureReported: false
|
|
37397
|
+
} : parseSurface(raw);
|
|
37398
|
+
const seen = new Set(state.surfacedSignalIds);
|
|
37399
|
+
const unseen = [];
|
|
37400
|
+
for (const item of items) {
|
|
37401
|
+
const signalId = item.signalId.toLowerCase();
|
|
37402
|
+
if (!UUID_RE16.test(signalId) || seen.has(signalId)) continue;
|
|
37403
|
+
seen.add(signalId);
|
|
37404
|
+
unseen.push(item);
|
|
37405
|
+
}
|
|
37406
|
+
return {
|
|
37407
|
+
unseen,
|
|
37408
|
+
droppedSinceLastCheck: droppedCount < state.reportedDroppedCount ? droppedCount : droppedCount - state.reportedDroppedCount,
|
|
37409
|
+
credentialFailureReported: state.credentialFailureReported
|
|
37410
|
+
};
|
|
37411
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37412
|
+
}
|
|
37413
|
+
async commit(options) {
|
|
37414
|
+
await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
37415
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
37416
|
+
const state = raw === null ? {
|
|
37417
|
+
version: 1,
|
|
37418
|
+
surfacedSignalIds: [],
|
|
37419
|
+
reportedDroppedCount: 0,
|
|
37420
|
+
credentialFailureReported: false
|
|
37421
|
+
} : parseSurface(raw);
|
|
37422
|
+
const seen = new Set(state.surfacedSignalIds);
|
|
37423
|
+
for (const signalId of options.signalIds ?? []) {
|
|
37424
|
+
const checked = signalId.toLowerCase();
|
|
37425
|
+
if (UUID_RE16.test(checked)) seen.add(checked);
|
|
37426
|
+
}
|
|
37427
|
+
await writeSecureJsonFile(
|
|
37428
|
+
this.path,
|
|
37429
|
+
JSON.stringify(parseSurface(JSON.stringify({
|
|
37430
|
+
version: 1,
|
|
37431
|
+
surfacedSignalIds: [...seen].slice(-HOOK_SURFACED_IDS_MAX),
|
|
37432
|
+
reportedDroppedCount: options.droppedCount ?? state.reportedDroppedCount,
|
|
37433
|
+
credentialFailureReported: options.credentialFailureReported ?? state.credentialFailureReported
|
|
37434
|
+
})))
|
|
37435
|
+
);
|
|
37436
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37437
|
+
}
|
|
37438
|
+
async claimUnseen(items) {
|
|
37439
|
+
const staged = await this.stage(items, 0);
|
|
37440
|
+
if (staged.unseen.length > 0) {
|
|
37441
|
+
await this.commit({ signalIds: staged.unseen.map((item) => item.signalId) });
|
|
37442
|
+
}
|
|
37443
|
+
return staged.unseen;
|
|
37444
|
+
}
|
|
37445
|
+
};
|
|
37446
|
+
function parseGlobalState(raw) {
|
|
37447
|
+
let value;
|
|
37448
|
+
try {
|
|
37449
|
+
value = JSON.parse(raw);
|
|
37450
|
+
} catch {
|
|
37451
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37452
|
+
}
|
|
37453
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37454
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37455
|
+
}
|
|
37456
|
+
const row = value;
|
|
37457
|
+
if (!exactKeys2(row, ["version", "lastCheckAt"]) || row.version !== 1 || typeof row.lastCheckAt !== "number" || !Number.isSafeInteger(row.lastCheckAt) || row.lastCheckAt < 0) {
|
|
37458
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37459
|
+
}
|
|
37460
|
+
return { version: 1, lastCheckAt: row.lastCheckAt };
|
|
37461
|
+
}
|
|
37462
|
+
async function reserveCheck(stateDirectory2, cooldownMs, now) {
|
|
37463
|
+
return await withFileLock(stateDirectory2, GLOBAL_STATE_LOCK, async () => {
|
|
37464
|
+
const path = (0, import_node_path18.join)(stateDirectory2, GLOBAL_STATE_FILE);
|
|
37465
|
+
const raw = await readSecureJsonFile(path, MAX_GLOBAL_STATE_BYTES);
|
|
37466
|
+
const previous = raw === null ? null : parseGlobalState(raw);
|
|
37467
|
+
if (previous !== null && now - previous.lastCheckAt < cooldownMs) return false;
|
|
37468
|
+
await writeSecureJsonFile(path, JSON.stringify({ version: 1, lastCheckAt: now }));
|
|
37469
|
+
return true;
|
|
37470
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37471
|
+
}
|
|
37472
|
+
function processIsAlive(pid) {
|
|
37473
|
+
try {
|
|
37474
|
+
process.kill(pid, 0);
|
|
37475
|
+
return true;
|
|
37476
|
+
} catch (error) {
|
|
37477
|
+
return error.code === "EPERM";
|
|
37478
|
+
}
|
|
37479
|
+
}
|
|
37480
|
+
async function statusContext(stateDirectory2, key2, instanceDirectory) {
|
|
37481
|
+
const provisional = {
|
|
37482
|
+
key: key2,
|
|
37483
|
+
instanceDirectory,
|
|
37484
|
+
statusPath: (0, import_node_path18.join)(instanceDirectory, "status.json"),
|
|
37485
|
+
logPath: (0, import_node_path18.join)(instanceDirectory, "events.ndjson"),
|
|
37486
|
+
socketPath: ""
|
|
37487
|
+
};
|
|
37488
|
+
const status = await readListenerStatus(provisional).catch(() => null);
|
|
37489
|
+
if (status === null) return null;
|
|
37490
|
+
const paths = listenerPaths({
|
|
37491
|
+
profileId: status.profileId,
|
|
37492
|
+
workspaceId: status.workspaceId,
|
|
37493
|
+
principalId: status.principalId,
|
|
37494
|
+
stateDirectory: stateDirectory2
|
|
37495
|
+
});
|
|
37496
|
+
if (paths.key !== key2 || paths.instanceDirectory !== instanceDirectory) return null;
|
|
37497
|
+
return { paths, status };
|
|
37498
|
+
}
|
|
37499
|
+
async function listenerIsLive(context) {
|
|
37500
|
+
if (context.status.state === "stopped" || context.status.state === "failed" || !processIsAlive(context.status.pid)) {
|
|
37501
|
+
return false;
|
|
37502
|
+
}
|
|
37503
|
+
try {
|
|
37504
|
+
await queryListenerControl(context.paths, "status", 250);
|
|
37505
|
+
return true;
|
|
37506
|
+
} catch {
|
|
37507
|
+
return false;
|
|
37508
|
+
}
|
|
37509
|
+
}
|
|
37510
|
+
async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive) {
|
|
37511
|
+
let entries;
|
|
37512
|
+
try {
|
|
37513
|
+
entries = await (0, import_promises10.readdir)(stateDirectory2, { withFileTypes: true });
|
|
37514
|
+
} catch (error) {
|
|
37515
|
+
if (error.code === "ENOENT") return [];
|
|
37516
|
+
throw error;
|
|
37517
|
+
}
|
|
37518
|
+
const contexts = [];
|
|
37519
|
+
for (const entry of entries) {
|
|
37520
|
+
if (!entry.isDirectory() || !INSTANCE_KEY_RE.test(entry.name)) continue;
|
|
37521
|
+
const instanceDirectory = (0, import_node_path18.join)(stateDirectory2, entry.name);
|
|
37522
|
+
const storedStatus = await statusContext(
|
|
37523
|
+
stateDirectory2,
|
|
37524
|
+
entry.name,
|
|
37525
|
+
instanceDirectory
|
|
37526
|
+
);
|
|
37527
|
+
const statusIsLive = storedStatus === null ? null : await isListenerLive(storedStatus);
|
|
37528
|
+
if (statusIsLive === false) continue;
|
|
37529
|
+
await deleteSecureJsonFile(
|
|
37530
|
+
(0, import_node_path18.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
37531
|
+
).catch(() => void 0);
|
|
37532
|
+
try {
|
|
37533
|
+
const credential = await readListenerCredentialState(instanceDirectory);
|
|
37534
|
+
contexts.push({
|
|
37535
|
+
instanceDirectory,
|
|
37536
|
+
paths: storedStatus?.paths ?? null,
|
|
37537
|
+
status: storedStatus?.status ?? null,
|
|
37538
|
+
credential,
|
|
37539
|
+
credentialReadFailed: credential === null && storedStatus !== null
|
|
37540
|
+
});
|
|
37541
|
+
} catch {
|
|
37542
|
+
if (storedStatus === null) continue;
|
|
37543
|
+
contexts.push({
|
|
37544
|
+
instanceDirectory,
|
|
37545
|
+
paths: storedStatus.paths,
|
|
37546
|
+
status: storedStatus.status,
|
|
37547
|
+
credential: null,
|
|
37548
|
+
credentialReadFailed: true
|
|
37549
|
+
});
|
|
37550
|
+
}
|
|
37551
|
+
}
|
|
37552
|
+
return contexts;
|
|
37553
|
+
}
|
|
37554
|
+
function entryFromSignal(signal, principalId, directory, now) {
|
|
37555
|
+
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;
|
|
37556
|
+
return {
|
|
37557
|
+
signalId: signal.id,
|
|
37558
|
+
workspaceId: signal.workspace_id,
|
|
37559
|
+
principalId,
|
|
37560
|
+
fromId: signal.from,
|
|
37561
|
+
fromKind: signal.from_kind,
|
|
37562
|
+
...signal.kind === "ask" || signal.kind === "note" ? { kind: signal.kind } : {},
|
|
37563
|
+
senderName,
|
|
37564
|
+
body: signal.body,
|
|
37565
|
+
createdAt: signal.created_at,
|
|
37566
|
+
queuedAt: new Date(now).toISOString()
|
|
37567
|
+
};
|
|
37568
|
+
}
|
|
37569
|
+
function preview(value) {
|
|
37570
|
+
let text = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
37571
|
+
if (text.length > HOOK_BODY_PREVIEW_CHARS) {
|
|
37572
|
+
text = `${text.slice(0, HOOK_BODY_PREVIEW_CHARS - 1)}\u2026`;
|
|
37573
|
+
}
|
|
37574
|
+
return JSON.stringify(text);
|
|
37575
|
+
}
|
|
37576
|
+
function renderHookSignal(item) {
|
|
37577
|
+
const sender = item.senderName === null ? item.fromId : item.senderName;
|
|
37578
|
+
const senderKind = item.fromKind === "user" ? "teammate" : "agent";
|
|
37579
|
+
const intent = item.kind === "ask" ? "is asking you:" : item.kind === "note" ? "sent you a note:" : "sent you a message:";
|
|
37580
|
+
const replyLabel = item.kind === "note" ? "reply (optional):" : "reply:";
|
|
37581
|
+
return [
|
|
37582
|
+
`[CommonSwarm] ${senderKind} ${JSON.stringify(sender)} ${intent}`,
|
|
37583
|
+
preview(item.body),
|
|
37584
|
+
`${replyLabel} cswarm reply ${item.signalId} "<answer>" --workspace-id ${item.workspaceId}`
|
|
37585
|
+
].join("\n");
|
|
37586
|
+
}
|
|
37587
|
+
async function inboxItems(context, options) {
|
|
37588
|
+
const stored = context.credential;
|
|
37589
|
+
const target2 = cloudTarget(stored.targetUrl, stored.anonKey);
|
|
37590
|
+
const readOptions = {
|
|
37591
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
37592
|
+
signal: options.signal,
|
|
37593
|
+
deadlineMs: options.deadlineMs,
|
|
37594
|
+
now: options.now
|
|
37595
|
+
};
|
|
37596
|
+
const page = await readAgentSignalPage(
|
|
37597
|
+
target2,
|
|
37598
|
+
{ kind: "agent", token: stored.credential },
|
|
37599
|
+
{
|
|
37600
|
+
workspaceId: stored.workspaceId,
|
|
37601
|
+
inbox: true,
|
|
37602
|
+
ascending: false,
|
|
37603
|
+
limit: 100,
|
|
37604
|
+
includeStale: false
|
|
37605
|
+
},
|
|
37606
|
+
readOptions,
|
|
37607
|
+
{ tolerateMalformedRows: true, maxMalformedRows: 3 }
|
|
37608
|
+
);
|
|
37609
|
+
const directed = page.signals.filter(
|
|
37610
|
+
(signal) => (signal.kind === "ask" || signal.kind === "note") && signal.workspace_id === stored.workspaceId && signal.to_agent === stored.principalId
|
|
37611
|
+
);
|
|
37612
|
+
if (directed.length === 0) return [];
|
|
37613
|
+
let directory = null;
|
|
37614
|
+
try {
|
|
37615
|
+
directory = await readAgentSignalDirectory(
|
|
37616
|
+
target2,
|
|
37617
|
+
stored.credential,
|
|
37618
|
+
stored.workspaceId,
|
|
37619
|
+
readOptions
|
|
37620
|
+
);
|
|
37621
|
+
} catch {
|
|
37622
|
+
directory = null;
|
|
37623
|
+
}
|
|
37624
|
+
const candidates = directed.map((signal) => entryFromSignal(signal, stored.principalId, directory, options.now())).sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
37625
|
+
return candidates;
|
|
37626
|
+
}
|
|
37627
|
+
function renderDroppedAsks(count2) {
|
|
37628
|
+
return `${count2} routed asks were dropped from the overflow queue; check cswarm inbox. The signals remain in the inbox.`;
|
|
37629
|
+
}
|
|
37630
|
+
var CREDENTIAL_READ_WARNING = "CommonSwarm could not read the configured listener credential safely. The hook is not checking routed asks. Restart the listener with a fresh credential; any credential state file must be mode 0600. Then run cswarm listen status.";
|
|
37631
|
+
var CREDENTIAL_401_WARNING = "CommonSwarm could not authenticate a listener credential (HTTP 401). The hook is not checking routed asks. Restart the listener with a fresh credential, then run cswarm listen status.";
|
|
37632
|
+
async function checkListenerHooks(options) {
|
|
37633
|
+
try {
|
|
37634
|
+
const stateDirectory2 = options.stateDirectory ?? defaultListenerStateDirectory();
|
|
37635
|
+
if (!(0, import_node_path18.isAbsolute)(stateDirectory2)) return "";
|
|
37636
|
+
const now = options.now ?? Date.now;
|
|
37637
|
+
const cooldownSeconds = options.cooldownSeconds ?? HOOK_DEFAULT_COOLDOWN_SECONDS;
|
|
37638
|
+
if (!Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400) {
|
|
37639
|
+
return "";
|
|
37640
|
+
}
|
|
37641
|
+
const contexts = await discoverContexts(
|
|
37642
|
+
stateDirectory2,
|
|
37643
|
+
options.isListenerLive ?? listenerIsLive
|
|
37644
|
+
);
|
|
37645
|
+
if (contexts.length === 0) return "";
|
|
37646
|
+
const networkAllowed = await reserveCheck(
|
|
37647
|
+
stateDirectory2,
|
|
37648
|
+
cooldownSeconds * 1e3,
|
|
37649
|
+
now()
|
|
37650
|
+
);
|
|
37651
|
+
const checks = await Promise.all(contexts.map(async (context) => {
|
|
37652
|
+
const queue = new FilePendingMainQueue(context.instanceDirectory);
|
|
37653
|
+
const pending = await queue.read();
|
|
37654
|
+
const stats = await queue.stats();
|
|
37655
|
+
let network = [];
|
|
37656
|
+
let credentialFailure = context.credentialReadFailed ? "read" : null;
|
|
37657
|
+
let credentialHealthy = false;
|
|
37658
|
+
if (networkAllowed && !options.signal.aborted && context.credential !== null) {
|
|
37659
|
+
try {
|
|
37660
|
+
network = await inboxItems(context, {
|
|
37661
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
37662
|
+
signal: options.signal,
|
|
37663
|
+
deadlineMs: options.deadlineMs,
|
|
37664
|
+
now
|
|
37665
|
+
});
|
|
37666
|
+
credentialHealthy = true;
|
|
37667
|
+
} catch (error) {
|
|
37668
|
+
if (followHttpDetails(error)?.status === 401) credentialFailure = "401";
|
|
37669
|
+
}
|
|
37670
|
+
}
|
|
37671
|
+
return {
|
|
37672
|
+
context,
|
|
37673
|
+
queue,
|
|
37674
|
+
pending,
|
|
37675
|
+
droppedCount: stats.droppedCount,
|
|
37676
|
+
network,
|
|
37677
|
+
credentialFailure,
|
|
37678
|
+
credentialHealthy
|
|
37679
|
+
};
|
|
37680
|
+
}));
|
|
37681
|
+
const commits = [];
|
|
37682
|
+
const blocks = [];
|
|
37683
|
+
const emittedCredentialWarnings = /* @__PURE__ */ new Set();
|
|
37684
|
+
for (const check of checks) {
|
|
37685
|
+
const store2 = new FileHookSurfaceStore(check.context.instanceDirectory);
|
|
37686
|
+
const staged = await store2.stage(
|
|
37687
|
+
[...check.pending, ...check.network],
|
|
37688
|
+
check.droppedCount
|
|
37689
|
+
);
|
|
37690
|
+
blocks.push(...staged.unseen.map(renderHookSignal));
|
|
37691
|
+
const reportDrops = staged.droppedSinceLastCheck > 0;
|
|
37692
|
+
if (reportDrops) blocks.push(renderDroppedAsks(staged.droppedSinceLastCheck));
|
|
37693
|
+
const reportCredentialFailure = check.credentialFailure !== null && !staged.credentialFailureReported;
|
|
37694
|
+
if (reportCredentialFailure) {
|
|
37695
|
+
const warning = check.credentialFailure === "401" ? CREDENTIAL_401_WARNING : CREDENTIAL_READ_WARNING;
|
|
37696
|
+
if (!emittedCredentialWarnings.has(warning)) {
|
|
37697
|
+
emittedCredentialWarnings.add(warning);
|
|
37698
|
+
blocks.push(warning);
|
|
37699
|
+
}
|
|
37700
|
+
}
|
|
37701
|
+
const pendingSignalIds = new Set(check.pending.map((entry) => entry.signalId));
|
|
37702
|
+
commits.push({
|
|
37703
|
+
check,
|
|
37704
|
+
store: store2,
|
|
37705
|
+
signalIds: staged.unseen.map((item) => item.signalId),
|
|
37706
|
+
printedPendingSignalIds: staged.unseen.map((item) => item.signalId).filter((signalId) => pendingSignalIds.has(signalId)),
|
|
37707
|
+
reportDrops,
|
|
37708
|
+
reportCredentialFailure
|
|
37709
|
+
});
|
|
37710
|
+
}
|
|
37711
|
+
const output = blocks.join("\n\n");
|
|
37712
|
+
if (output.length > 0) await (options.write ?? (() => void 0))(output);
|
|
37713
|
+
for (const commit of commits) {
|
|
37714
|
+
await commit.store.commit({
|
|
37715
|
+
signalIds: commit.signalIds,
|
|
37716
|
+
...commit.reportDrops ? { droppedCount: commit.check.droppedCount } : {},
|
|
37717
|
+
...commit.reportCredentialFailure ? { credentialFailureReported: true } : commit.check.credentialHealthy ? { credentialFailureReported: false } : {}
|
|
37718
|
+
});
|
|
37719
|
+
const remainingCount = await commit.check.queue.remove(
|
|
37720
|
+
new Set(commit.printedPendingSignalIds),
|
|
37721
|
+
HOOK_LOCK_TIMEOUT_MS
|
|
37722
|
+
);
|
|
37723
|
+
if (commit.check.context.paths !== null && commit.check.context.status !== null) {
|
|
37724
|
+
const latest = await readListenerStatus(commit.check.context.paths).catch(() => null);
|
|
37725
|
+
if (latest !== null && latest.pendingForMainCount !== remainingCount) {
|
|
37726
|
+
await writeListenerStatus(commit.check.context.paths, {
|
|
37727
|
+
...latest,
|
|
37728
|
+
pendingForMainCount: remainingCount
|
|
37729
|
+
});
|
|
37730
|
+
}
|
|
37731
|
+
}
|
|
37732
|
+
}
|
|
37733
|
+
return output;
|
|
37734
|
+
} catch {
|
|
37735
|
+
return "";
|
|
37736
|
+
}
|
|
37737
|
+
}
|
|
37738
|
+
async function runListenerHookCheck(options = {}) {
|
|
37739
|
+
const controller = new AbortController();
|
|
37740
|
+
const deadlineMs = Date.now() + HOOK_CHECK_TIMEOUT_MS;
|
|
37741
|
+
let timer2;
|
|
37742
|
+
try {
|
|
37743
|
+
const checking = checkListenerHooks({
|
|
37744
|
+
...options,
|
|
37745
|
+
signal: controller.signal,
|
|
37746
|
+
deadlineMs
|
|
37747
|
+
});
|
|
37748
|
+
const timedOut = new Promise((resolve) => {
|
|
37749
|
+
timer2 = setTimeout(() => {
|
|
37750
|
+
controller.abort();
|
|
37751
|
+
resolve("");
|
|
37752
|
+
}, HOOK_CHECK_TIMEOUT_MS);
|
|
37753
|
+
});
|
|
37754
|
+
return await Promise.race([checking, timedOut]);
|
|
37755
|
+
} catch {
|
|
37756
|
+
return "";
|
|
37757
|
+
} finally {
|
|
37758
|
+
if (timer2 !== void 0) clearTimeout(timer2);
|
|
37759
|
+
}
|
|
37760
|
+
}
|
|
37761
|
+
|
|
36879
37762
|
// src/cli.ts
|
|
36880
37763
|
var import_meta = {};
|
|
36881
37764
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -36888,7 +37771,9 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36888
37771
|
"claude-executable",
|
|
36889
37772
|
"codex-executable",
|
|
36890
37773
|
"confirm",
|
|
37774
|
+
"cooldown",
|
|
36891
37775
|
"cwd",
|
|
37776
|
+
"defer-over",
|
|
36892
37777
|
"device-id",
|
|
36893
37778
|
"effort",
|
|
36894
37779
|
"email",
|
|
@@ -36920,6 +37805,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36920
37805
|
"principal-id",
|
|
36921
37806
|
"provider",
|
|
36922
37807
|
"reveal-anon-key",
|
|
37808
|
+
"route",
|
|
36923
37809
|
"run-id",
|
|
36924
37810
|
"since",
|
|
36925
37811
|
"site",
|
|
@@ -36934,7 +37820,8 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36934
37820
|
"url",
|
|
36935
37821
|
"version",
|
|
36936
37822
|
"wait",
|
|
36937
|
-
"workspace-id"
|
|
37823
|
+
"workspace-id",
|
|
37824
|
+
"write"
|
|
36938
37825
|
]);
|
|
36939
37826
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
36940
37827
|
"agent-token-stdin",
|
|
@@ -36952,9 +37839,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36952
37839
|
"local",
|
|
36953
37840
|
"ndjson",
|
|
36954
37841
|
"no-browser",
|
|
36955
|
-
"reveal-anon-key"
|
|
37842
|
+
"reveal-anon-key",
|
|
37843
|
+
"write"
|
|
36956
37844
|
]);
|
|
36957
|
-
var
|
|
37845
|
+
var UUID_RE17 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
36958
37846
|
var AGENT_CREDENTIAL_MESSAGE = "Agent credential minted. It is bound to this task and run so the agent's work stays scoped and attributable.";
|
|
36959
37847
|
var AGENT_CREDENTIAL_MESSAGE_D088 = "Agent credential minted. It is bound to this run, so the agent's work is attributable to it.";
|
|
36960
37848
|
var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
@@ -36962,8 +37850,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
36962
37850
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
36963
37851
|
];
|
|
36964
37852
|
function packageVersion() {
|
|
36965
|
-
if ("0.1.
|
|
36966
|
-
return "0.1.
|
|
37853
|
+
if ("0.1.26".length > 0) {
|
|
37854
|
+
return "0.1.26";
|
|
36967
37855
|
}
|
|
36968
37856
|
try {
|
|
36969
37857
|
const value = JSON.parse(
|
|
@@ -37091,9 +37979,12 @@ Usage:
|
|
|
37091
37979
|
cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37092
37980
|
cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37093
37981
|
cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37094
|
-
cswarm listen start --agent-token-stdin [--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>] [--foreground] [--json]
|
|
37982
|
+
cswarm listen start --agent-token-stdin [--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]
|
|
37095
37983
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
37096
37984
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
37985
|
+
cswarm hook check [--cooldown <seconds>]
|
|
37986
|
+
cswarm hook install claude [--write]
|
|
37987
|
+
cswarm hook uninstall claude --write
|
|
37097
37988
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
37098
37989
|
cswarm new --name "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
37099
37990
|
cswarm workspaces [--url <url> --anon-key <key>] [--json]
|
|
@@ -37129,6 +38020,10 @@ Credential selection for command/dogfood:
|
|
|
37129
38020
|
read and command, nothing persisted -- either form
|
|
37130
38021
|
feedback command only, nothing persisted -- either form
|
|
37131
38022
|
listen start persists durable state, rotates -- needs expires_at
|
|
38023
|
+
hook check reads the listener's owned 0600 credential state;
|
|
38024
|
+
never accepts or prints a credential
|
|
38025
|
+
hook install/uninstall
|
|
38026
|
+
edits only local Claude Code settings; no credential
|
|
37132
38027
|
token revoke names what it revokes -- needs token_id
|
|
37133
38028
|
workspace close is human-session-only; it never accepts an agent token
|
|
37134
38029
|
|
|
@@ -37153,6 +38048,15 @@ TTL); a turn that lands just before a rotation can be clamped to the ~5m
|
|
|
37153
38048
|
renewal lead, and if it times out there, durable delivery retries it on the
|
|
37154
38049
|
fresh credential.
|
|
37155
38050
|
|
|
38051
|
+
listen start --route worker|main|split chooses where directed asks go. worker
|
|
38052
|
+
is the unchanged default. main queues every ask for the interactive session.
|
|
38053
|
+
split queues asks whose body is longer than --defer-over <chars>; the bound is
|
|
38054
|
+
1..10000 and an equal-length ask stays on the worker path. Run cswarm hook check
|
|
38055
|
+
to surface queued asks. hook check has its own 3s ceiling, exits 0 on every
|
|
38056
|
+
outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
38057
|
+
hook install claude prints the UserPromptSubmit JSON by default and changes the
|
|
38058
|
+
project's .claude/settings.json only with --write; uninstall also requires --write.
|
|
38059
|
+
|
|
37156
38060
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
37157
38061
|
stored human login. Agent self-surrender of a token uses --agent-token-stdin and never takes the secret on argv. Invite-link accept signs in when needed, then accepts and
|
|
37158
38062
|
registers one principal. Invitation links, agent credentials, and capability links
|
|
@@ -37281,7 +38185,7 @@ function parsedAgentCredential(value) {
|
|
|
37281
38185
|
const withExpiry = [...requiredKeys, "expires_at"].sort();
|
|
37282
38186
|
const actualKeys = Object.keys(artifact).sort();
|
|
37283
38187
|
const shape = actualKeys.length === requiredKeys.length ? requiredKeys : withExpiry;
|
|
37284
|
-
if (actualKeys.length !== shape.length || !actualKeys.every((key2, index) => key2 === shape[index]) || !ACCEPTED_AGENT_CREDENTIAL_MESSAGES.includes(artifact.message) || artifact.status !== "accepted" || typeof artifact.principal_id !== "string" || !
|
|
38188
|
+
if (actualKeys.length !== shape.length || !actualKeys.every((key2, index) => key2 === shape[index]) || !ACCEPTED_AGENT_CREDENTIAL_MESSAGES.includes(artifact.message) || artifact.status !== "accepted" || typeof artifact.principal_id !== "string" || !UUID_RE17.test(artifact.principal_id) || typeof artifact.token_id !== "string" || !UUID_RE17.test(artifact.token_id) || typeof artifact.run_id !== "string" || !UUID_RE17.test(artifact.run_id) || typeof artifact.agent_token !== "string") {
|
|
37285
38189
|
throw new Error("agent credential JSON is malformed");
|
|
37286
38190
|
}
|
|
37287
38191
|
let expiresAt = null;
|
|
@@ -37362,7 +38266,7 @@ async function stdinInviteLink() {
|
|
|
37362
38266
|
return link;
|
|
37363
38267
|
}
|
|
37364
38268
|
async function confirmationLine(prompt) {
|
|
37365
|
-
const reader = (0,
|
|
38269
|
+
const reader = (0, import_promises12.createInterface)({
|
|
37366
38270
|
input: process.stdin,
|
|
37367
38271
|
output: process.stderr,
|
|
37368
38272
|
terminal: Boolean(process.stdin.isTTY)
|
|
@@ -37519,7 +38423,7 @@ async function runNew(args) {
|
|
|
37519
38423
|
project: {
|
|
37520
38424
|
workspace_id: created,
|
|
37521
38425
|
name,
|
|
37522
|
-
stream_id: typeof response.stream_id === "string" &&
|
|
38426
|
+
stream_id: typeof response.stream_id === "string" && UUID_RE17.test(response.stream_id) ? response.stream_id : null
|
|
37523
38427
|
}
|
|
37524
38428
|
});
|
|
37525
38429
|
return;
|
|
@@ -38293,7 +39197,7 @@ async function runLinkNew(args) {
|
|
|
38293
39197
|
2
|
|
38294
39198
|
);
|
|
38295
39199
|
const taskId = args.required("task-id");
|
|
38296
|
-
if (!
|
|
39200
|
+
if (!UUID_RE17.test(taskId)) {
|
|
38297
39201
|
throw new Error("--task-id must be the work item's UUID");
|
|
38298
39202
|
}
|
|
38299
39203
|
const site = capabilitySiteOrigin(
|
|
@@ -38353,7 +39257,7 @@ async function runLinkRevoke(args) {
|
|
|
38353
39257
|
2
|
|
38354
39258
|
);
|
|
38355
39259
|
const capabilityId = args.required("capability-id");
|
|
38356
|
-
if (!
|
|
39260
|
+
if (!UUID_RE17.test(capabilityId)) {
|
|
38357
39261
|
throw new Error(
|
|
38358
39262
|
"--capability-id must be the id printed when the link was created"
|
|
38359
39263
|
);
|
|
@@ -38568,6 +39472,33 @@ function listenerTurnBudgetMs(value) {
|
|
|
38568
39472
|
}
|
|
38569
39473
|
return milliseconds;
|
|
38570
39474
|
}
|
|
39475
|
+
function listenerRouteConfiguration(routeValue, deferOverValue) {
|
|
39476
|
+
const routeMode = routeValue ?? "worker";
|
|
39477
|
+
if (routeMode !== "worker" && routeMode !== "main" && routeMode !== "split") {
|
|
39478
|
+
throw new Error("--route must be worker, main, or split");
|
|
39479
|
+
}
|
|
39480
|
+
if (routeMode !== "split") {
|
|
39481
|
+
if (deferOverValue !== void 0) {
|
|
39482
|
+
throw new Error("--defer-over is only valid with --route split");
|
|
39483
|
+
}
|
|
39484
|
+
return { routeMode, deferOverChars: null };
|
|
39485
|
+
}
|
|
39486
|
+
if (deferOverValue === void 0) {
|
|
39487
|
+
throw new Error("--route split requires --defer-over <chars>");
|
|
39488
|
+
}
|
|
39489
|
+
if (!/^\d+$/.test(deferOverValue)) {
|
|
39490
|
+
throw new Error(
|
|
39491
|
+
`--defer-over must be an integer from ${LISTENER_DEFER_OVER_MIN} to ${LISTENER_DEFER_OVER_MAX}`
|
|
39492
|
+
);
|
|
39493
|
+
}
|
|
39494
|
+
const deferOverChars = Number(deferOverValue);
|
|
39495
|
+
if (!Number.isSafeInteger(deferOverChars) || deferOverChars < LISTENER_DEFER_OVER_MIN || deferOverChars > LISTENER_DEFER_OVER_MAX) {
|
|
39496
|
+
throw new Error(
|
|
39497
|
+
`--defer-over must be an integer from ${LISTENER_DEFER_OVER_MIN} to ${LISTENER_DEFER_OVER_MAX}`
|
|
39498
|
+
);
|
|
39499
|
+
}
|
|
39500
|
+
return { routeMode, deferOverChars };
|
|
39501
|
+
}
|
|
38571
39502
|
var TURN_BUDGET_CREDENTIAL_MARGIN_MS = 6e4;
|
|
38572
39503
|
function clampTurnBudgetToCredential(budgetMs, credentialExpiresAt, nowMs) {
|
|
38573
39504
|
if (credentialExpiresAt === null) return budgetMs;
|
|
@@ -38862,7 +39793,7 @@ async function runReply(args) {
|
|
|
38862
39793
|
"json"
|
|
38863
39794
|
], 3);
|
|
38864
39795
|
const signalId = args.positionals[1];
|
|
38865
|
-
if (signalId === void 0 || !
|
|
39796
|
+
if (signalId === void 0 || !UUID_RE17.test(signalId)) {
|
|
38866
39797
|
throw new Error("reply requires the signal UUID being answered");
|
|
38867
39798
|
}
|
|
38868
39799
|
const body = args.positionals[2];
|
|
@@ -39190,7 +40121,7 @@ async function runInboxFollowCommand(args) {
|
|
|
39190
40121
|
}
|
|
39191
40122
|
}
|
|
39192
40123
|
function listenerUuid(value, flag) {
|
|
39193
|
-
if (!value || !
|
|
40124
|
+
if (!value || !UUID_RE17.test(value)) {
|
|
39194
40125
|
throw new Error(`--${flag} must be a UUID`);
|
|
39195
40126
|
}
|
|
39196
40127
|
return value.toLowerCase();
|
|
@@ -39203,7 +40134,7 @@ function listenerPermissionMode(value) {
|
|
|
39203
40134
|
function listenerStateDirectory(args) {
|
|
39204
40135
|
const value = args.optional("state-dir");
|
|
39205
40136
|
if (value === void 0) return void 0;
|
|
39206
|
-
if (!(0,
|
|
40137
|
+
if (!(0, import_node_path19.isAbsolute)(value)) {
|
|
39207
40138
|
throw new Error("--state-dir must be an absolute path");
|
|
39208
40139
|
}
|
|
39209
40140
|
return value;
|
|
@@ -39363,18 +40294,25 @@ function listenerStatusJson(status, permissionMode) {
|
|
|
39363
40294
|
lastTerminalDeliveryFailureAt: status.lastTerminalDeliveryFailureAt ?? null,
|
|
39364
40295
|
lastClaimAt: status.lastClaimAt ?? null,
|
|
39365
40296
|
lastAckAt: status.lastAckAt ?? null,
|
|
40297
|
+
routeMode: status.routeMode ?? "worker",
|
|
40298
|
+
deferOverChars: status.deferOverChars ?? null,
|
|
40299
|
+
pendingForMainCount: status.pendingForMainCount ?? 0,
|
|
40300
|
+
droppedForMainCount: status.droppedForMainCount ?? 0,
|
|
39366
40301
|
...mode3 ? {
|
|
39367
40302
|
permission_mode: mode3,
|
|
39368
40303
|
/* "allowed once" alone overstates it: allowOnceOrDeny selects allow_once only when the
|
|
39369
40304
|
* host OFFERS that option, and denies otherwise. Both review arms flagged the
|
|
39370
40305
|
* unqualified form on 4844b4e7. */
|
|
39371
|
-
same_owner_delivery: mode3 === "allow" ? "worker session; tool requests allowed once each when the host offers allow_once, otherwise denied" : "worker session; tool requests denied",
|
|
39372
|
-
cross_owner_delivery: mode3 === "allow" ? "same worker session with sender provenance; tool requests allowed once each when the host offers allow_once, otherwise denied" : "same worker session with sender provenance; tool requests denied"
|
|
40306
|
+
same_owner_delivery: status.routeMode === "main" ? "interactive session; no ACP worker prompt" : mode3 === "allow" ? "worker session; tool requests allowed once each when the host offers allow_once, otherwise denied" : "worker session; tool requests denied",
|
|
40307
|
+
cross_owner_delivery: status.routeMode === "main" ? "interactive session; no ACP worker prompt" : mode3 === "allow" ? "same worker session with sender provenance; tool requests allowed once each when the host offers allow_once, otherwise denied" : "same worker session with sender provenance; tool requests denied"
|
|
39373
40308
|
} : {},
|
|
39374
40309
|
host_limits: listenerHostLimits(status.provider)
|
|
39375
40310
|
};
|
|
39376
40311
|
}
|
|
39377
40312
|
function renderListenerStatus(status) {
|
|
40313
|
+
const routeMode = status.routeMode ?? "worker";
|
|
40314
|
+
const pendingForMainCount = status.pendingForMainCount ?? 0;
|
|
40315
|
+
const droppedForMainCount = status.droppedForMainCount ?? 0;
|
|
39378
40316
|
const lines = [
|
|
39379
40317
|
`Listener ${status.state} for agent ${status.principalId}.`,
|
|
39380
40318
|
`Provider: ${status.provider}; process: ${status.pid}; started: ${status.startedAt}.`,
|
|
@@ -39401,6 +40339,23 @@ function renderListenerStatus(status) {
|
|
|
39401
40339
|
`Pending deliveries reported by the service: ${status.pendingDeliveryCount}.`
|
|
39402
40340
|
);
|
|
39403
40341
|
}
|
|
40342
|
+
lines.push(
|
|
40343
|
+
routeMode === "split" ? `Ask route: split; bodies over ${status.deferOverChars} characters wait for this interactive session.` : routeMode === "main" ? "Ask route: main; directed asks wait for this interactive session." : "Ask route: worker (default)."
|
|
40344
|
+
);
|
|
40345
|
+
if (routeMode !== "worker") {
|
|
40346
|
+
lines.push(`Asks waiting for this session: ${pendingForMainCount}.`);
|
|
40347
|
+
lines.push(`Routed asks dropped from the overflow queue: ${droppedForMainCount}.`);
|
|
40348
|
+
if (droppedForMainCount > 0) {
|
|
40349
|
+
lines.push(
|
|
40350
|
+
"The signals remain in the inbox. Recover them with: cswarm inbox"
|
|
40351
|
+
);
|
|
40352
|
+
}
|
|
40353
|
+
if (pendingForMainCount > 0) {
|
|
40354
|
+
lines.push(
|
|
40355
|
+
`${pendingForMainCount} asks waiting for this session; they surface at your next prompt, or run cswarm hook check.`
|
|
40356
|
+
);
|
|
40357
|
+
}
|
|
40358
|
+
}
|
|
39404
40359
|
if (status.lastTerminalDeliveryFailureCount !== null && status.lastTerminalDeliveryFailureCount > 0) {
|
|
39405
40360
|
lines.push(
|
|
39406
40361
|
`The last claim reported ${status.lastTerminalDeliveryFailureCount} terminal delivery failures; they remain recorded, and the listener will keep receiving.`
|
|
@@ -39485,7 +40440,7 @@ function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEn
|
|
|
39485
40440
|
} catch (error) {
|
|
39486
40441
|
const code = error.code;
|
|
39487
40442
|
if (typeof code === "string") {
|
|
39488
|
-
if ((0,
|
|
40443
|
+
if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
|
|
39489
40444
|
const detail = error instanceof Error ? error.message : code;
|
|
39490
40445
|
throw new Error(
|
|
39491
40446
|
`could not use --claude-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/claude-agent-acp@0.64.2 if this path should be replaced`
|
|
@@ -39502,7 +40457,7 @@ function resolveDetachedCodexExecutable(executable = "codex-acp", pathEnv = proc
|
|
|
39502
40457
|
} catch (error) {
|
|
39503
40458
|
const code = error.code;
|
|
39504
40459
|
if (typeof code === "string") {
|
|
39505
|
-
if ((0,
|
|
40460
|
+
if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
|
|
39506
40461
|
const detail = error instanceof Error ? error.message : code;
|
|
39507
40462
|
throw new Error(
|
|
39508
40463
|
`could not use --codex-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/codex-acp@1.1.9 if this path should be replaced`
|
|
@@ -39547,11 +40502,32 @@ async function runConfiguredListener(options) {
|
|
|
39547
40502
|
principalId: options.principalId,
|
|
39548
40503
|
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
39549
40504
|
});
|
|
39550
|
-
const
|
|
40505
|
+
const liveCredentialSession = await agentSession(
|
|
39551
40506
|
options.cloud,
|
|
39552
40507
|
options.workspaceId,
|
|
39553
40508
|
options.agent
|
|
39554
40509
|
);
|
|
40510
|
+
let storedCredential = null;
|
|
40511
|
+
const credentialSession = {
|
|
40512
|
+
bearer: async () => {
|
|
40513
|
+
const credential = await liveCredentialSession.bearer();
|
|
40514
|
+
if (credential !== storedCredential) {
|
|
40515
|
+
await writeListenerCredentialState(paths.instanceDirectory, {
|
|
40516
|
+
target: options.cloud,
|
|
40517
|
+
workspaceId: options.workspaceId,
|
|
40518
|
+
principalId: options.principalId,
|
|
40519
|
+
credential
|
|
40520
|
+
}).then(() => {
|
|
40521
|
+
storedCredential = credential;
|
|
40522
|
+
});
|
|
40523
|
+
}
|
|
40524
|
+
const stored = await readListenerCredentialState(paths.instanceDirectory);
|
|
40525
|
+
if (stored === null || stored.credential !== credential) {
|
|
40526
|
+
throw new Error("listener credential state did not preserve the live credential");
|
|
40527
|
+
}
|
|
40528
|
+
return stored.credential;
|
|
40529
|
+
}
|
|
40530
|
+
};
|
|
39555
40531
|
const resolveSenderProvenance = async (signal, context) => {
|
|
39556
40532
|
const credential = await credentialSession.bearer();
|
|
39557
40533
|
const senderDirectory = await readAgentSignalDirectory(
|
|
@@ -39579,7 +40555,7 @@ async function runConfiguredListener(options) {
|
|
|
39579
40555
|
}
|
|
39580
40556
|
const applied = resolveTurnBudgetOrDefer(
|
|
39581
40557
|
turnBudgetMs,
|
|
39582
|
-
|
|
40558
|
+
liveCredentialSession.expiry,
|
|
39583
40559
|
Date.now(),
|
|
39584
40560
|
renewalFailed
|
|
39585
40561
|
);
|
|
@@ -39629,6 +40605,9 @@ async function runConfiguredListener(options) {
|
|
|
39629
40605
|
};
|
|
39630
40606
|
let selectedJournal;
|
|
39631
40607
|
let selectedListenerInstanceId;
|
|
40608
|
+
const routeMode = options.routeMode ?? "worker";
|
|
40609
|
+
const deferOverChars = options.deferOverChars ?? null;
|
|
40610
|
+
const pendingMainQueue = new FilePendingMainQueue(paths.instanceDirectory);
|
|
39632
40611
|
process.on("SIGINT", onProcessSignal);
|
|
39633
40612
|
process.on("SIGTERM", onProcessSignal);
|
|
39634
40613
|
try {
|
|
@@ -39639,6 +40618,8 @@ async function runConfiguredListener(options) {
|
|
|
39639
40618
|
principalId: options.principalId,
|
|
39640
40619
|
provider: options.provider,
|
|
39641
40620
|
permissionMode: options.permissionMode,
|
|
40621
|
+
routeMode,
|
|
40622
|
+
deferOverChars,
|
|
39642
40623
|
// The bound a timeout event reports: the last turn's clamped budget when
|
|
39643
40624
|
// one has run, else the configured cap.
|
|
39644
40625
|
getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
|
|
@@ -39677,7 +40658,10 @@ async function runConfiguredListener(options) {
|
|
|
39677
40658
|
declareModel: listenerModelLabel(options.provider),
|
|
39678
40659
|
listenerInstanceId,
|
|
39679
40660
|
deliveryJournal: selectedJournal,
|
|
39680
|
-
resolveSenderProvenance
|
|
40661
|
+
resolveSenderProvenance,
|
|
40662
|
+
routeMode,
|
|
40663
|
+
deferOverChars,
|
|
40664
|
+
pendingMainQueue
|
|
39681
40665
|
});
|
|
39682
40666
|
}
|
|
39683
40667
|
});
|
|
@@ -39702,6 +40686,8 @@ async function runListenStart(args) {
|
|
|
39702
40686
|
"codex-executable",
|
|
39703
40687
|
"state-dir",
|
|
39704
40688
|
"turn-budget",
|
|
40689
|
+
"route",
|
|
40690
|
+
"defer-over",
|
|
39705
40691
|
"foreground",
|
|
39706
40692
|
"json"
|
|
39707
40693
|
], 2);
|
|
@@ -39713,6 +40699,10 @@ async function runListenStart(args) {
|
|
|
39713
40699
|
const provider = listenerProvider(args);
|
|
39714
40700
|
validateListenerProviderFlags(args, provider);
|
|
39715
40701
|
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
40702
|
+
const routing = listenerRouteConfiguration(
|
|
40703
|
+
args.optional("route"),
|
|
40704
|
+
args.optional("defer-over")
|
|
40705
|
+
);
|
|
39716
40706
|
const cloud = await target(args);
|
|
39717
40707
|
const workspaceId2 = listenerUuid(
|
|
39718
40708
|
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
@@ -39722,7 +40712,7 @@ async function runListenStart(args) {
|
|
|
39722
40712
|
assertDurableListenerCredential(agent);
|
|
39723
40713
|
const principalId = agent.principalId;
|
|
39724
40714
|
const cwd = args.optional("cwd") ?? process.cwd();
|
|
39725
|
-
if (!(0,
|
|
40715
|
+
if (!(0, import_node_path19.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
|
|
39726
40716
|
const permissionMode = listenerPermissionMode(args.optional("permissions"));
|
|
39727
40717
|
const stateDirectory2 = listenerStateDirectory(args);
|
|
39728
40718
|
const paths = listenerPaths({
|
|
@@ -39748,6 +40738,7 @@ async function runListenStart(args) {
|
|
|
39748
40738
|
permissionMode,
|
|
39749
40739
|
provider,
|
|
39750
40740
|
turnBudgetMs,
|
|
40741
|
+
...routing,
|
|
39751
40742
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39752
40743
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39753
40744
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39758,7 +40749,7 @@ async function runListenStart(args) {
|
|
|
39758
40749
|
});
|
|
39759
40750
|
} else {
|
|
39760
40751
|
const entrypoint = process.argv[1];
|
|
39761
|
-
if (!entrypoint || !(0,
|
|
40752
|
+
if (!entrypoint || !(0, import_node_path19.isAbsolute)(entrypoint)) {
|
|
39762
40753
|
throw new Error("cannot locate the cswarm executable for detached start");
|
|
39763
40754
|
}
|
|
39764
40755
|
const artifact = JSON.stringify(agentCredentialArtifact({
|
|
@@ -39795,6 +40786,8 @@ async function runListenStart(args) {
|
|
|
39795
40786
|
permissionMode,
|
|
39796
40787
|
provider,
|
|
39797
40788
|
nodeExecArgv: process.execArgv,
|
|
40789
|
+
route: routing.routeMode,
|
|
40790
|
+
...routing.deferOverChars === null ? {} : { deferOver: routing.deferOverChars },
|
|
39798
40791
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {},
|
|
39799
40792
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39800
40793
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
@@ -39828,17 +40821,36 @@ async function runListenStart(args) {
|
|
|
39828
40821
|
listenerFailureMessage(status.lastErrorCode ?? "unknown_error", provider)
|
|
39829
40822
|
);
|
|
39830
40823
|
}
|
|
40824
|
+
if ((status.routeMode ?? "worker") !== "worker") {
|
|
40825
|
+
const recordedPending = status.pendingForMainCount ?? 0;
|
|
40826
|
+
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
40827
|
+
const queueStats = await new FilePendingMainQueue(
|
|
40828
|
+
paths.instanceDirectory
|
|
40829
|
+
).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
|
|
40830
|
+
status = {
|
|
40831
|
+
...status,
|
|
40832
|
+
pendingForMainCount: queueStats.count,
|
|
40833
|
+
droppedForMainCount: queueStats.droppedCount
|
|
40834
|
+
};
|
|
40835
|
+
}
|
|
39831
40836
|
if (args.has("json")) {
|
|
39832
40837
|
printJson(listenerStatusJson(status, permissionMode));
|
|
39833
40838
|
return;
|
|
39834
40839
|
}
|
|
39835
|
-
const
|
|
40840
|
+
const routingNote = routing.routeMode === "main" ? "Directed asks are queued for your interactive session and never prompt the ACP worker. Run cswarm hook check to surface them.\n" : routing.routeMode === "split" ? `Directed asks over ${routing.deferOverChars} characters are queued for your interactive session; shorter asks use the worker. Run cswarm hook check to surface queued asks.
|
|
40841
|
+
` : "";
|
|
40842
|
+
const workerAudience = routing.routeMode === "main" ? "Directed asks do not reach that worker." : routing.routeMode === "split" ? "Only asks at or below the split threshold reach that worker, with sender and operator provenance in the prompt." : "Every sender reaches that worker with sender and operator provenance in the prompt.";
|
|
40843
|
+
const hostNote = provider === "opencode" ? `The OpenCode worker uses one private auth/config home and your selected project cwd. ${workerAudience} Tool requests are approved one at a time by default, when the worker asks and the host offers a one-time approval; --permissions deny refuses them. The deny canary does not cover steady-state allow.
|
|
40844
|
+
` : provider === "claude" ? `The Claude worker uses your selected cwd and normal Claude Code keychain/OAuth state through claude-agent-acp 0.64.2. ${workerAudience}
|
|
40845
|
+
` : provider === "codex" ? `The Codex worker uses your selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9. CommonSwarm selects read-only mode before its deny canary. ${workerAudience}
|
|
40846
|
+
` : `The Grok worker uses your selected cwd and local Grok configuration, including user and cmux hooks. ${workerAudience}
|
|
40847
|
+
`;
|
|
39836
40848
|
process.stdout.write(
|
|
39837
40849
|
`${args.has("foreground") ? "Listener stopped." : "Listener is ready and will keep receiving after this command exits."}
|
|
39838
40850
|
${renderListenerStatus(status)}
|
|
39839
40851
|
Same-owner tool requests are ${permissionMode === "allow" ? "approved one at a time, when the worker asks and the host offers a one-time approval" : "denied. This worker can reply to messages but cannot do anything it must ask permission for; restart with --permissions allow if that is not what you want"}. The same permission mode applies to every sender relation.
|
|
39840
40852
|
The short credential rotates while this process remains alive and secure local state is available; a person reauthorises after the 30-day horizon.
|
|
39841
|
-
` + hostNote + `Use listen status/stop with --workspace-id ${workspaceId2} --principal-id ${principalId} and the same Cloud target.
|
|
40853
|
+
` + routingNote + hostNote + `Use listen status/stop with --workspace-id ${workspaceId2} --principal-id ${principalId} and the same Cloud target.
|
|
39842
40854
|
`
|
|
39843
40855
|
);
|
|
39844
40856
|
}
|
|
@@ -39857,18 +40869,24 @@ async function runListenSupervisor(args) {
|
|
|
39857
40869
|
"claude-executable",
|
|
39858
40870
|
"codex-executable",
|
|
39859
40871
|
"state-dir",
|
|
39860
|
-
"turn-budget"
|
|
40872
|
+
"turn-budget",
|
|
40873
|
+
"route",
|
|
40874
|
+
"defer-over"
|
|
39861
40875
|
], 1);
|
|
39862
40876
|
const provider = listenerProvider(args);
|
|
39863
40877
|
validateListenerProviderFlags(args, provider);
|
|
39864
40878
|
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
40879
|
+
const routing = listenerRouteConfiguration(
|
|
40880
|
+
args.optional("route"),
|
|
40881
|
+
args.optional("defer-over")
|
|
40882
|
+
);
|
|
39865
40883
|
const cloud = await target(args);
|
|
39866
40884
|
const workspaceId2 = listenerUuid(args.optional("workspace-id"), "workspace-id");
|
|
39867
40885
|
const principalId = listenerUuid(args.optional("principal-id"), "principal-id");
|
|
39868
40886
|
const agent = await stdinCredential();
|
|
39869
40887
|
assertDurableListenerCredential(agent, principalId);
|
|
39870
40888
|
const cwd = args.required("cwd");
|
|
39871
|
-
if (!(0,
|
|
40889
|
+
if (!(0, import_node_path19.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
|
|
39872
40890
|
const status = await runConfiguredListener({
|
|
39873
40891
|
cloud,
|
|
39874
40892
|
workspaceId: workspaceId2,
|
|
@@ -39878,6 +40896,7 @@ async function runListenSupervisor(args) {
|
|
|
39878
40896
|
permissionMode: listenerPermissionMode(args.optional("permissions")),
|
|
39879
40897
|
provider,
|
|
39880
40898
|
turnBudgetMs,
|
|
40899
|
+
...routing,
|
|
39881
40900
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39882
40901
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39883
40902
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39910,7 +40929,7 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
39910
40929
|
principalId,
|
|
39911
40930
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {}
|
|
39912
40931
|
});
|
|
39913
|
-
|
|
40932
|
+
let status = command2 === "stop" ? await stopListener(paths) : await effectiveListenerStatus(paths);
|
|
39914
40933
|
if (status === null) {
|
|
39915
40934
|
if (args.has("json")) {
|
|
39916
40935
|
printJson({ status: "not_found", workspace_id: workspaceId2, principal_id: principalId });
|
|
@@ -39919,6 +40938,18 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
39919
40938
|
}
|
|
39920
40939
|
return;
|
|
39921
40940
|
}
|
|
40941
|
+
if ((status.routeMode ?? "worker") !== "worker") {
|
|
40942
|
+
const recordedPending = status.pendingForMainCount ?? 0;
|
|
40943
|
+
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
40944
|
+
const queueStats = await new FilePendingMainQueue(
|
|
40945
|
+
paths.instanceDirectory
|
|
40946
|
+
).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
|
|
40947
|
+
status = {
|
|
40948
|
+
...status,
|
|
40949
|
+
pendingForMainCount: queueStats.count,
|
|
40950
|
+
droppedForMainCount: queueStats.droppedCount
|
|
40951
|
+
};
|
|
40952
|
+
}
|
|
39922
40953
|
if (args.has("json")) {
|
|
39923
40954
|
printJson(listenerStatusJson(status));
|
|
39924
40955
|
} else {
|
|
@@ -39938,6 +40969,148 @@ async function runListen(args) {
|
|
|
39938
40969
|
}
|
|
39939
40970
|
throw new UsageError("listen requires start, status, or stop");
|
|
39940
40971
|
}
|
|
40972
|
+
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
40973
|
+
function claudeUserPromptHookSnippet() {
|
|
40974
|
+
return {
|
|
40975
|
+
hooks: {
|
|
40976
|
+
UserPromptSubmit: [
|
|
40977
|
+
{
|
|
40978
|
+
hooks: [
|
|
40979
|
+
{
|
|
40980
|
+
type: "command",
|
|
40981
|
+
command: CLAUDE_HOOK_COMMAND
|
|
40982
|
+
}
|
|
40983
|
+
]
|
|
40984
|
+
}
|
|
40985
|
+
]
|
|
40986
|
+
}
|
|
40987
|
+
};
|
|
40988
|
+
}
|
|
40989
|
+
function projectClaudeSettingsPath() {
|
|
40990
|
+
return (0, import_node_path19.join)(process.cwd(), ".claude", "settings.json");
|
|
40991
|
+
}
|
|
40992
|
+
function readProjectSettings(path) {
|
|
40993
|
+
let raw;
|
|
40994
|
+
try {
|
|
40995
|
+
raw = (0, import_node_fs7.readFileSync)(path, "utf8");
|
|
40996
|
+
} catch (error) {
|
|
40997
|
+
if (error.code === "ENOENT") return {};
|
|
40998
|
+
throw error;
|
|
40999
|
+
}
|
|
41000
|
+
const value = JSON.parse(raw);
|
|
41001
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
41002
|
+
throw new Error("local .claude/settings.json must contain a JSON object");
|
|
41003
|
+
}
|
|
41004
|
+
return value;
|
|
41005
|
+
}
|
|
41006
|
+
function installClaudeHook(settings) {
|
|
41007
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? { ...settings.hooks } : {};
|
|
41008
|
+
const current = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
|
|
41009
|
+
const alreadyInstalled = current.some((group) => {
|
|
41010
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) return false;
|
|
41011
|
+
const commands = group.hooks;
|
|
41012
|
+
return Array.isArray(commands) && commands.some(
|
|
41013
|
+
(hook) => hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === CLAUDE_HOOK_COMMAND
|
|
41014
|
+
);
|
|
41015
|
+
});
|
|
41016
|
+
if (!alreadyInstalled) {
|
|
41017
|
+
const snippetHooks = claudeUserPromptHookSnippet().hooks.UserPromptSubmit;
|
|
41018
|
+
current.push(snippetHooks[0]);
|
|
41019
|
+
}
|
|
41020
|
+
hooks.UserPromptSubmit = current;
|
|
41021
|
+
return { ...settings, hooks };
|
|
41022
|
+
}
|
|
41023
|
+
function uninstallClaudeHook(settings) {
|
|
41024
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
|
|
41025
|
+
return settings;
|
|
41026
|
+
}
|
|
41027
|
+
const hooks = { ...settings.hooks };
|
|
41028
|
+
if (!Array.isArray(hooks.UserPromptSubmit)) return settings;
|
|
41029
|
+
const groups = [];
|
|
41030
|
+
for (const group of hooks.UserPromptSubmit) {
|
|
41031
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) {
|
|
41032
|
+
groups.push(group);
|
|
41033
|
+
continue;
|
|
41034
|
+
}
|
|
41035
|
+
const row = { ...group };
|
|
41036
|
+
if (!Array.isArray(row.hooks)) {
|
|
41037
|
+
groups.push(group);
|
|
41038
|
+
continue;
|
|
41039
|
+
}
|
|
41040
|
+
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === CLAUDE_HOOK_COMMAND));
|
|
41041
|
+
if (row.hooks.length > 0) groups.push(row);
|
|
41042
|
+
}
|
|
41043
|
+
if (groups.length > 0) hooks.UserPromptSubmit = groups;
|
|
41044
|
+
else delete hooks.UserPromptSubmit;
|
|
41045
|
+
if (Object.keys(hooks).length === 0) {
|
|
41046
|
+
const result = { ...settings };
|
|
41047
|
+
delete result.hooks;
|
|
41048
|
+
return result;
|
|
41049
|
+
}
|
|
41050
|
+
return { ...settings, hooks };
|
|
41051
|
+
}
|
|
41052
|
+
async function runHook(args) {
|
|
41053
|
+
const command2 = args.positionals[1];
|
|
41054
|
+
if (command2 === "check") {
|
|
41055
|
+
args.assertShape(["cooldown"], 2);
|
|
41056
|
+
const rawCooldown = args.optional("cooldown");
|
|
41057
|
+
const cooldownSeconds = rawCooldown === void 0 ? void 0 : Number(rawCooldown);
|
|
41058
|
+
if (cooldownSeconds !== void 0 && (!/^\d+$/.test(rawCooldown) || !Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400)) {
|
|
41059
|
+
return;
|
|
41060
|
+
}
|
|
41061
|
+
const hardExit = setTimeout(() => {
|
|
41062
|
+
process.exit(0);
|
|
41063
|
+
}, 3e3);
|
|
41064
|
+
hardExit.unref();
|
|
41065
|
+
try {
|
|
41066
|
+
await runListenerHookCheck({
|
|
41067
|
+
...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
|
|
41068
|
+
write: async (output) => {
|
|
41069
|
+
await new Promise((resolve, reject) => {
|
|
41070
|
+
process.stdout.write(`${output}
|
|
41071
|
+
`, (error) => {
|
|
41072
|
+
if (error) reject(error);
|
|
41073
|
+
else resolve();
|
|
41074
|
+
});
|
|
41075
|
+
});
|
|
41076
|
+
}
|
|
41077
|
+
});
|
|
41078
|
+
return;
|
|
41079
|
+
} finally {
|
|
41080
|
+
clearTimeout(hardExit);
|
|
41081
|
+
}
|
|
41082
|
+
}
|
|
41083
|
+
if (command2 !== "install" && command2 !== "uninstall") {
|
|
41084
|
+
throw new UsageError("hook requires check, install, or uninstall");
|
|
41085
|
+
}
|
|
41086
|
+
args.assertShape(["write"], 3);
|
|
41087
|
+
if (args.positionals[2] !== "claude") {
|
|
41088
|
+
throw new Error("hook install/uninstall currently supports claude");
|
|
41089
|
+
}
|
|
41090
|
+
if (command2 === "uninstall" && !args.has("write")) {
|
|
41091
|
+
throw new Error("hook uninstall claude requires --write");
|
|
41092
|
+
}
|
|
41093
|
+
const snippet = claudeUserPromptHookSnippet();
|
|
41094
|
+
if (!args.has("write")) {
|
|
41095
|
+
process.stdout.write(`${JSON.stringify(snippet, null, 2)}
|
|
41096
|
+
`);
|
|
41097
|
+
return;
|
|
41098
|
+
}
|
|
41099
|
+
const path = projectClaudeSettingsPath();
|
|
41100
|
+
const settings = readProjectSettings(path);
|
|
41101
|
+
const updated = command2 === "install" ? installClaudeHook(settings) : uninstallClaudeHook(settings);
|
|
41102
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path19.dirname)(path), { recursive: true });
|
|
41103
|
+
(0, import_node_fs7.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
|
|
41104
|
+
`, {
|
|
41105
|
+
encoding: "utf8",
|
|
41106
|
+
mode: 384
|
|
41107
|
+
});
|
|
41108
|
+
process.stdout.write(
|
|
41109
|
+
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${CLAUDE_HOOK_COMMAND}
|
|
41110
|
+
` : `Removed the CommonSwarm UserPromptSubmit hook from ${path}. Other settings were kept.
|
|
41111
|
+
`
|
|
41112
|
+
);
|
|
41113
|
+
}
|
|
39941
41114
|
function formatFileSize(value) {
|
|
39942
41115
|
const bytes = Number(value ?? 0);
|
|
39943
41116
|
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
@@ -39972,7 +41145,7 @@ async function fileRows(context) {
|
|
|
39972
41145
|
);
|
|
39973
41146
|
}
|
|
39974
41147
|
async function resolveFileSelector(context, selector) {
|
|
39975
|
-
if (
|
|
41148
|
+
if (UUID_RE17.test(selector)) return selector.toLowerCase();
|
|
39976
41149
|
const rows3 = await fileRows(context);
|
|
39977
41150
|
const match = rows3.find(
|
|
39978
41151
|
(row) => row.name.toLowerCase() === selector.toLowerCase()
|
|
@@ -39994,7 +41167,7 @@ async function runFilePut(args) {
|
|
|
39994
41167
|
} catch {
|
|
39995
41168
|
throw new Error(`could not read ${localPath}; check the path and permissions`);
|
|
39996
41169
|
}
|
|
39997
|
-
const name = args.optional("name") ?? (0,
|
|
41170
|
+
const name = args.optional("name") ?? (0, import_node_path19.basename)(localPath);
|
|
39998
41171
|
if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
|
|
39999
41172
|
throw new Error(
|
|
40000
41173
|
`this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
|
|
@@ -40096,7 +41269,7 @@ async function runFileGet(args) {
|
|
|
40096
41269
|
credential: context.selected.bearer
|
|
40097
41270
|
};
|
|
40098
41271
|
const grant = await fileDownloadUrl(send, { fileId, versionN });
|
|
40099
|
-
const destination = args.optional("out") ?? (0,
|
|
41272
|
+
const destination = args.optional("out") ?? (0, import_node_path19.basename)(grant.name);
|
|
40100
41273
|
const bytes = await getObject(context.cloud, grant.download_path);
|
|
40101
41274
|
writeDestination(destination, bytes, args.has("force"), import_node_fs7.writeFileSync);
|
|
40102
41275
|
if (args.has("json")) {
|
|
@@ -40312,10 +41485,10 @@ async function runSeed(args) {
|
|
|
40312
41485
|
throw new Error("DATABASE_URL is required for the fixture bridge");
|
|
40313
41486
|
}
|
|
40314
41487
|
const tokenOut = process.env.SEED_TOKEN_OUT;
|
|
40315
|
-
if (!tokenOut || !(0,
|
|
41488
|
+
if (!tokenOut || !(0, import_node_path19.isAbsolute)(tokenOut)) {
|
|
40316
41489
|
throw new Error("SEED_TOKEN_OUT must be an absolute path");
|
|
40317
41490
|
}
|
|
40318
|
-
const tokenFile = await (0,
|
|
41491
|
+
const tokenFile = await (0, import_promises11.open)(tokenOut, "wx", 384).catch((error) => {
|
|
40319
41492
|
if (error.code === "EEXIST") {
|
|
40320
41493
|
throw new Error("SEED_TOKEN_OUT already exists; refusing to overwrite it");
|
|
40321
41494
|
}
|
|
@@ -40354,7 +41527,7 @@ async function runSeed(args) {
|
|
|
40354
41527
|
tokenWritten = true;
|
|
40355
41528
|
}
|
|
40356
41529
|
await tokenFile.close();
|
|
40357
|
-
if (!tokenWritten) await (0,
|
|
41530
|
+
if (!tokenWritten) await (0, import_promises11.unlink)(tokenOut);
|
|
40358
41531
|
process.stdout.write(`${JSON.stringify({
|
|
40359
41532
|
userId: result.userId,
|
|
40360
41533
|
membershipRole: result.membershipRole,
|
|
@@ -40367,7 +41540,7 @@ async function runSeed(args) {
|
|
|
40367
41540
|
`);
|
|
40368
41541
|
} catch (error) {
|
|
40369
41542
|
await tokenFile.close().catch(() => void 0);
|
|
40370
|
-
if (!tokenWritten) await (0,
|
|
41543
|
+
if (!tokenWritten) await (0, import_promises11.unlink)(tokenOut).catch(() => void 0);
|
|
40371
41544
|
throw error;
|
|
40372
41545
|
}
|
|
40373
41546
|
}
|
|
@@ -40392,6 +41565,10 @@ async function main() {
|
|
|
40392
41565
|
await runListenSupervisor(args);
|
|
40393
41566
|
return;
|
|
40394
41567
|
}
|
|
41568
|
+
if (verb === "hook") {
|
|
41569
|
+
await runHook(args);
|
|
41570
|
+
return;
|
|
41571
|
+
}
|
|
40395
41572
|
if (verb === "listen") {
|
|
40396
41573
|
await runListen(args);
|
|
40397
41574
|
return;
|
|
@@ -40543,6 +41720,10 @@ function safeParagraph(message) {
|
|
|
40543
41720
|
return message.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ").slice(0, 2e3);
|
|
40544
41721
|
}
|
|
40545
41722
|
main().catch((error) => {
|
|
41723
|
+
if (process.argv[2] === "hook" && process.argv[3] === "check") {
|
|
41724
|
+
process.exitCode = 0;
|
|
41725
|
+
return;
|
|
41726
|
+
}
|
|
40546
41727
|
if (error instanceof RenewalReauthorisationRequired || error instanceof RenewalRevoked) {
|
|
40547
41728
|
process.stderr.write(`${safeParagraph(error.message)}
|
|
40548
41729
|
`);
|
|
@@ -40591,11 +41772,14 @@ ${usage()}
|
|
|
40591
41772
|
EXIT_RESTARTABLE,
|
|
40592
41773
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
40593
41774
|
clampTurnBudgetToCredential,
|
|
41775
|
+
claudeUserPromptHookSnippet,
|
|
40594
41776
|
describeAudience,
|
|
40595
41777
|
listenerFailureMessage,
|
|
40596
41778
|
listenerHostLimits,
|
|
40597
41779
|
listenerPermissionMode,
|
|
41780
|
+
listenerRouteConfiguration,
|
|
40598
41781
|
listenerStatusJson,
|
|
41782
|
+
renderListenerStatus,
|
|
40599
41783
|
renderRoster,
|
|
40600
41784
|
replyRefusalHint,
|
|
40601
41785
|
resolveDetachedClaudeExecutable,
|