commonswarm 0.1.44 → 0.1.45
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 +1055 -93
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -13503,6 +13503,7 @@ var require_main3 = __commonJS({
|
|
|
13503
13503
|
var cli_exports = {};
|
|
13504
13504
|
__export(cli_exports, {
|
|
13505
13505
|
EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
|
|
13506
|
+
ListenerUnattendedRefusedError: () => ListenerUnattendedRefusedError,
|
|
13506
13507
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
13507
13508
|
clampTurnBudgetToCredential: () => clampTurnBudgetToCredential,
|
|
13508
13509
|
claudeUserPromptHookSnippet: () => claudeUserPromptHookSnippet,
|
|
@@ -13521,12 +13522,12 @@ __export(cli_exports, {
|
|
|
13521
13522
|
});
|
|
13522
13523
|
module.exports = __toCommonJS(cli_exports);
|
|
13523
13524
|
var import_node_crypto20 = require("node:crypto");
|
|
13524
|
-
var
|
|
13525
|
+
var import_node_child_process9 = require("node:child_process");
|
|
13525
13526
|
var import_node_fs7 = require("node:fs");
|
|
13526
|
-
var
|
|
13527
|
+
var import_promises12 = require("node:fs/promises");
|
|
13527
13528
|
var import_node_os10 = require("node:os");
|
|
13528
13529
|
var import_node_path21 = require("node:path");
|
|
13529
|
-
var
|
|
13530
|
+
var import_promises13 = require("node:readline/promises");
|
|
13530
13531
|
|
|
13531
13532
|
// src/cloud/auth.ts
|
|
13532
13533
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -22900,9 +22901,9 @@ var FileCommandRefused = class extends Error {
|
|
|
22900
22901
|
};
|
|
22901
22902
|
var FileTransportError = class extends Error {
|
|
22902
22903
|
/**
|
|
22903
|
-
* True when
|
|
22904
|
-
*
|
|
22905
|
-
*
|
|
22904
|
+
* True when the request did not complete: no response arrived, or an
|
|
22905
|
+
* idempotent read's body stalled. Reads may retry; writes reuse the same ids
|
|
22906
|
+
* because their outcome is unknown. A received refusal is never retried.
|
|
22906
22907
|
*/
|
|
22907
22908
|
constructor(message, noResponse = false) {
|
|
22908
22909
|
super(message);
|
|
@@ -23050,7 +23051,7 @@ async function getObject(target2, downloadPath, fetcher = fetch, options = {}) {
|
|
|
23050
23051
|
options
|
|
23051
23052
|
));
|
|
23052
23053
|
} catch {
|
|
23053
|
-
throw new FileTransportError("the download
|
|
23054
|
+
throw new FileTransportError("the download did not complete", true);
|
|
23054
23055
|
}
|
|
23055
23056
|
if (!response.ok || body === null) {
|
|
23056
23057
|
throw new FileTransportError(
|
|
@@ -23131,7 +23132,7 @@ async function listFilesAsAgent(target2, credential, workspaceId2, fetcher = fet
|
|
|
23131
23132
|
options
|
|
23132
23133
|
));
|
|
23133
23134
|
} catch {
|
|
23134
|
-
throw new FileTransportError("file list
|
|
23135
|
+
throw new FileTransportError("the file list did not complete", true);
|
|
23135
23136
|
}
|
|
23136
23137
|
if (!response.ok) {
|
|
23137
23138
|
throw new FileCommandRefused(
|
|
@@ -23176,7 +23177,7 @@ async function listFilesAsHuman(target2, accessToken, workspaceId2, fetcher = fe
|
|
|
23176
23177
|
options
|
|
23177
23178
|
));
|
|
23178
23179
|
} catch {
|
|
23179
|
-
throw new FileTransportError("file list
|
|
23180
|
+
throw new FileTransportError("the file list did not complete", true);
|
|
23180
23181
|
}
|
|
23181
23182
|
if (!response.ok) {
|
|
23182
23183
|
throw new FileCommandRefused(
|
|
@@ -23366,8 +23367,7 @@ function assertOwnedByCurrentUser(uid2) {
|
|
|
23366
23367
|
throw new Error("credential path is not owned by the current user");
|
|
23367
23368
|
}
|
|
23368
23369
|
}
|
|
23369
|
-
async function
|
|
23370
|
-
let created = false;
|
|
23370
|
+
async function existingSecureDirectory(path) {
|
|
23371
23371
|
try {
|
|
23372
23372
|
const info = await (0, import_promises.lstat)(path);
|
|
23373
23373
|
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
@@ -23380,20 +23380,22 @@ async function secureDirectory(path) {
|
|
|
23380
23380
|
);
|
|
23381
23381
|
}
|
|
23382
23382
|
} catch (error) {
|
|
23383
|
-
if (error.code
|
|
23384
|
-
|
|
23385
|
-
await (0, import_promises.chmod)(path, 448);
|
|
23386
|
-
created = true;
|
|
23383
|
+
if (error.code === "ENOENT") return false;
|
|
23384
|
+
throw error;
|
|
23387
23385
|
}
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
}
|
|
23386
|
+
return true;
|
|
23387
|
+
}
|
|
23388
|
+
async function secureDirectory(path) {
|
|
23389
|
+
if (await existingSecureDirectory(path)) return;
|
|
23390
|
+
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
23391
|
+
await (0, import_promises.chmod)(path, 448);
|
|
23392
|
+
const info = await (0, import_promises.lstat)(path);
|
|
23393
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
23394
|
+
throw new Error(`credential directory is not a real directory: ${path}`);
|
|
23395
|
+
}
|
|
23396
|
+
assertOwnedByCurrentUser(info.uid);
|
|
23397
|
+
if (mode(info.mode) !== 448) {
|
|
23398
|
+
throw new Error(`credential directory could not be secured to mode 0700: ${path}`);
|
|
23397
23399
|
}
|
|
23398
23400
|
}
|
|
23399
23401
|
async function ensureSecureStateDirectory(path) {
|
|
@@ -23555,6 +23557,20 @@ async function readSecureJsonFile(path, maxBytes) {
|
|
|
23555
23557
|
throw error;
|
|
23556
23558
|
}
|
|
23557
23559
|
}
|
|
23560
|
+
async function readSecureJsonFileIfPresent(path, maxBytes) {
|
|
23561
|
+
if (!await existingSecureDirectory((0, import_node_path.dirname)(path))) return null;
|
|
23562
|
+
try {
|
|
23563
|
+
await secureCredentialFile(path);
|
|
23564
|
+
const raw = await (0, import_promises.readFile)(path, "utf8");
|
|
23565
|
+
if (Buffer.byteLength(raw, "utf8") > maxBytes) {
|
|
23566
|
+
throw new Error("stored record is larger than this store accepts");
|
|
23567
|
+
}
|
|
23568
|
+
return raw;
|
|
23569
|
+
} catch (error) {
|
|
23570
|
+
if (error.code === "ENOENT") return null;
|
|
23571
|
+
throw error;
|
|
23572
|
+
}
|
|
23573
|
+
}
|
|
23558
23574
|
async function deleteSecureJsonFile(path) {
|
|
23559
23575
|
await secureDirectory((0, import_node_path.dirname)(path));
|
|
23560
23576
|
try {
|
|
@@ -25744,7 +25760,7 @@ var src_default = Postgres;
|
|
|
25744
25760
|
function Postgres(a, b2) {
|
|
25745
25761
|
const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
|
|
25746
25762
|
let ending = false;
|
|
25747
|
-
const queries = queue_default(), connecting = queue_default(), reserved = queue_default(), closed = queue_default(), ended = queue_default(),
|
|
25763
|
+
const queries = queue_default(), connecting = queue_default(), reserved = queue_default(), closed = queue_default(), ended = queue_default(), open6 = queue_default(), busy = queue_default(), full = queue_default(), queues = { connecting, reserved, closed, ended, open: open6, busy, full };
|
|
25748
25764
|
const connections = [...Array(options.max)].map(() => connection_default(options, queues, { onopen, onend, onclose }));
|
|
25749
25765
|
const sql = Sql(handler);
|
|
25750
25766
|
Object.assign(sql, {
|
|
@@ -25857,7 +25873,7 @@ function Postgres(a, b2) {
|
|
|
25857
25873
|
}
|
|
25858
25874
|
async function reserve() {
|
|
25859
25875
|
const queue = queue_default();
|
|
25860
|
-
const c =
|
|
25876
|
+
const c = open6.length ? open6.shift() : await new Promise((resolve2, reject) => {
|
|
25861
25877
|
const query = { reserve: resolve2, reject };
|
|
25862
25878
|
queries.push(query);
|
|
25863
25879
|
closed.length && connect(closed.shift(), query);
|
|
@@ -25930,7 +25946,7 @@ function Postgres(a, b2) {
|
|
|
25930
25946
|
c.queue.remove(c);
|
|
25931
25947
|
queue.push(c);
|
|
25932
25948
|
c.queue = queue;
|
|
25933
|
-
queue ===
|
|
25949
|
+
queue === open6 ? c.idleTimer.start() : c.idleTimer.cancel();
|
|
25934
25950
|
return c;
|
|
25935
25951
|
}
|
|
25936
25952
|
function json(x) {
|
|
@@ -25944,8 +25960,8 @@ function Postgres(a, b2) {
|
|
|
25944
25960
|
function handler(query) {
|
|
25945
25961
|
if (ending)
|
|
25946
25962
|
return query.reject(Errors.connection("CONNECTION_ENDED", options, options));
|
|
25947
|
-
if (
|
|
25948
|
-
return go(
|
|
25963
|
+
if (open6.length)
|
|
25964
|
+
return go(open6.shift(), query);
|
|
25949
25965
|
if (closed.length)
|
|
25950
25966
|
return connect(closed.shift(), query);
|
|
25951
25967
|
busy.length ? go(busy.shift(), query) : queries.push(query);
|
|
@@ -25990,7 +26006,7 @@ function Postgres(a, b2) {
|
|
|
25990
26006
|
}
|
|
25991
26007
|
function onopen(c) {
|
|
25992
26008
|
if (queries.length === 0)
|
|
25993
|
-
return move(c,
|
|
26009
|
+
return move(c, open6);
|
|
25994
26010
|
let max = Math.ceil(queries.length / (connecting.length + 1)), ready = true;
|
|
25995
26011
|
while (ready && queries.length && max-- > 0) {
|
|
25996
26012
|
const query = queries.shift();
|
|
@@ -29948,6 +29964,16 @@ var CURSOR_MAX_BYTES = 4 * 1024;
|
|
|
29948
29964
|
var ARRIVAL_SNIPPET_MAX = 180;
|
|
29949
29965
|
var ARRIVAL_WATCH_POLL_MS = 25e3;
|
|
29950
29966
|
var ARRIVAL_RETRY_NOTICE_THRESHOLD_MS = 6e4;
|
|
29967
|
+
var EXIT_NOTIFY_ORPHANED = 74;
|
|
29968
|
+
var NotifyStdoutClosedError = class extends Error {
|
|
29969
|
+
name = "NotifyStdoutClosedError";
|
|
29970
|
+
code = "notify_stdout_closed";
|
|
29971
|
+
constructor() {
|
|
29972
|
+
super(
|
|
29973
|
+
"[notify_stdout_closed] inbox --notify lost its stdout reader and stopped before advancing its cursor. Start one fresh watcher under a live Monitor."
|
|
29974
|
+
);
|
|
29975
|
+
}
|
|
29976
|
+
};
|
|
29951
29977
|
function createArrivalRetryNoticePolicy(thresholdMs = ARRIVAL_RETRY_NOTICE_THRESHOLD_MS) {
|
|
29952
29978
|
let firstFailureAt = null;
|
|
29953
29979
|
let failureEmitted = false;
|
|
@@ -30065,6 +30091,33 @@ function formatArrivalNotification(notification) {
|
|
|
30065
30091
|
const attachmentCopy = notification.attachment_count === 0 ? "" : ` \u2014 ${notification.attachment_count} attachment${notification.attachment_count === 1 ? "" : "s"}`;
|
|
30066
30092
|
return `CommonSwarm from ${notification.sender_kind} ${notification.sender}: ${notification.snippet}${attachmentCopy} \u2014 reply: ${notification.reply_command}`;
|
|
30067
30093
|
}
|
|
30094
|
+
function notifyWriteError(error) {
|
|
30095
|
+
return error.code === "EPIPE" ? new NotifyStdoutClosedError() : error;
|
|
30096
|
+
}
|
|
30097
|
+
async function writeArrivalMonitorLine(line, stream2 = process.stdout) {
|
|
30098
|
+
await new Promise((resolve2, reject) => {
|
|
30099
|
+
let settled = false;
|
|
30100
|
+
const finish = (error) => {
|
|
30101
|
+
if (settled) return;
|
|
30102
|
+
settled = true;
|
|
30103
|
+
if (error) {
|
|
30104
|
+
setImmediate(() => stream2.off("error", onError));
|
|
30105
|
+
reject(notifyWriteError(error));
|
|
30106
|
+
} else {
|
|
30107
|
+
stream2.off("error", onError);
|
|
30108
|
+
resolve2();
|
|
30109
|
+
}
|
|
30110
|
+
};
|
|
30111
|
+
const onError = (error) => finish(error);
|
|
30112
|
+
stream2.once("error", onError);
|
|
30113
|
+
try {
|
|
30114
|
+
stream2.write(`${line}
|
|
30115
|
+
`, (error) => finish(error));
|
|
30116
|
+
} catch (error) {
|
|
30117
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
30118
|
+
}
|
|
30119
|
+
});
|
|
30120
|
+
}
|
|
30068
30121
|
function cursorOf(signal) {
|
|
30069
30122
|
return { created_at: signal.created_at, id: signal.id };
|
|
30070
30123
|
}
|
|
@@ -30275,7 +30328,11 @@ function parseDeliveryReceipt(value) {
|
|
|
30275
30328
|
"protocol",
|
|
30276
30329
|
"delivery receipt returned a malformed last_error_code"
|
|
30277
30330
|
);
|
|
30278
|
-
})()
|
|
30331
|
+
})(),
|
|
30332
|
+
pending_for_main_count: Object.hasOwn(row, "pending_for_main_count") ? row.pending_for_main_count === null ? null : nonNegativeInteger(
|
|
30333
|
+
row.pending_for_main_count,
|
|
30334
|
+
"pending_for_main_count"
|
|
30335
|
+
) : null
|
|
30279
30336
|
};
|
|
30280
30337
|
}
|
|
30281
30338
|
function parseUntrackedAgent(value) {
|
|
@@ -30629,9 +30686,10 @@ function renderSignalReceiptReport(report, nowMs = Date.now()) {
|
|
|
30629
30686
|
].join("\n");
|
|
30630
30687
|
}
|
|
30631
30688
|
if (state === "queued") {
|
|
30689
|
+
const queueCount = receipt.pending_for_main_count;
|
|
30632
30690
|
return [
|
|
30633
|
-
`Queued for agent ${receipt.recipient_agent_principal_id}'s interactive session ${relativeAge(receipt.acked_at, nowMs)}
|
|
30634
|
-
|
|
30691
|
+
`Queued for agent ${receipt.recipient_agent_principal_id}'s interactive session ${relativeAge(receipt.acked_at, nowMs)}; waiting for the recipient's session hook${typeof queueCount === "number" ? ` (${queueCount} in queue)` : ""}.`,
|
|
30692
|
+
`Ask the agent's operator to check its listener with: ${listenerStatusCommand(report, receipt)}`,
|
|
30635
30693
|
`Check again with: ${receiptCheckCommand(report)}`
|
|
30636
30694
|
].join("\n");
|
|
30637
30695
|
}
|
|
@@ -30686,7 +30744,8 @@ function signalReceiptJsonPayload(report, nowMs = Date.now()) {
|
|
|
30686
30744
|
acked_at: receipt.acked_at,
|
|
30687
30745
|
attempt_count: receipt.attempt_count,
|
|
30688
30746
|
lease_expiry_count: receipt.lease_expiry_count,
|
|
30689
|
-
last_error_code: receipt.last_error_code
|
|
30747
|
+
last_error_code: receipt.last_error_code,
|
|
30748
|
+
pending_for_main_count: receipt.pending_for_main_count ?? null
|
|
30690
30749
|
}
|
|
30691
30750
|
)
|
|
30692
30751
|
};
|
|
@@ -30799,7 +30858,7 @@ var CLAUDE_ACP_MIN_VERSION = "0.64.2";
|
|
|
30799
30858
|
var CLAUDE_ACP_LAST_MEASURED_VERSION = "0.64.2";
|
|
30800
30859
|
var CLAUDE_PERMISSION_MODE_ID = "default";
|
|
30801
30860
|
var CODEX_ACP_MIN_VERSION = "1.1.9";
|
|
30802
|
-
var CODEX_ACP_LAST_MEASURED_VERSION = "1.
|
|
30861
|
+
var CODEX_ACP_LAST_MEASURED_VERSION = "1.8.0";
|
|
30803
30862
|
var CODEX_PERMISSION_MODE_ID = "read-only";
|
|
30804
30863
|
var ACP_PROTOCOL_VERSION = 1;
|
|
30805
30864
|
var OPENCODE_FORCED_PERMISSION_TOOLS = [
|
|
@@ -31041,10 +31100,12 @@ var AcpVersionBelowFloorError = class extends AcpVersionError {
|
|
|
31041
31100
|
actual;
|
|
31042
31101
|
};
|
|
31043
31102
|
var AcpPermissionCanaryError = class extends AcpHostError {
|
|
31044
|
-
constructor(message) {
|
|
31103
|
+
constructor(message, reasonCode = null) {
|
|
31045
31104
|
super("permission_canary_failed", message);
|
|
31105
|
+
this.reasonCode = reasonCode;
|
|
31046
31106
|
this.name = "AcpPermissionCanaryError";
|
|
31047
31107
|
}
|
|
31108
|
+
reasonCode;
|
|
31048
31109
|
};
|
|
31049
31110
|
var AcpPromptsBlockedError = class extends AcpHostError {
|
|
31050
31111
|
constructor() {
|
|
@@ -31513,7 +31574,8 @@ var AcpHostSession = class _AcpHostSession {
|
|
|
31513
31574
|
}
|
|
31514
31575
|
const detail = last?.reason ?? "permission-boundary canary failed: need host reject + correlated terminal tool status";
|
|
31515
31576
|
throw new AcpPermissionCanaryError(
|
|
31516
|
-
total === 1 ? detail : `${detail} (failed ${total} attempts)
|
|
31577
|
+
total === 1 ? detail : `${detail} (failed ${total} attempts)`,
|
|
31578
|
+
last?.reasonCode ?? null
|
|
31517
31579
|
);
|
|
31518
31580
|
}
|
|
31519
31581
|
/** Test/helper: force-enable prompts without canary (never used by production open path). */
|
|
@@ -31558,7 +31620,8 @@ var AcpHostSession = class _AcpHostSession {
|
|
|
31558
31620
|
passed: false,
|
|
31559
31621
|
sawPermissionRequest: this.canaryState.sawPermissionRequest,
|
|
31560
31622
|
sawDeniedToolResult: this.canaryState.sawDeniedToolResult,
|
|
31561
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
31623
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
31624
|
+
...err instanceof AcpHostError ? { reasonCode: err.code } : {}
|
|
31562
31625
|
};
|
|
31563
31626
|
}
|
|
31564
31627
|
}
|
|
@@ -33098,6 +33161,17 @@ async function assertCodexVersionFloor(executable, options) {
|
|
|
33098
33161
|
});
|
|
33099
33162
|
const version3 = parseCodexVersionOutput(stdout);
|
|
33100
33163
|
if (!version3) {
|
|
33164
|
+
const codexCliVersion = parseProviderVersionOutput(
|
|
33165
|
+
stdout,
|
|
33166
|
+
/\bcodex-cli\b/i,
|
|
33167
|
+
false
|
|
33168
|
+
);
|
|
33169
|
+
if (codexCliVersion) {
|
|
33170
|
+
throw new AcpVersionError(
|
|
33171
|
+
"this is the Codex CLI; --codex-executable takes the codex-acp bridge (npm i -g @agentclientprotocol/codex-acp)",
|
|
33172
|
+
"executable_not_bridge"
|
|
33173
|
+
);
|
|
33174
|
+
}
|
|
33101
33175
|
throw new AcpVersionParseError(
|
|
33102
33176
|
`could not parse codex-acp version from: ${stdout.trim().slice(0, 200)}`
|
|
33103
33177
|
);
|
|
@@ -33163,10 +33237,13 @@ async function openCodexAcpSession(options) {
|
|
|
33163
33237
|
typeof pathEnv === "string" ? pathEnv : void 0
|
|
33164
33238
|
);
|
|
33165
33239
|
const env = buildCodexChildEnv(parentEnv);
|
|
33240
|
+
let providerVersion;
|
|
33166
33241
|
if (!options.skipVersionCheck) {
|
|
33167
|
-
await assertCodexVersionFloor(executable, {
|
|
33168
|
-
|
|
33169
|
-
|
|
33242
|
+
providerVersion = await assertCodexVersionFloor(executable, { env });
|
|
33243
|
+
options.onVersionNotice?.({
|
|
33244
|
+
provider: "codex-acp",
|
|
33245
|
+
runningVersion: providerVersion,
|
|
33246
|
+
lastMeasuredVersion: CODEX_ACP_LAST_MEASURED_VERSION
|
|
33170
33247
|
});
|
|
33171
33248
|
}
|
|
33172
33249
|
if (options.signal?.aborted) {
|
|
@@ -33258,7 +33335,15 @@ async function openCodexAcpSession(options) {
|
|
|
33258
33335
|
})();
|
|
33259
33336
|
return closePromise;
|
|
33260
33337
|
};
|
|
33261
|
-
return {
|
|
33338
|
+
return {
|
|
33339
|
+
session,
|
|
33340
|
+
child,
|
|
33341
|
+
executable,
|
|
33342
|
+
args,
|
|
33343
|
+
env,
|
|
33344
|
+
...providerVersion ? { providerVersion } : {},
|
|
33345
|
+
close
|
|
33346
|
+
};
|
|
33262
33347
|
} catch (error) {
|
|
33263
33348
|
removeAbortListener();
|
|
33264
33349
|
transport.close();
|
|
@@ -35026,6 +35111,10 @@ var CodexListenerClosedDuringOpen = class extends Error {
|
|
|
35026
35111
|
this.name = "CodexListenerClosedDuringOpen";
|
|
35027
35112
|
}
|
|
35028
35113
|
};
|
|
35114
|
+
function pathIsInsideOrEqual(ancestor, candidate) {
|
|
35115
|
+
const fromAncestor = (0, import_node_path14.relative)(ancestor, candidate);
|
|
35116
|
+
return fromAncestor === "" || fromAncestor !== ".." && !fromAncestor.startsWith(`..${import_node_path14.sep}`) && !(0, import_node_path14.isAbsolute)(fromAncestor);
|
|
35117
|
+
}
|
|
35029
35118
|
var CodexListenerModel = class {
|
|
35030
35119
|
constructor(options) {
|
|
35031
35120
|
this.options = options;
|
|
@@ -35157,10 +35246,25 @@ var CodexListenerModel = class {
|
|
|
35157
35246
|
}
|
|
35158
35247
|
/** Force Codex's measured shell permission path without changing worker cwd. */
|
|
35159
35248
|
async enablePromptsAfterCodexCanary(handle) {
|
|
35249
|
+
const configuredHome = this.options.env?.HOME;
|
|
35250
|
+
const home = configuredHome && (0, import_node_path14.isAbsolute)(configuredHome) ? configuredHome : (0, import_node_os9.homedir)();
|
|
35251
|
+
const sentinelDirectory = (0, import_node_path14.join)(home, ".cswarm", "canary");
|
|
35252
|
+
await (0, import_promises8.mkdir)(sentinelDirectory, { recursive: true, mode: 448 });
|
|
35253
|
+
await (0, import_promises8.chmod)(sentinelDirectory, 448);
|
|
35254
|
+
const [workerCwd, canaryDirectory] = await Promise.all([
|
|
35255
|
+
(0, import_promises8.realpath)(this.options.cwd),
|
|
35256
|
+
(0, import_promises8.realpath)(sentinelDirectory)
|
|
35257
|
+
]);
|
|
35258
|
+
if (pathIsInsideOrEqual(workerCwd, canaryDirectory)) {
|
|
35259
|
+
throw new AcpPermissionCanaryError(
|
|
35260
|
+
`canary_path_inside_cwd: ${canaryDirectory} is inside listener cwd ${workerCwd}. Next: pass a --cwd that is not your home directory`
|
|
35261
|
+
);
|
|
35262
|
+
}
|
|
35160
35263
|
const sentinelPath = (0, import_node_path14.join)(
|
|
35161
|
-
|
|
35264
|
+
canaryDirectory,
|
|
35162
35265
|
`cswarm-codex-permission-canary-${process.pid}-${(0, import_node_crypto17.randomUUID)()}`
|
|
35163
35266
|
);
|
|
35267
|
+
let canaryError;
|
|
35164
35268
|
let sentinelCreated = false;
|
|
35165
35269
|
try {
|
|
35166
35270
|
await handle.session.enablePromptsAfterCanary({
|
|
@@ -35168,6 +35272,8 @@ var CodexListenerModel = class {
|
|
|
35168
35272
|
probeText: `Use a shell command to create ${sentinelPath} with content CSWARM_CANARY_NOOP. You must use the shell. Do nothing else.`,
|
|
35169
35273
|
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
35170
35274
|
});
|
|
35275
|
+
} catch (error) {
|
|
35276
|
+
canaryError = error;
|
|
35171
35277
|
} finally {
|
|
35172
35278
|
try {
|
|
35173
35279
|
await (0, import_promises8.lstat)(sentinelPath);
|
|
@@ -35177,11 +35283,39 @@ var CodexListenerModel = class {
|
|
|
35177
35283
|
if (error.code !== "ENOENT") throw error;
|
|
35178
35284
|
}
|
|
35179
35285
|
}
|
|
35286
|
+
const observation = handle.session.canaryObservation;
|
|
35287
|
+
const sawPermissionRequest = observation?.sawPermissionRequest === true;
|
|
35288
|
+
const bridgeVersion = handle.providerVersion ?? handle.session.info?.agentVersion ?? "unknown";
|
|
35289
|
+
if (sentinelCreated && !sawPermissionRequest) {
|
|
35290
|
+
throw new AcpPermissionCanaryError(
|
|
35291
|
+
`canary_executed_without_permission: codex-acp ${bridgeVersion} ran the shell probe without a permission request and wrote ${sentinelPath}. Next: replace codex-acp ${bridgeVersion} with a bridge version that asks before writing ${sentinelPath}`
|
|
35292
|
+
);
|
|
35293
|
+
}
|
|
35180
35294
|
if (sentinelCreated) {
|
|
35181
35295
|
throw new AcpPermissionCanaryError(
|
|
35182
|
-
|
|
35296
|
+
`canary_write_not_blocked: codex-acp ${bridgeVersion} wrote ${sentinelPath} even though the host rejected the canary. Next: update or reinstall the codex-acp bridge`
|
|
35183
35297
|
);
|
|
35184
35298
|
}
|
|
35299
|
+
if (canaryError instanceof AcpPermissionCanaryError) {
|
|
35300
|
+
if (canaryError.reasonCode === "timeout") {
|
|
35301
|
+
throw new AcpPermissionCanaryError(
|
|
35302
|
+
`canary_timeout: ${canaryError.message}. Next: retry to run a fresh bounded permission canary`,
|
|
35303
|
+
"timeout"
|
|
35304
|
+
);
|
|
35305
|
+
}
|
|
35306
|
+
if (canaryError.reasonCode !== null) {
|
|
35307
|
+
throw new AcpPermissionCanaryError(
|
|
35308
|
+
`canary_bridge_error: codex-acp ${bridgeVersion} returned ${canaryError.message}. Next: resolve the quoted bridge error, then retry`,
|
|
35309
|
+
canaryError.reasonCode
|
|
35310
|
+
);
|
|
35311
|
+
}
|
|
35312
|
+
if (!sawPermissionRequest) {
|
|
35313
|
+
throw new AcpPermissionCanaryError(
|
|
35314
|
+
`canary_no_tool_call: codex-acp ${bridgeVersion} did not request permission or create ${sentinelPath}. Next: retry to re-sample the model's shell choice`
|
|
35315
|
+
);
|
|
35316
|
+
}
|
|
35317
|
+
}
|
|
35318
|
+
if (canaryError !== void 0) throw canaryError;
|
|
35185
35319
|
}
|
|
35186
35320
|
};
|
|
35187
35321
|
|
|
@@ -37138,6 +37272,7 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
37138
37272
|
"lastErrorDetail",
|
|
37139
37273
|
"providerVersion",
|
|
37140
37274
|
"providerLastMeasuredVersion",
|
|
37275
|
+
"cswarmVersion",
|
|
37141
37276
|
"lastWorkerStderrTail",
|
|
37142
37277
|
"logPath",
|
|
37143
37278
|
"deliveryMode",
|
|
@@ -37197,7 +37332,7 @@ function parseStatus(raw) {
|
|
|
37197
37332
|
const nullableUuid3 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE17.test(candidate);
|
|
37198
37333
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
37199
37334
|
const nullableTimestamp3 = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
37200
|
-
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE17.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE17.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE17.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))) || !nullableUuid3(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastErrorDetail === void 0 || row.lastErrorDetail === null || typeof row.lastErrorDetail === "string" && row.lastErrorDetail.length > 0 && row.lastErrorDetail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastErrorDetail)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path16.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp3(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp3(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp3(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)) {
|
|
37335
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE17.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE17.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE17.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))) || !nullableUuid3(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastErrorDetail === void 0 || row.lastErrorDetail === null || typeof row.lastErrorDetail === "string" && row.lastErrorDetail.length > 0 && row.lastErrorDetail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastErrorDetail)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || !(row.cswarmVersion === void 0 || row.cswarmVersion === null || typeof row.cswarmVersion === "string" && SEMVER_RE2.test(row.cswarmVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path16.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp3(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp3(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp3(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)) {
|
|
37201
37336
|
throw new Error("stored listener status is malformed");
|
|
37202
37337
|
}
|
|
37203
37338
|
const routeMode = row.routeMode ?? "worker";
|
|
@@ -37217,6 +37352,7 @@ function parseStatus(raw) {
|
|
|
37217
37352
|
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
|
|
37218
37353
|
providerVersion: row.providerVersion ?? null,
|
|
37219
37354
|
providerLastMeasuredVersion: row.providerLastMeasuredVersion ?? null,
|
|
37355
|
+
...row.cswarmVersion === void 0 ? {} : { cswarmVersion: row.cswarmVersion },
|
|
37220
37356
|
routeMode,
|
|
37221
37357
|
deferOverChars,
|
|
37222
37358
|
pendingForMainCount: row.pendingForMainCount ?? 0,
|
|
@@ -37241,6 +37377,10 @@ async function readListenerStatus(paths) {
|
|
|
37241
37377
|
const raw = await readSecureJsonFile(paths.statusPath, MAX_STATUS_BYTES);
|
|
37242
37378
|
return raw === null ? null : parseStatus(raw);
|
|
37243
37379
|
}
|
|
37380
|
+
async function readListenerStatusIfPresent(paths) {
|
|
37381
|
+
const raw = await readSecureJsonFileIfPresent(paths.statusPath, MAX_STATUS_BYTES);
|
|
37382
|
+
return raw === null ? null : parseStatus(raw);
|
|
37383
|
+
}
|
|
37244
37384
|
async function appendListenerEvent(paths, event) {
|
|
37245
37385
|
const allowed = /* @__PURE__ */ new Set([
|
|
37246
37386
|
"ts",
|
|
@@ -37646,6 +37786,7 @@ async function runListenerSupervisor(options) {
|
|
|
37646
37786
|
version: 1,
|
|
37647
37787
|
instanceId: proposedInstanceId,
|
|
37648
37788
|
provider: options.provider ?? "grok",
|
|
37789
|
+
...options.cswarmVersion ? { cswarmVersion: options.cswarmVersion } : {},
|
|
37649
37790
|
...options.permissionMode ? { permissionMode: options.permissionMode } : {},
|
|
37650
37791
|
profileId: options.profileId,
|
|
37651
37792
|
workspaceId: options.workspaceId.toLowerCase(),
|
|
@@ -38950,6 +39091,9 @@ function newestFirst(topics) {
|
|
|
38950
39091
|
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || left.topic.localeCompare(right.topic)
|
|
38951
39092
|
);
|
|
38952
39093
|
}
|
|
39094
|
+
function changedTopics(previous, topics) {
|
|
39095
|
+
return previous === null ? newestFirst(topics).slice(0, BRAIN_DIGEST_TOPIC_LIMIT) : topics.filter((topic) => previous.topicVersions[topic.topic] !== topic.version);
|
|
39096
|
+
}
|
|
38953
39097
|
function renderBrainDigest(topicCount, topics) {
|
|
38954
39098
|
if (topics.length === 0) return null;
|
|
38955
39099
|
const names = newestFirst(topics).map((topic) => `${topic.topic} v${topic.version}`).join(", ");
|
|
@@ -38967,6 +39111,19 @@ var FileBrainDigestStore = class {
|
|
|
38967
39111
|
instanceDirectory;
|
|
38968
39112
|
location;
|
|
38969
39113
|
principalId;
|
|
39114
|
+
/** Read the shared high-water without advancing it. */
|
|
39115
|
+
async preview(topics) {
|
|
39116
|
+
const raw = await readSecureJsonFileIfPresent(
|
|
39117
|
+
this.location,
|
|
39118
|
+
MAX_BRAIN_DIGEST_STATE_BYTES
|
|
39119
|
+
);
|
|
39120
|
+
const previous = raw === null ? null : parseState(raw);
|
|
39121
|
+
if (previous !== null && previous.principalId !== this.principalId) {
|
|
39122
|
+
throw new Error("stored brain digest state belongs to another principal");
|
|
39123
|
+
}
|
|
39124
|
+
currentState(this.principalId, topics);
|
|
39125
|
+
return renderBrainDigest(topics.length, changedTopics(previous, topics));
|
|
39126
|
+
}
|
|
38970
39127
|
async consume(topics) {
|
|
38971
39128
|
return await withFileLock(this.instanceDirectory, BRAIN_DIGEST_LOCK, async () => {
|
|
38972
39129
|
const raw = await readSecureJsonFile(
|
|
@@ -38978,9 +39135,7 @@ var FileBrainDigestStore = class {
|
|
|
38978
39135
|
throw new Error("stored brain digest state belongs to another principal");
|
|
38979
39136
|
}
|
|
38980
39137
|
const next = currentState(this.principalId, topics);
|
|
38981
|
-
const changed = previous
|
|
38982
|
-
(topic) => previous.topicVersions[topic.topic] !== topic.version
|
|
38983
|
-
);
|
|
39138
|
+
const changed = changedTopics(previous, topics);
|
|
38984
39139
|
const serialized = JSON.stringify(next);
|
|
38985
39140
|
if (Buffer.byteLength(serialized, "utf8") > MAX_BRAIN_DIGEST_STATE_BYTES) {
|
|
38986
39141
|
throw new Error("brain digest state is larger than this store accepts");
|
|
@@ -39120,6 +39275,31 @@ var FileHookSurfaceStore = class {
|
|
|
39120
39275
|
}
|
|
39121
39276
|
instanceDirectory;
|
|
39122
39277
|
path;
|
|
39278
|
+
/** Preview unseen hook rows without taking a write lock or advancing state. */
|
|
39279
|
+
async previewUnseen(items) {
|
|
39280
|
+
const raw = await readSecureJsonFileIfPresent(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
39281
|
+
const seen = new Set(raw === null ? [] : parseSurface(raw).surfacedSignalIds);
|
|
39282
|
+
const unseen = [];
|
|
39283
|
+
for (const item of items) {
|
|
39284
|
+
const signalId = item.signalId.toLowerCase();
|
|
39285
|
+
if (!UUID_RE21.test(signalId) || seen.has(signalId)) continue;
|
|
39286
|
+
seen.add(signalId);
|
|
39287
|
+
unseen.push(item);
|
|
39288
|
+
}
|
|
39289
|
+
return unseen;
|
|
39290
|
+
}
|
|
39291
|
+
/** Read attendance evidence without advancing the hook high-water. */
|
|
39292
|
+
async evidence() {
|
|
39293
|
+
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
39294
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
39295
|
+
if (raw === null) return { exists: false, surfacedSignalIds: [] };
|
|
39296
|
+
const state = parseSurface(raw);
|
|
39297
|
+
return {
|
|
39298
|
+
exists: true,
|
|
39299
|
+
surfacedSignalIds: state.surfacedSignalIds
|
|
39300
|
+
};
|
|
39301
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
39302
|
+
}
|
|
39123
39303
|
async stage(items, droppedCount) {
|
|
39124
39304
|
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
39125
39305
|
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
@@ -39643,6 +39823,486 @@ async function runListenerHookCheck(options = {}) {
|
|
|
39643
39823
|
}
|
|
39644
39824
|
}
|
|
39645
39825
|
|
|
39826
|
+
// src/listener/attendance-canary.ts
|
|
39827
|
+
var import_promises11 = require("node:fs/promises");
|
|
39828
|
+
var LOG_TAIL_BYTES = 256 * 1024;
|
|
39829
|
+
function agentReceipt(receipts, principalId) {
|
|
39830
|
+
for (const receipt of receipts) {
|
|
39831
|
+
if ("recipient_agent_principal_id" in receipt && receipt.recipient_agent_principal_id === principalId) {
|
|
39832
|
+
return receipt;
|
|
39833
|
+
}
|
|
39834
|
+
}
|
|
39835
|
+
return null;
|
|
39836
|
+
}
|
|
39837
|
+
async function readLogTail(path) {
|
|
39838
|
+
let handle;
|
|
39839
|
+
try {
|
|
39840
|
+
handle = await (0, import_promises11.open)(path, "r");
|
|
39841
|
+
} catch (error) {
|
|
39842
|
+
if (error.code === "ENOENT") return "";
|
|
39843
|
+
throw error;
|
|
39844
|
+
}
|
|
39845
|
+
try {
|
|
39846
|
+
const size2 = (await handle.stat()).size;
|
|
39847
|
+
const start = Math.max(0, size2 - LOG_TAIL_BYTES);
|
|
39848
|
+
const buffer2 = Buffer.alloc(size2 - start);
|
|
39849
|
+
await handle.read(buffer2, 0, buffer2.length, start);
|
|
39850
|
+
let text = buffer2.toString("utf8");
|
|
39851
|
+
if (start > 0) {
|
|
39852
|
+
const newline = text.indexOf("\n");
|
|
39853
|
+
text = newline < 0 ? "" : text.slice(newline + 1);
|
|
39854
|
+
}
|
|
39855
|
+
return text;
|
|
39856
|
+
} finally {
|
|
39857
|
+
await handle.close();
|
|
39858
|
+
}
|
|
39859
|
+
}
|
|
39860
|
+
async function logEvidence(path, signalId) {
|
|
39861
|
+
let claimedAt = null;
|
|
39862
|
+
let routeDecision = null;
|
|
39863
|
+
let routedAt = null;
|
|
39864
|
+
for (const line of (await readLogTail(path)).split("\n")) {
|
|
39865
|
+
if (line.length === 0) continue;
|
|
39866
|
+
let value;
|
|
39867
|
+
try {
|
|
39868
|
+
value = JSON.parse(line);
|
|
39869
|
+
} catch {
|
|
39870
|
+
continue;
|
|
39871
|
+
}
|
|
39872
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
39873
|
+
const row = value;
|
|
39874
|
+
if (row.signal_id !== signalId || typeof row.ts !== "string") continue;
|
|
39875
|
+
if (row.event === "listener_delivery_claim") claimedAt = row.ts;
|
|
39876
|
+
if (row.event === "listener_routing_decision" && (row.route_decision === "main" || row.route_decision === "worker")) {
|
|
39877
|
+
routeDecision = row.route_decision;
|
|
39878
|
+
routedAt = row.ts;
|
|
39879
|
+
}
|
|
39880
|
+
}
|
|
39881
|
+
return { claimedAt, routeDecision, routedAt };
|
|
39882
|
+
}
|
|
39883
|
+
async function runListenerAttendanceCanary(options) {
|
|
39884
|
+
const now = options.now ?? Date.now;
|
|
39885
|
+
const sleep2 = options.sleep ?? ((milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds)));
|
|
39886
|
+
const startedAt = now();
|
|
39887
|
+
const deadlineMs = startedAt + options.waitMs;
|
|
39888
|
+
const client = new ThinCommandClient(options.target, options.fetcher, {
|
|
39889
|
+
signalRequestTimeoutMs: options.waitMs
|
|
39890
|
+
});
|
|
39891
|
+
const posted = await client.sendSignal({
|
|
39892
|
+
workspaceId: options.workspaceId,
|
|
39893
|
+
credential: await options.credential(),
|
|
39894
|
+
command: {
|
|
39895
|
+
kind: "post_signal",
|
|
39896
|
+
signal_kind: "note",
|
|
39897
|
+
body: "CommonSwarm listener attendance canary. No reply is needed.",
|
|
39898
|
+
to_user_id: null,
|
|
39899
|
+
to_agent_principal_id: options.principalId,
|
|
39900
|
+
in_reply_to: null,
|
|
39901
|
+
about: null,
|
|
39902
|
+
until_ms: 10 * 6e4
|
|
39903
|
+
}
|
|
39904
|
+
});
|
|
39905
|
+
const signalId = posted.response.signal.id;
|
|
39906
|
+
let claimedAt = null;
|
|
39907
|
+
let routeDecision = null;
|
|
39908
|
+
let routedAt = null;
|
|
39909
|
+
let pendingForMainCount = null;
|
|
39910
|
+
let surfacedAt = null;
|
|
39911
|
+
let observedAt = null;
|
|
39912
|
+
let receiptReadErrorCode = null;
|
|
39913
|
+
while (true) {
|
|
39914
|
+
const log = await logEvidence(options.paths.logPath, signalId);
|
|
39915
|
+
claimedAt ??= log.claimedAt;
|
|
39916
|
+
routeDecision ??= log.routeDecision;
|
|
39917
|
+
routedAt ??= log.routedAt;
|
|
39918
|
+
const hook = await new FileHookSurfaceStore(
|
|
39919
|
+
options.paths.instanceDirectory
|
|
39920
|
+
).evidence();
|
|
39921
|
+
if (hook.surfacedSignalIds.includes(signalId)) {
|
|
39922
|
+
surfacedAt ??= new Date(now()).toISOString();
|
|
39923
|
+
}
|
|
39924
|
+
if (now() >= deadlineMs) break;
|
|
39925
|
+
try {
|
|
39926
|
+
const report = await readAgentDeliveryReceipts(
|
|
39927
|
+
options.target,
|
|
39928
|
+
await options.credential(),
|
|
39929
|
+
options.workspaceId,
|
|
39930
|
+
signalId,
|
|
39931
|
+
{
|
|
39932
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
39933
|
+
deadlineMs,
|
|
39934
|
+
now
|
|
39935
|
+
}
|
|
39936
|
+
);
|
|
39937
|
+
receiptReadErrorCode = null;
|
|
39938
|
+
const receipt = agentReceipt(report.receipts, options.principalId);
|
|
39939
|
+
if (receipt !== null) {
|
|
39940
|
+
claimedAt ??= receipt.delivered_at;
|
|
39941
|
+
if (receipt.ack_outcome === "queued") {
|
|
39942
|
+
routeDecision ??= "main";
|
|
39943
|
+
routedAt ??= receipt.acked_at;
|
|
39944
|
+
pendingForMainCount = receipt.pending_for_main_count ?? null;
|
|
39945
|
+
}
|
|
39946
|
+
const state = deliveryReceiptState(receipt, now());
|
|
39947
|
+
if (state === "observed" || state === "replied") {
|
|
39948
|
+
observedAt = receipt.acked_at;
|
|
39949
|
+
}
|
|
39950
|
+
}
|
|
39951
|
+
} catch (error) {
|
|
39952
|
+
receiptReadErrorCode = error instanceof DeliveryReceiptReadError ? error.code : error instanceof SignalReadTimeoutError ? "timeout" : "transport";
|
|
39953
|
+
}
|
|
39954
|
+
const complete = claimedAt !== null && routeDecision !== null && (routeDecision === "worker" || surfacedAt !== null) && observedAt !== null;
|
|
39955
|
+
if (complete || now() >= deadlineMs) break;
|
|
39956
|
+
await sleep2(Math.min(options.pollMs ?? 250, deadlineMs - now()));
|
|
39957
|
+
}
|
|
39958
|
+
const stalledAt = claimedAt === null ? "claimed" : routeDecision === null ? "routed" : routeDecision === "main" && surfacedAt === null ? "surfaced" : observedAt === null ? "observed" : null;
|
|
39959
|
+
return {
|
|
39960
|
+
signalId,
|
|
39961
|
+
acceptedAt: new Date(startedAt).toISOString(),
|
|
39962
|
+
claimedAt,
|
|
39963
|
+
routeDecision,
|
|
39964
|
+
routedAt,
|
|
39965
|
+
pendingForMainCount,
|
|
39966
|
+
surfacedAt,
|
|
39967
|
+
observedAt,
|
|
39968
|
+
receiptReadErrorCode,
|
|
39969
|
+
stalledAt
|
|
39970
|
+
};
|
|
39971
|
+
}
|
|
39972
|
+
function renderListenerAttendanceCanary(result, workspaceId2, principalId) {
|
|
39973
|
+
const statusCommand = `cswarm listen status --workspace-id ${workspaceId2} --principal-id ${principalId}`;
|
|
39974
|
+
const route = result.routeDecision === "main" ? `queued for the interactive session${result.pendingForMainCount === null ? "" : ` (${result.pendingForMainCount} in queue)`}` : result.routeDecision === "worker" ? "sent to the worker" : "not measured";
|
|
39975
|
+
const lines = [
|
|
39976
|
+
`Canary note: ${result.signalId}.`,
|
|
39977
|
+
`ACCEPTED: yes at ${result.acceptedAt}.`,
|
|
39978
|
+
`CLAIMED: ${result.claimedAt === null ? "no" : `yes at ${result.claimedAt}`}.`,
|
|
39979
|
+
`QUEUED/WORKER: ${route}.`,
|
|
39980
|
+
`SURFACED: ${result.routeDecision === "worker" ? "not required for the worker route" : result.surfacedAt === null ? "no" : `yes at ${result.surfacedAt}`}.`,
|
|
39981
|
+
`OBSERVED: ${result.observedAt === null ? "no" : `yes at ${result.observedAt}`}.`
|
|
39982
|
+
];
|
|
39983
|
+
if (result.receiptReadErrorCode !== null) {
|
|
39984
|
+
lines.push(`RECEIPT READ: failed (${result.receiptReadErrorCode}).`);
|
|
39985
|
+
}
|
|
39986
|
+
if (result.stalledAt === null) {
|
|
39987
|
+
lines.push("Canary passed: every required hop was measured.");
|
|
39988
|
+
} else if (result.stalledAt === "surfaced") {
|
|
39989
|
+
lines.push(
|
|
39990
|
+
`STALLED: surfaced. Next: cswarm hook install claude --principal-id ${principalId} --write, then start a fresh session. Or restart the listener with --route worker.`
|
|
39991
|
+
);
|
|
39992
|
+
} else {
|
|
39993
|
+
lines.push(`STALLED: ${result.stalledAt}. Next: ${statusCommand}`);
|
|
39994
|
+
}
|
|
39995
|
+
return lines.join("\n");
|
|
39996
|
+
}
|
|
39997
|
+
|
|
39998
|
+
// src/resume.ts
|
|
39999
|
+
var import_node_child_process8 = require("node:child_process");
|
|
40000
|
+
function execFileText(file, args) {
|
|
40001
|
+
return new Promise((resolve2, reject) => {
|
|
40002
|
+
(0, import_node_child_process8.execFile)(file, [...args], {
|
|
40003
|
+
encoding: "utf8",
|
|
40004
|
+
maxBuffer: 4 * 1024 * 1024
|
|
40005
|
+
}, (error, stdout) => {
|
|
40006
|
+
if (error) reject(error);
|
|
40007
|
+
else resolve2(stdout);
|
|
40008
|
+
});
|
|
40009
|
+
});
|
|
40010
|
+
}
|
|
40011
|
+
function systemProcessTable() {
|
|
40012
|
+
return {
|
|
40013
|
+
async list() {
|
|
40014
|
+
const output = await execFileText("ps", ["-axo", "pid=,command="]);
|
|
40015
|
+
return output.split("\n").flatMap((line) => {
|
|
40016
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
40017
|
+
if (!match) return [];
|
|
40018
|
+
const pid = Number(match[1]);
|
|
40019
|
+
return Number.isSafeInteger(pid) && pid > 0 ? [{ pid, command: match[2] }] : [];
|
|
40020
|
+
});
|
|
40021
|
+
}
|
|
40022
|
+
};
|
|
40023
|
+
}
|
|
40024
|
+
function lsofStdoutConsumer() {
|
|
40025
|
+
return {
|
|
40026
|
+
async inspect(pid) {
|
|
40027
|
+
let output;
|
|
40028
|
+
try {
|
|
40029
|
+
output = await execFileText(
|
|
40030
|
+
process.platform === "darwin" ? "/usr/sbin/lsof" : "lsof",
|
|
40031
|
+
["-nP", "-a", "-p", String(pid), "-d", "1", "-F", "pftan"]
|
|
40032
|
+
);
|
|
40033
|
+
} catch {
|
|
40034
|
+
return "cannot_determine";
|
|
40035
|
+
}
|
|
40036
|
+
const lines = output.split("\n");
|
|
40037
|
+
const type = lines.find((line) => line.startsWith("t"))?.slice(1) ?? "";
|
|
40038
|
+
const names = lines.filter((line) => line.startsWith("n")).map((line) => line.slice(1));
|
|
40039
|
+
if (type === "unix") {
|
|
40040
|
+
if (names.some((name) => name === "->(none)")) return "orphaned";
|
|
40041
|
+
if (names.some((name) => name.startsWith("->") && name !== "->(none)")) {
|
|
40042
|
+
return "live_reader";
|
|
40043
|
+
}
|
|
40044
|
+
return "cannot_determine";
|
|
40045
|
+
}
|
|
40046
|
+
if (type === "PIPE" || type === "FIFO") return "cannot_determine";
|
|
40047
|
+
return type.length === 0 ? "cannot_determine" : "not_pipe";
|
|
40048
|
+
}
|
|
40049
|
+
};
|
|
40050
|
+
}
|
|
40051
|
+
function commandHasFlagValue(command2, flag, values2) {
|
|
40052
|
+
for (const value of values2) {
|
|
40053
|
+
const marker = `${flag} ${value}`;
|
|
40054
|
+
let start = command2.indexOf(marker);
|
|
40055
|
+
while (start !== -1) {
|
|
40056
|
+
const before = start === 0 ? " " : command2[start - 1];
|
|
40057
|
+
const afterIndex = start + marker.length;
|
|
40058
|
+
const after = afterIndex >= command2.length ? " " : command2[afterIndex];
|
|
40059
|
+
if (/\s/.test(before) && /\s/.test(after)) return true;
|
|
40060
|
+
start = command2.indexOf(marker, start + 1);
|
|
40061
|
+
}
|
|
40062
|
+
}
|
|
40063
|
+
return false;
|
|
40064
|
+
}
|
|
40065
|
+
function isNotifyCommand(command2) {
|
|
40066
|
+
return /(?:^|\s)inbox(?:\s|$)/.test(command2) && /(?:^|\s)--notify(?:\s|$)/.test(command2);
|
|
40067
|
+
}
|
|
40068
|
+
async function findNotifyWatchers(options) {
|
|
40069
|
+
const processTable = options.processTable ?? systemProcessTable();
|
|
40070
|
+
const stdoutConsumer = options.stdoutConsumer ?? lsofStdoutConsumer();
|
|
40071
|
+
const rows3 = await processTable.list();
|
|
40072
|
+
const matches = rows3.flatMap((row) => {
|
|
40073
|
+
if (!isNotifyCommand(row.command)) return [];
|
|
40074
|
+
const matchedBy = [];
|
|
40075
|
+
if (commandHasFlagValue(row.command, "--agent-token-file", options.credentialPaths)) {
|
|
40076
|
+
matchedBy.push("agent_token_file");
|
|
40077
|
+
}
|
|
40078
|
+
if (commandHasFlagValue(row.command, "--principal-id", [options.principalId])) {
|
|
40079
|
+
matchedBy.push("principal_id");
|
|
40080
|
+
}
|
|
40081
|
+
return matchedBy.length === 0 ? [] : [{ pid: row.pid, matchedBy }];
|
|
40082
|
+
});
|
|
40083
|
+
const unique = [...new Map(matches.map((row) => [row.pid, row])).values()].sort((left, right) => left.pid - right.pid);
|
|
40084
|
+
return await Promise.all(unique.map(async (row) => ({
|
|
40085
|
+
...row,
|
|
40086
|
+
stdout: await stdoutConsumer.inspect(row.pid)
|
|
40087
|
+
})));
|
|
40088
|
+
}
|
|
40089
|
+
async function readOnlyListenerInspection(paths, adapters = {}) {
|
|
40090
|
+
const query = adapters.queryStatus ?? queryListenerControl;
|
|
40091
|
+
const read = adapters.readStatus ?? readListenerStatusIfPresent;
|
|
40092
|
+
try {
|
|
40093
|
+
return {
|
|
40094
|
+
checkedDirectory: paths.instanceDirectory,
|
|
40095
|
+
status: await query(paths, "status"),
|
|
40096
|
+
source: "live_process"
|
|
40097
|
+
};
|
|
40098
|
+
} catch {
|
|
40099
|
+
const status = await read(paths);
|
|
40100
|
+
return {
|
|
40101
|
+
checkedDirectory: paths.instanceDirectory,
|
|
40102
|
+
status,
|
|
40103
|
+
source: status === null ? "not_found" : "recorded_file"
|
|
40104
|
+
};
|
|
40105
|
+
}
|
|
40106
|
+
}
|
|
40107
|
+
async function inspectResume(options, adapters) {
|
|
40108
|
+
const identity = await adapters.readIdentity();
|
|
40109
|
+
const principalId = identity.principalId.toLowerCase();
|
|
40110
|
+
const paths = listenerPaths({
|
|
40111
|
+
profileId: options.target.profileId,
|
|
40112
|
+
workspaceId: options.workspaceId,
|
|
40113
|
+
principalId,
|
|
40114
|
+
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
40115
|
+
});
|
|
40116
|
+
const listener = await readOnlyListenerInspection(paths, adapters);
|
|
40117
|
+
const watchers = await findNotifyWatchers({
|
|
40118
|
+
credentialPaths: options.credentialPathAliases ?? [options.credentialFile],
|
|
40119
|
+
principalId,
|
|
40120
|
+
...adapters.processTable ? { processTable: adapters.processTable } : {},
|
|
40121
|
+
...adapters.stdoutConsumer ? { stdoutConsumer: adapters.stdoutConsumer } : {}
|
|
40122
|
+
});
|
|
40123
|
+
const topics = await adapters.readBrainTopics();
|
|
40124
|
+
const digestStore = new FileBrainDigestStore(paths.instanceDirectory, principalId);
|
|
40125
|
+
const digest = await digestStore.preview(topics);
|
|
40126
|
+
const inbox = await adapters.readInboxCount(principalId, paths.instanceDirectory);
|
|
40127
|
+
return {
|
|
40128
|
+
identity: { ...identity, principalId },
|
|
40129
|
+
listener,
|
|
40130
|
+
watchers,
|
|
40131
|
+
brain: { digest, highWaterFile: digestStore.location },
|
|
40132
|
+
inbox,
|
|
40133
|
+
target: options.target,
|
|
40134
|
+
workspaceId: options.workspaceId,
|
|
40135
|
+
credentialFile: options.credentialFile,
|
|
40136
|
+
installedVersion: options.installedVersion
|
|
40137
|
+
};
|
|
40138
|
+
}
|
|
40139
|
+
function safeText(value) {
|
|
40140
|
+
return value.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ").slice(0, 2e3);
|
|
40141
|
+
}
|
|
40142
|
+
function shellArg(value) {
|
|
40143
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
40144
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
40145
|
+
}
|
|
40146
|
+
function commonCommandArgs(report) {
|
|
40147
|
+
return [
|
|
40148
|
+
"--agent-token-file",
|
|
40149
|
+
shellArg(report.credentialFile),
|
|
40150
|
+
"--url",
|
|
40151
|
+
shellArg(report.target.url),
|
|
40152
|
+
"--anon-key",
|
|
40153
|
+
shellArg(report.target.anonKey),
|
|
40154
|
+
"--workspace-id",
|
|
40155
|
+
report.workspaceId
|
|
40156
|
+
].join(" ");
|
|
40157
|
+
}
|
|
40158
|
+
function restartCommand(report, status) {
|
|
40159
|
+
const common = commonCommandArgs(report);
|
|
40160
|
+
const route = status.routeMode ?? "worker";
|
|
40161
|
+
const start = [
|
|
40162
|
+
"cswarm listen start",
|
|
40163
|
+
common,
|
|
40164
|
+
`--provider ${status.provider}`,
|
|
40165
|
+
`--permissions ${status.permissionMode ?? "allow"}`,
|
|
40166
|
+
`--route ${route}`,
|
|
40167
|
+
...route === "split" && status.deferOverChars !== null && status.deferOverChars !== void 0 ? [`--defer-over ${status.deferOverChars}`] : []
|
|
40168
|
+
].join(" ");
|
|
40169
|
+
return `cswarm listen stop ${common} && ${start}`;
|
|
40170
|
+
}
|
|
40171
|
+
function watcherStateLine(watcher) {
|
|
40172
|
+
const matched = watcher.matchedBy.map(
|
|
40173
|
+
(value) => value === "agent_token_file" ? "credential path" : "principal id"
|
|
40174
|
+
).join(" and ");
|
|
40175
|
+
const state = watcher.stdout === "live_reader" ? "stdout has a live pipe reader" : watcher.stdout === "orphaned" ? "ORPHAN: stdout pipe has no reader" : watcher.stdout === "not_pipe" ? "stdout is not a pipe; the dead-reader check does not apply" : "stdout reader cannot be determined on this host";
|
|
40176
|
+
return `- PID ${watcher.pid}: ${state}; matched ${matched}.`;
|
|
40177
|
+
}
|
|
40178
|
+
function renderResume(report) {
|
|
40179
|
+
const lines = [
|
|
40180
|
+
"Identity",
|
|
40181
|
+
`You are ${safeText(report.identity.displayName)} (${report.identity.principalId}).`,
|
|
40182
|
+
"Next: use this principal for every listener, watcher, brain, and inbox check below.",
|
|
40183
|
+
"",
|
|
40184
|
+
"Listener"
|
|
40185
|
+
];
|
|
40186
|
+
const listener = report.listener;
|
|
40187
|
+
const status = listener.status;
|
|
40188
|
+
if (status === null) {
|
|
40189
|
+
lines.push(
|
|
40190
|
+
`No listener found under ${safeText(listener.checkedDirectory)} for profile ${report.target.profileId}.`,
|
|
40191
|
+
`Next: start one with the original provider: cswarm listen start ${commonCommandArgs(report)} --provider <provider>`
|
|
40192
|
+
);
|
|
40193
|
+
} else {
|
|
40194
|
+
if (listener.source === "live_process") {
|
|
40195
|
+
lines.push(
|
|
40196
|
+
`Found under ${safeText(listener.checkedDirectory)}. State: ${status.state}, reported by running PID ${status.pid}.`
|
|
40197
|
+
);
|
|
40198
|
+
} else {
|
|
40199
|
+
lines.push(
|
|
40200
|
+
`Found a status file under ${safeText(listener.checkedDirectory)}. Recorded state: ${status.state}; the control socket did not answer, so a running listener is not established.`
|
|
40201
|
+
);
|
|
40202
|
+
}
|
|
40203
|
+
const runningVersion = status.cswarmVersion ?? null;
|
|
40204
|
+
if (listener.source !== "live_process") {
|
|
40205
|
+
lines.push(
|
|
40206
|
+
`Running listener cswarm version: cannot determine because the process did not answer; status file recorded ${runningVersion ?? "no version"}; installed CLI: ${report.installedVersion}.`,
|
|
40207
|
+
`Next: restart the listener because its process did not answer: ${restartCommand(report, status)}`
|
|
40208
|
+
);
|
|
40209
|
+
} else if (runningVersion === null) {
|
|
40210
|
+
lines.push(
|
|
40211
|
+
`Listener cswarm version: cannot determine from this listener; installed CLI: ${report.installedVersion}.`,
|
|
40212
|
+
`Next: restart it to make the running version reportable: ${restartCommand(report, status)}`
|
|
40213
|
+
);
|
|
40214
|
+
} else if (runningVersion !== report.installedVersion) {
|
|
40215
|
+
lines.push(
|
|
40216
|
+
`VERSION MISMATCH: listener runs ${runningVersion}; installed ${report.installedVersion} \u2014 restart it: ${restartCommand(report, status)}`
|
|
40217
|
+
);
|
|
40218
|
+
} else {
|
|
40219
|
+
lines.push(
|
|
40220
|
+
`Listener-reported cswarm: ${runningVersion}; installed CLI: ${report.installedVersion}.`,
|
|
40221
|
+
"Next: no listener restart is needed for a version change."
|
|
40222
|
+
);
|
|
40223
|
+
}
|
|
40224
|
+
}
|
|
40225
|
+
lines.push(
|
|
40226
|
+
"",
|
|
40227
|
+
"Notify watchers",
|
|
40228
|
+
`Checked process arguments for inbox --notify matching --agent-token-file ${safeText(report.credentialFile)} or --principal-id ${report.identity.principalId}.`
|
|
40229
|
+
);
|
|
40230
|
+
if (report.watchers.length === 0) {
|
|
40231
|
+
lines.push(
|
|
40232
|
+
"Found: 0.",
|
|
40233
|
+
`Next: start one watcher under a live Monitor: cswarm inbox --notify ${commonCommandArgs(report)}`
|
|
40234
|
+
);
|
|
40235
|
+
} else {
|
|
40236
|
+
lines.push(`Found: ${report.watchers.length}.`);
|
|
40237
|
+
lines.push(...report.watchers.map(watcherStateLine));
|
|
40238
|
+
const orphans = report.watchers.filter((watcher) => watcher.stdout === "orphaned");
|
|
40239
|
+
if (orphans.length > 0) {
|
|
40240
|
+
lines.push(
|
|
40241
|
+
`Next: stop only the orphan watcher${orphans.length === 1 ? "" : "s"}; CommonSwarm did not kill anything: kill ${orphans.map((watcher) => watcher.pid).join(" ")}`
|
|
40242
|
+
);
|
|
40243
|
+
} else if (report.watchers.some((watcher) => watcher.stdout === "cannot_determine")) {
|
|
40244
|
+
lines.push(
|
|
40245
|
+
"Next: verify each unknown stdout reader in the host Monitor before you start another watcher."
|
|
40246
|
+
);
|
|
40247
|
+
} else {
|
|
40248
|
+
lines.push("Next: keep one watcher with a live output surface; do not start a duplicate.");
|
|
40249
|
+
}
|
|
40250
|
+
}
|
|
40251
|
+
lines.push(
|
|
40252
|
+
"",
|
|
40253
|
+
"Brain digest",
|
|
40254
|
+
`Checked ${safeText(report.brain.highWaterFile)} without advancing it.`
|
|
40255
|
+
);
|
|
40256
|
+
if (report.brain.digest === null) {
|
|
40257
|
+
lines.push(
|
|
40258
|
+
"No brain topic is new or changed since this principal's digest high-water.",
|
|
40259
|
+
"Next: no brain read is needed now."
|
|
40260
|
+
);
|
|
40261
|
+
} else {
|
|
40262
|
+
lines.push(report.brain.digest, "Next: read any needed topic with the command above.");
|
|
40263
|
+
}
|
|
40264
|
+
const inboxCount = report.inbox.exact ? String(report.inbox.count) : `at least ${report.inbox.count}`;
|
|
40265
|
+
lines.push(
|
|
40266
|
+
"",
|
|
40267
|
+
"Unread inbox",
|
|
40268
|
+
`Unread directed asks and notes from the same read used by the hook: ${inboxCount}.`,
|
|
40269
|
+
report.inbox.count === 0 ? "Next: no inbox action is needed now." : `Next: read them without acknowledging them first: cswarm inbox ${commonCommandArgs(report)}`,
|
|
40270
|
+
"",
|
|
40271
|
+
"Read-only check complete. No cursor, brain high-water, listener status, receipt, acknowledgement, or process was changed."
|
|
40272
|
+
);
|
|
40273
|
+
return lines.join("\n");
|
|
40274
|
+
}
|
|
40275
|
+
function resumeJson(report) {
|
|
40276
|
+
return {
|
|
40277
|
+
identity: {
|
|
40278
|
+
display_name: report.identity.displayName,
|
|
40279
|
+
principal_id: report.identity.principalId
|
|
40280
|
+
},
|
|
40281
|
+
listener: {
|
|
40282
|
+
found: report.listener.status !== null,
|
|
40283
|
+
checked_directory: report.listener.checkedDirectory,
|
|
40284
|
+
source: report.listener.source,
|
|
40285
|
+
state: report.listener.status?.state ?? null,
|
|
40286
|
+
pid: report.listener.status?.pid ?? null,
|
|
40287
|
+
running_cswarm_version: report.listener.source === "live_process" ? report.listener.status?.cswarmVersion ?? null : null,
|
|
40288
|
+
installed_cswarm_version: report.installedVersion,
|
|
40289
|
+
version_mismatch: report.listener.source === "live_process" && report.listener.status?.cswarmVersion !== null && report.listener.status?.cswarmVersion !== void 0 && report.listener.status.cswarmVersion !== report.installedVersion
|
|
40290
|
+
},
|
|
40291
|
+
notify_watchers: report.watchers.map((watcher) => ({
|
|
40292
|
+
pid: watcher.pid,
|
|
40293
|
+
matched_by: watcher.matchedBy,
|
|
40294
|
+
stdout: watcher.stdout
|
|
40295
|
+
})),
|
|
40296
|
+
brain: {
|
|
40297
|
+
high_water_file: report.brain.highWaterFile,
|
|
40298
|
+
high_water_advanced: false,
|
|
40299
|
+
digest: report.brain.digest
|
|
40300
|
+
},
|
|
40301
|
+
unread_inbox: report.inbox,
|
|
40302
|
+
read_only: true
|
|
40303
|
+
};
|
|
40304
|
+
}
|
|
40305
|
+
|
|
39646
40306
|
// src/cli.ts
|
|
39647
40307
|
var import_meta = {};
|
|
39648
40308
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -39650,6 +40310,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39650
40310
|
"agent-token-file",
|
|
39651
40311
|
"agent-token-stdin",
|
|
39652
40312
|
"all-devices",
|
|
40313
|
+
"allow-unattended",
|
|
39653
40314
|
"anon-key",
|
|
39654
40315
|
"attach",
|
|
39655
40316
|
"branch",
|
|
@@ -39699,6 +40360,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39699
40360
|
"since",
|
|
39700
40361
|
"site",
|
|
39701
40362
|
"slug",
|
|
40363
|
+
"state-dir",
|
|
39702
40364
|
"renewal-horizon-days",
|
|
39703
40365
|
"standing",
|
|
39704
40366
|
"task-id",
|
|
@@ -39718,6 +40380,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39718
40380
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
39719
40381
|
"agent-token-stdin",
|
|
39720
40382
|
"all-devices",
|
|
40383
|
+
"allow-unattended",
|
|
39721
40384
|
"confirm-standing",
|
|
39722
40385
|
"force-file-store",
|
|
39723
40386
|
"follow",
|
|
@@ -39747,8 +40410,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
39747
40410
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
39748
40411
|
];
|
|
39749
40412
|
function packageVersion() {
|
|
39750
|
-
if ("0.1.
|
|
39751
|
-
return "0.1.
|
|
40413
|
+
if ("0.1.45".length > 0) {
|
|
40414
|
+
return "0.1.45";
|
|
39752
40415
|
}
|
|
39753
40416
|
try {
|
|
39754
40417
|
const value = JSON.parse(
|
|
@@ -39865,6 +40528,7 @@ Usage:
|
|
|
39865
40528
|
cswarm target clear [--json]
|
|
39866
40529
|
cswarm status [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
39867
40530
|
cswarm whoami ${requiredAgentCredential} [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
40531
|
+
cswarm resume --agent-token-file <path> [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
|
|
39868
40532
|
cswarm members [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
39869
40533
|
cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--until <dur>] [--json]
|
|
39870
40534
|
cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--attach <path> ...] [--until <dur>] [--json] # text: 1..8000 characters
|
|
@@ -39884,7 +40548,8 @@ Usage:
|
|
|
39884
40548
|
cswarm brain get <topic> [--version <n>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
39885
40549
|
cswarm brain put <topic> [<markdown-path>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # without a path, reads Markdown from stdin
|
|
39886
40550
|
cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
39887
|
-
cswarm listen start ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--foreground] [--json]
|
|
40551
|
+
cswarm listen start ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--allow-unattended] [--foreground] [--json]
|
|
40552
|
+
cswarm listen canary ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--state-dir <path>] [--wait <seconds>] [--json]
|
|
39888
40553
|
cswarm listen status ${agentCredential2} [--url <url> --anon-key <key>] --workspace-id <uuid> [--principal-id <uuid>] [--json]
|
|
39889
40554
|
cswarm listen stop ${agentCredential2} [--url <url> --anon-key <key>] --workspace-id <uuid> [--principal-id <uuid>] [--json]
|
|
39890
40555
|
cswarm hook check [--principal-id <uuid> ...] [--cooldown <seconds>]
|
|
@@ -39923,6 +40588,7 @@ Credential selection for command/dogfood:
|
|
|
39923
40588
|
One that persists or references the credential needs the complete
|
|
39924
40589
|
JSON artifact, because it needs a field a bare secret does not carry:
|
|
39925
40590
|
whoami reads server-proven identity -- complete or bare form
|
|
40591
|
+
resume reads reconnect state -- complete or bare file form
|
|
39926
40592
|
members reads only -- either form
|
|
39927
40593
|
working-on, note, ask, reply, feed, inbox
|
|
39928
40594
|
signal command/read only -- either form
|
|
@@ -39935,6 +40601,8 @@ Credential selection for command/dogfood:
|
|
|
39935
40601
|
command, dogfood
|
|
39936
40602
|
task protocol commands -- either form
|
|
39937
40603
|
listen start persists durable state, rotates -- needs expires_at
|
|
40604
|
+
listen canary posts one self-note and selects local state -- needs
|
|
40605
|
+
principal_id; it does not renew the credential
|
|
39938
40606
|
listen status
|
|
39939
40607
|
selects the listener profile -- complete JSON or
|
|
39940
40608
|
an explicit --principal-id without a credential
|
|
@@ -39972,15 +40640,20 @@ fresh credential.
|
|
|
39972
40640
|
listen start --route worker|main|split chooses where directed messages go. worker
|
|
39973
40641
|
is the unchanged default. main queues every ask or note for the interactive session.
|
|
39974
40642
|
split queues messages whose body is longer than --defer-over <chars>; the bound is
|
|
39975
|
-
1..10000 and an equal-length message stays on the worker path.
|
|
40643
|
+
1..10000 and an equal-length message stays on the worker path. main and split require
|
|
40644
|
+
a principal-scoped Claude hook or prior hook surface. --allow-unattended accepts the
|
|
40645
|
+
risk explicitly. Run cswarm hook check
|
|
39976
40646
|
--principal-id <uuid> to surface that agent's queued messages. A bare check works only
|
|
39977
40647
|
when the state directory holds one principal. hook check has its own 3s ceiling, exits 0
|
|
39978
40648
|
on every outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
40649
|
+
listen canary posts one self-addressed note, waits at most --wait seconds (default 10),
|
|
40650
|
+
and reports accepted, claimed, queued/worker, surfaced, and observed as separate hops.
|
|
39979
40651
|
hook install claude prints principal-scoped UserPromptSubmit JSON by default. --write changes
|
|
39980
40652
|
<project>/.claude/settings.local.json, which applies only to Claude Code sessions started in
|
|
39981
40653
|
that project. Inside a git repository, the local file must be ignored. --user opts in to
|
|
39982
|
-
\${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json and warns
|
|
39983
|
-
repository-wide .claude/settings.json scope and
|
|
40654
|
+
\${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json and warns that every Claude Code session reading
|
|
40655
|
+
that directory is affected. --repo keeps the repository-wide .claude/settings.json scope and
|
|
40656
|
+
also requires an ignored file. Uninstall also
|
|
39984
40657
|
requires --write and uses the same scope selection.
|
|
39985
40658
|
|
|
39986
40659
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
@@ -40237,7 +40910,7 @@ async function stdinInviteLink() {
|
|
|
40237
40910
|
return link;
|
|
40238
40911
|
}
|
|
40239
40912
|
async function confirmationLine(prompt) {
|
|
40240
|
-
const reader = (0,
|
|
40913
|
+
const reader = (0, import_promises13.createInterface)({
|
|
40241
40914
|
input: process.stdin,
|
|
40242
40915
|
output: process.stderr,
|
|
40243
40916
|
terminal: Boolean(process.stdin.isTTY)
|
|
@@ -42168,6 +42841,100 @@ Owner: ${ownerName} (${identity.owner_user_id}).
|
|
|
42168
42841
|
`)
|
|
42169
42842
|
);
|
|
42170
42843
|
}
|
|
42844
|
+
async function runResume(args) {
|
|
42845
|
+
args.assertShape([
|
|
42846
|
+
...TARGET_FLAGS,
|
|
42847
|
+
"workspace-id",
|
|
42848
|
+
"agent-token-file",
|
|
42849
|
+
"state-dir",
|
|
42850
|
+
"json"
|
|
42851
|
+
], 1);
|
|
42852
|
+
const suppliedCredentialPath = args.optional("agent-token-file");
|
|
42853
|
+
if (suppliedCredentialPath === void 0) {
|
|
42854
|
+
throw new UsageError("cswarm resume needs --agent-token-file <path>");
|
|
42855
|
+
}
|
|
42856
|
+
if (/[\u0000-\u001f\u007f-\u009f]/.test(suppliedCredentialPath)) {
|
|
42857
|
+
throw new Error("--agent-token-file must not contain control characters");
|
|
42858
|
+
}
|
|
42859
|
+
const credentialFile = (0, import_node_path21.resolve)(suppliedCredentialPath);
|
|
42860
|
+
const cloud = await target(args);
|
|
42861
|
+
const workspaceId2 = listenerUuid(
|
|
42862
|
+
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
42863
|
+
"workspace-id"
|
|
42864
|
+
);
|
|
42865
|
+
const agent = await agentCredential(args);
|
|
42866
|
+
const report = await inspectResume({
|
|
42867
|
+
target: cloud,
|
|
42868
|
+
workspaceId: workspaceId2,
|
|
42869
|
+
credentialFile,
|
|
42870
|
+
credentialPathAliases: [.../* @__PURE__ */ new Set([suppliedCredentialPath, credentialFile])],
|
|
42871
|
+
installedVersion: CLI_BUILD_VERSION,
|
|
42872
|
+
...listenerStateDirectory(args) ? { stateDirectory: listenerStateDirectory(args) } : {}
|
|
42873
|
+
}, {
|
|
42874
|
+
readIdentity: async () => {
|
|
42875
|
+
const directory = await readAgentSignalDirectory(
|
|
42876
|
+
cloud,
|
|
42877
|
+
agent.token,
|
|
42878
|
+
workspaceId2
|
|
42879
|
+
);
|
|
42880
|
+
const identity = directory.identity;
|
|
42881
|
+
if (identity === void 0 || identity.workspace_id !== workspaceId2) {
|
|
42882
|
+
throw new Error(
|
|
42883
|
+
"the read service authenticated this credential but did not return its identity for this workspace; resume stopped without changing state"
|
|
42884
|
+
);
|
|
42885
|
+
}
|
|
42886
|
+
const principal = directory.agents.find(
|
|
42887
|
+
(candidate) => candidate.principal_id === identity.principal_id && candidate.owner_user_id === identity.owner_user_id
|
|
42888
|
+
);
|
|
42889
|
+
if (principal === void 0) {
|
|
42890
|
+
throw new Error(
|
|
42891
|
+
"the read service authenticated this credential but returned no matching live principal; resume stopped without changing state"
|
|
42892
|
+
);
|
|
42893
|
+
}
|
|
42894
|
+
if (agent.principalId !== null && agent.principalId !== identity.principal_id) {
|
|
42895
|
+
process.stderr.write(
|
|
42896
|
+
`WARNING: the credential authenticated as ${sanitizeDisplayLabel(principal.name, "Unnamed agent")} (${identity.principal_id}), but its JSON metadata names ${agent.principalId}. Resume used the authenticated identity.
|
|
42897
|
+
`
|
|
42898
|
+
);
|
|
42899
|
+
}
|
|
42900
|
+
return {
|
|
42901
|
+
displayName: sanitizeDisplayLabel(principal.name, "Unnamed agent"),
|
|
42902
|
+
principalId: identity.principal_id
|
|
42903
|
+
};
|
|
42904
|
+
},
|
|
42905
|
+
readBrainTopics: async () => brainTopicSnapshots(await listBrainRowsAsAgent(
|
|
42906
|
+
cloud,
|
|
42907
|
+
agent.token,
|
|
42908
|
+
workspaceId2
|
|
42909
|
+
)),
|
|
42910
|
+
readInboxCount: async (principalId, instanceDirectory) => {
|
|
42911
|
+
const page = await readAgentSignalPage(
|
|
42912
|
+
cloud,
|
|
42913
|
+
{ kind: "agent", token: agent.token },
|
|
42914
|
+
{
|
|
42915
|
+
workspaceId: workspaceId2,
|
|
42916
|
+
inbox: true,
|
|
42917
|
+
ascending: false,
|
|
42918
|
+
limit: 100,
|
|
42919
|
+
includeStale: false
|
|
42920
|
+
},
|
|
42921
|
+
fetch,
|
|
42922
|
+
{ tolerateMalformedRows: true, maxMalformedRows: 3 }
|
|
42923
|
+
);
|
|
42924
|
+
const candidates = page.signals.filter(
|
|
42925
|
+
(signal) => (signal.kind === "ask" || signal.kind === "note") && signal.workspace_id === workspaceId2 && signal.to_agent === principalId
|
|
42926
|
+
).map((signal) => ({ signalId: signal.id }));
|
|
42927
|
+
const unseen = await new FileHookSurfaceStore(instanceDirectory).previewUnseen(candidates);
|
|
42928
|
+
return {
|
|
42929
|
+
count: unseen.length,
|
|
42930
|
+
exact: page.rawCount < 100 && page.malformedRows === 0
|
|
42931
|
+
};
|
|
42932
|
+
}
|
|
42933
|
+
});
|
|
42934
|
+
if (args.has("json")) printJson(resumeJson(report));
|
|
42935
|
+
else process.stdout.write(`${renderResume(report)}
|
|
42936
|
+
`);
|
|
42937
|
+
}
|
|
42171
42938
|
async function runSignalRead(args, inbox) {
|
|
42172
42939
|
const notify = inbox && args.has("notify");
|
|
42173
42940
|
args.assertShape(notify ? [
|
|
@@ -42268,15 +43035,6 @@ async function runSignalRead(args, inbox) {
|
|
|
42268
43035
|
})}
|
|
42269
43036
|
`);
|
|
42270
43037
|
}
|
|
42271
|
-
async function writeMonitorLine(line) {
|
|
42272
|
-
await new Promise((resolve2, reject) => {
|
|
42273
|
-
process.stdout.write(`${line}
|
|
42274
|
-
`, (error) => {
|
|
42275
|
-
if (error) reject(error);
|
|
42276
|
-
else resolve2();
|
|
42277
|
-
});
|
|
42278
|
-
});
|
|
42279
|
-
}
|
|
42280
43038
|
async function runInboxNotifyCommand(args) {
|
|
42281
43039
|
if (!hasAgentCredential(args)) {
|
|
42282
43040
|
throw new Error(
|
|
@@ -42333,7 +43091,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
42333
43091
|
selected.selectedWorkspace,
|
|
42334
43092
|
cloud
|
|
42335
43093
|
);
|
|
42336
|
-
await
|
|
43094
|
+
await writeArrivalMonitorLine(
|
|
42337
43095
|
args.has("json") ? JSON.stringify(notification) : formatArrivalNotification(notification)
|
|
42338
43096
|
);
|
|
42339
43097
|
},
|
|
@@ -42677,10 +43435,41 @@ function listenerHostLimits(provider) {
|
|
|
42677
43435
|
}
|
|
42678
43436
|
};
|
|
42679
43437
|
}
|
|
42680
|
-
function
|
|
43438
|
+
function listenerAttendanceState(status, evidence) {
|
|
43439
|
+
const routeMode = status.routeMode ?? "worker";
|
|
43440
|
+
const pending = status.pendingForMainCount ?? 0;
|
|
43441
|
+
const connected = status.state === "ready";
|
|
43442
|
+
const attendanceState = routeMode === "worker" ? "not_required" : pending > 0 ? "unattended" : evidence.hookSurfaceAdvanced ? "attended" : "unproven";
|
|
43443
|
+
const attended = attendanceState === "attended" ? true : attendanceState === "unattended" ? false : null;
|
|
43444
|
+
const handled = pending > 0 ? false : routeMode === "worker" && status.lastAckAt !== null ? true : null;
|
|
43445
|
+
return {
|
|
43446
|
+
connected,
|
|
43447
|
+
attended,
|
|
43448
|
+
attendanceState,
|
|
43449
|
+
handled,
|
|
43450
|
+
handledState: handled === true ? "handled" : handled === false ? "not_handled" : "not_yet_measured"
|
|
43451
|
+
};
|
|
43452
|
+
}
|
|
43453
|
+
function listenerAttendanceRemedy(principalId) {
|
|
43454
|
+
return `cswarm hook install claude --principal-id ${principalId} --write, then start a fresh session. Or restart the listener with --route worker.`;
|
|
43455
|
+
}
|
|
43456
|
+
function listenerStatusJson(status, permissionMode, evidence = {
|
|
43457
|
+
pendingForMainOldestAt: null,
|
|
43458
|
+
hookSurfaceExists: false,
|
|
43459
|
+
hookSurfaceAdvanced: false
|
|
43460
|
+
}, nowMs = Date.now()) {
|
|
42681
43461
|
const mode3 = permissionMode ?? status.permissionMode;
|
|
43462
|
+
const attendance = listenerAttendanceState(status, evidence);
|
|
43463
|
+
const pending = status.pendingForMainCount ?? 0;
|
|
42682
43464
|
return {
|
|
42683
43465
|
...status,
|
|
43466
|
+
...attendance,
|
|
43467
|
+
hookSurfaceExists: evidence.hookSurfaceExists,
|
|
43468
|
+
hookSurfaceAdvanced: evidence.hookSurfaceAdvanced,
|
|
43469
|
+
pendingForMainOldestAt: evidence.pendingForMainOldestAt,
|
|
43470
|
+
pendingForMainOldestAgeMs: evidence.pendingForMainOldestAt === null ? null : Math.max(0, nowMs - Date.parse(evidence.pendingForMainOldestAt)),
|
|
43471
|
+
attendanceWarningCode: pending > 0 ? "listener_unattended_main_queue" : null,
|
|
43472
|
+
attendanceNextStep: pending > 0 ? listenerAttendanceRemedy(status.principalId) : null,
|
|
42684
43473
|
deliveryMode: status.deliveryMode ?? null,
|
|
42685
43474
|
pendingDeliveryCount: status.pendingDeliveryCount ?? null,
|
|
42686
43475
|
lastTerminalDeliveryFailureCount: status.lastTerminalDeliveryFailureCount ?? null,
|
|
@@ -42702,16 +43491,25 @@ function listenerStatusJson(status, permissionMode) {
|
|
|
42702
43491
|
host_limits: listenerHostLimits(status.provider)
|
|
42703
43492
|
};
|
|
42704
43493
|
}
|
|
42705
|
-
function renderListenerStatus(status
|
|
43494
|
+
function renderListenerStatus(status, evidence = {
|
|
43495
|
+
pendingForMainOldestAt: null,
|
|
43496
|
+
hookSurfaceExists: false,
|
|
43497
|
+
hookSurfaceAdvanced: false
|
|
43498
|
+
}, nowMs = Date.now()) {
|
|
42706
43499
|
const routeMode = status.routeMode ?? "worker";
|
|
42707
43500
|
const pendingForMainCount = status.pendingForMainCount ?? 0;
|
|
42708
43501
|
const droppedForMainCount = status.droppedForMainCount ?? 0;
|
|
43502
|
+
const unattendedCount = `${pendingForMainCount} ${pendingForMainCount === 1 ? "message is" : "messages are"} unattended`;
|
|
43503
|
+
const attendance = listenerAttendanceState(status, evidence);
|
|
42709
43504
|
const lines = [
|
|
42710
|
-
`Listener ${status.state} for agent ${status.principalId}.`,
|
|
43505
|
+
pendingForMainCount > 0 ? `Listener WARNING for agent ${status.principalId}: ${unattendedCount}.` : `Listener ${status.state} for agent ${status.principalId}.`,
|
|
43506
|
+
`CONNECTED: ${attendance.connected ? "yes" : "no"}. Transport state is ${status.state}.`,
|
|
43507
|
+
`ATTENDED: ${attendance.attendanceState === "attended" ? "yes. The session hook has surfaced messages on this host" : attendance.attendanceState === "unattended" ? "no. The main-session queue is not draining" : attendance.attendanceState === "not_required" ? "not required for the worker route" : "not yet proven on this host"}.`,
|
|
43508
|
+
`HANDLED: ${attendance.handledState === "handled" ? "yes. A delivery acknowledgement is recorded" : attendance.handledState === "not_handled" ? "no. Queued messages have not reached the session hook" : "not yet measured"}.`,
|
|
42711
43509
|
`Provider: ${status.provider}; process: ${status.pid}; started: ${status.startedAt}.`,
|
|
42712
43510
|
status.readyAt ? `Ready since: ${status.readyAt}.` : "Not ready yet.",
|
|
42713
|
-
status.lastSignalId ? `Last handled signal: ${status.lastSignalId}.` : "No signal has been handled yet.",
|
|
42714
|
-
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener error is recorded."
|
|
43511
|
+
status.lastSignalId ? pendingForMainCount > 0 ? `Last claimed and queued signal: ${status.lastSignalId}. It is not handled yet.` : routeMode === "worker" ? `Last handled signal: ${status.lastSignalId}.` : `Last listener signal: ${status.lastSignalId}. Local status does not prove its final observed receipt.` : "No signal has been handled yet.",
|
|
43512
|
+
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener process error is recorded."
|
|
42715
43513
|
];
|
|
42716
43514
|
if (status.lastErrorDetail) {
|
|
42717
43515
|
const [first, ...rest] = status.lastErrorDetail.split("\n");
|
|
@@ -42727,7 +43525,7 @@ function renderListenerStatus(status) {
|
|
|
42727
43525
|
}
|
|
42728
43526
|
if (status.providerVersion && status.providerLastMeasuredVersion) {
|
|
42729
43527
|
lines.push(
|
|
42730
|
-
`Provider version ${status.providerVersion} is newer than the last measured version ${status.providerLastMeasuredVersion}. It is unverified but allowed because the startup permission canary passed. Next: verify this provider release with CommonSwarm and update the last-measured version.`
|
|
43528
|
+
status.providerVersion === status.providerLastMeasuredVersion ? `Provider version: ${status.providerVersion} (last measured: ${status.providerLastMeasuredVersion}).` : `Provider version ${status.providerVersion} is newer than the last measured version ${status.providerLastMeasuredVersion}. It is unverified but allowed because the startup permission canary passed. Next: verify this provider release with CommonSwarm and update the last-measured version.`
|
|
42731
43529
|
);
|
|
42732
43530
|
}
|
|
42733
43531
|
if (status.deliveryMode === "durable_claim") {
|
|
@@ -42755,8 +43553,14 @@ function renderListenerStatus(status) {
|
|
|
42755
43553
|
}
|
|
42756
43554
|
if (pendingForMainCount > 0) {
|
|
42757
43555
|
lines.push(
|
|
42758
|
-
|
|
43556
|
+
`WARNING [listener_unattended_main_queue]: ${unattendedCount}. The oldest was queued ${evidence.pendingForMainOldestAt === null ? "an unknown time" : relativeAge(evidence.pendingForMainOldestAt, nowMs)}${evidence.pendingForMainOldestAt === null ? "" : ` (queued at ${evidence.pendingForMainOldestAt})`}.`
|
|
42759
43557
|
);
|
|
43558
|
+
lines.push(`Next: ${listenerAttendanceRemedy(status.principalId)}`);
|
|
43559
|
+
if (status.state === "stopped" || status.state === "failed") {
|
|
43560
|
+
lines.push(
|
|
43561
|
+
`${pendingForMainCount} ${pendingForMainCount === 1 ? "message is" : "messages are"} also stranded because this listener is not running. Restart it by piping the same agent credential into: ${listenerRestartCommand(status)}`
|
|
43562
|
+
);
|
|
43563
|
+
}
|
|
42760
43564
|
}
|
|
42761
43565
|
}
|
|
42762
43566
|
if (status.lastTerminalDeliveryFailureCount !== null && status.lastTerminalDeliveryFailureCount > 0) {
|
|
@@ -42776,16 +43580,30 @@ async function unsurfacedPendingMainStats(instanceDirectory, fallback) {
|
|
|
42776
43580
|
const queue = new FilePendingMainQueue(instanceDirectory);
|
|
42777
43581
|
const pending = await queue.read();
|
|
42778
43582
|
const stats = await queue.stats();
|
|
42779
|
-
const
|
|
43583
|
+
const surface = new FileHookSurfaceStore(instanceDirectory);
|
|
43584
|
+
const staged = await surface.stage(
|
|
42780
43585
|
pending,
|
|
42781
43586
|
stats.droppedCount
|
|
42782
43587
|
);
|
|
42783
|
-
|
|
43588
|
+
const hook = await surface.evidence();
|
|
43589
|
+
const oldestAt = staged.unseen.reduce((oldest, item) => oldest === null || Date.parse(item.queuedAt) < Date.parse(oldest) ? item.queuedAt : oldest, null);
|
|
43590
|
+
return {
|
|
43591
|
+
count: staged.unseen.length,
|
|
43592
|
+
droppedCount: stats.droppedCount,
|
|
43593
|
+
oldestAt,
|
|
43594
|
+
hookSurfaceExists: hook.exists,
|
|
43595
|
+
hookSurfaceAdvanced: hook.surfacedSignalIds.length > 0
|
|
43596
|
+
};
|
|
42784
43597
|
} catch {
|
|
42785
|
-
return
|
|
43598
|
+
return {
|
|
43599
|
+
...fallback,
|
|
43600
|
+
oldestAt: null,
|
|
43601
|
+
hookSurfaceExists: false,
|
|
43602
|
+
hookSurfaceAdvanced: false
|
|
43603
|
+
};
|
|
42786
43604
|
}
|
|
42787
43605
|
}
|
|
42788
|
-
function listenerFailureMessage(code, provider) {
|
|
43606
|
+
function listenerFailureMessage(code, provider, detail) {
|
|
42789
43607
|
if (code === "version_below_floor") {
|
|
42790
43608
|
if (provider === "codex") {
|
|
42791
43609
|
return "the Codex listener requires codex-acp 1.1.9 or newer; update the bridge, then retry";
|
|
@@ -42804,6 +43622,9 @@ function listenerFailureMessage(code, provider) {
|
|
|
42804
43622
|
if (code === "version_refused") {
|
|
42805
43623
|
return `the ${provider ?? "provider"} version check could not run, so startup stopped. Next: run the provider's --version command and fix that error, then retry`;
|
|
42806
43624
|
}
|
|
43625
|
+
if (code === "executable_not_bridge" && provider === "codex") {
|
|
43626
|
+
return "this is the Codex CLI; --codex-executable takes the codex-acp bridge (npm i -g @agentclientprotocol/codex-acp)";
|
|
43627
|
+
}
|
|
42807
43628
|
if (code === "executable_missing" && provider === "claude") {
|
|
42808
43629
|
return "claude-agent-acp is not installed; run npm install -g @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2), then retry";
|
|
42809
43630
|
}
|
|
@@ -42845,7 +43666,8 @@ function listenerFailureMessage(code, provider) {
|
|
|
42845
43666
|
return "the Claude bridge did not complete the ACP permission canary; the startup canary ran, but no workspace signal prompt was delivered. Confirm Claude Code keychain/OAuth sign-in, then retry";
|
|
42846
43667
|
}
|
|
42847
43668
|
if (provider === "codex") {
|
|
42848
|
-
|
|
43669
|
+
const recorded = detail?.trim();
|
|
43670
|
+
return recorded ? `the Codex bridge did not complete the read-only ACP permission canary; no workspace signal prompt was delivered. Recorded reason: ${JSON.stringify(recorded)}` : "the Codex bridge did not complete the read-only ACP permission canary; no workspace signal prompt was delivered. The recorded reason was unavailable. Next: run cswarm listen status, then retry";
|
|
42849
43671
|
}
|
|
42850
43672
|
if (provider === "grok") {
|
|
42851
43673
|
return "the Grok bridge did not complete the ACP permission canary; no workspace signal prompt was delivered. The local cswarm listen status output includes the final error detail; read it, then retry";
|
|
@@ -43077,6 +43899,7 @@ async function runConfiguredListener(options) {
|
|
|
43077
43899
|
workspaceId: options.workspaceId,
|
|
43078
43900
|
principalId: options.principalId,
|
|
43079
43901
|
provider: options.provider,
|
|
43902
|
+
cswarmVersion: CLI_BUILD_VERSION,
|
|
43080
43903
|
permissionMode: options.permissionMode,
|
|
43081
43904
|
routeMode,
|
|
43082
43905
|
deferOverChars,
|
|
@@ -43159,6 +43982,7 @@ async function runListenStart(args) {
|
|
|
43159
43982
|
"turn-budget",
|
|
43160
43983
|
"route",
|
|
43161
43984
|
"defer-over",
|
|
43985
|
+
"allow-unattended",
|
|
43162
43986
|
"foreground",
|
|
43163
43987
|
"json"
|
|
43164
43988
|
], 2);
|
|
@@ -43198,6 +44022,9 @@ async function runListenStart(args) {
|
|
|
43198
44022
|
`a listener is already ${existing.state} for agent ${principalId}`
|
|
43199
44023
|
);
|
|
43200
44024
|
}
|
|
44025
|
+
if (routing.routeMode !== "worker" && !args.has("allow-unattended") && !await listenerHasAttendanceSurface(paths.instanceDirectory, cwd, principalId)) {
|
|
44026
|
+
throw new ListenerUnattendedRefusedError(principalId);
|
|
44027
|
+
}
|
|
43201
44028
|
let status;
|
|
43202
44029
|
if (args.has("foreground")) {
|
|
43203
44030
|
status = await runConfiguredListener({
|
|
@@ -43282,16 +44109,27 @@ async function runListenStart(args) {
|
|
|
43282
44109
|
});
|
|
43283
44110
|
} catch (error) {
|
|
43284
44111
|
if (error instanceof ListenerStartupError) {
|
|
43285
|
-
|
|
44112
|
+
const failedStatus = await effectiveListenerStatus(paths).catch(() => null);
|
|
44113
|
+
const detail = failedStatus?.lastErrorCode === error.code ? failedStatus.lastErrorDetail : null;
|
|
44114
|
+
throw new Error(listenerFailureMessage(error.code, provider, detail));
|
|
43286
44115
|
}
|
|
43287
44116
|
throw error;
|
|
43288
44117
|
}
|
|
43289
44118
|
}
|
|
43290
44119
|
if (status.state === "failed") {
|
|
43291
44120
|
throw new Error(
|
|
43292
|
-
listenerFailureMessage(
|
|
44121
|
+
listenerFailureMessage(
|
|
44122
|
+
status.lastErrorCode ?? "unknown_error",
|
|
44123
|
+
provider,
|
|
44124
|
+
status.lastErrorDetail
|
|
44125
|
+
)
|
|
43293
44126
|
);
|
|
43294
44127
|
}
|
|
44128
|
+
let attendanceEvidence = {
|
|
44129
|
+
pendingForMainOldestAt: null,
|
|
44130
|
+
hookSurfaceExists: false,
|
|
44131
|
+
hookSurfaceAdvanced: false
|
|
44132
|
+
};
|
|
43295
44133
|
if ((status.routeMode ?? "worker") !== "worker") {
|
|
43296
44134
|
const recordedPending = status.pendingForMainCount ?? 0;
|
|
43297
44135
|
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
@@ -43304,9 +44142,14 @@ async function runListenStart(args) {
|
|
|
43304
44142
|
pendingForMainCount: queueStats.count,
|
|
43305
44143
|
droppedForMainCount: queueStats.droppedCount
|
|
43306
44144
|
};
|
|
44145
|
+
attendanceEvidence = {
|
|
44146
|
+
pendingForMainOldestAt: queueStats.oldestAt,
|
|
44147
|
+
hookSurfaceExists: queueStats.hookSurfaceExists,
|
|
44148
|
+
hookSurfaceAdvanced: queueStats.hookSurfaceAdvanced
|
|
44149
|
+
};
|
|
43307
44150
|
}
|
|
43308
44151
|
if (args.has("json")) {
|
|
43309
|
-
printJson(listenerStatusJson(status, permissionMode));
|
|
44152
|
+
printJson(listenerStatusJson(status, permissionMode, attendanceEvidence));
|
|
43310
44153
|
return;
|
|
43311
44154
|
}
|
|
43312
44155
|
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.
|
|
@@ -43318,8 +44161,8 @@ async function runListenStart(args) {
|
|
|
43318
44161
|
` : `The Grok worker uses your selected cwd and local Grok configuration, including user and cmux hooks. ${workerAudience}
|
|
43319
44162
|
`;
|
|
43320
44163
|
process.stdout.write(
|
|
43321
|
-
`${args.has("foreground") ? "Listener stopped." : "Listener is ready and will keep receiving after this command exits."}
|
|
43322
|
-
${renderListenerStatus(status)}
|
|
44164
|
+
`${args.has("foreground") ? "Listener stopped." : (status.pendingForMainCount ?? 0) > 0 ? "Listener transport is connected, but queued messages are unattended." : "Listener is ready and will keep receiving after this command exits."}
|
|
44165
|
+
${renderListenerStatus(status, attendanceEvidence)}
|
|
43323
44166
|
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.
|
|
43324
44167
|
The short credential rotates while this process remains alive and secure local state is available. Run cswarm whoami with this credential to see whether its grant is timeboxed or standing.
|
|
43325
44168
|
` + routingNote + hostNote + `Use listen status/stop with the same agent credential, --workspace-id ${workspaceId2}, and the same Cloud target. --principal-id ${principalId} remains available when no credential is supplied.
|
|
@@ -43438,6 +44281,11 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
43438
44281
|
}
|
|
43439
44282
|
return;
|
|
43440
44283
|
}
|
|
44284
|
+
let attendanceEvidence = {
|
|
44285
|
+
pendingForMainOldestAt: null,
|
|
44286
|
+
hookSurfaceExists: false,
|
|
44287
|
+
hookSurfaceAdvanced: false
|
|
44288
|
+
};
|
|
43441
44289
|
if ((status.routeMode ?? "worker") !== "worker") {
|
|
43442
44290
|
const recordedPending = status.pendingForMainCount ?? 0;
|
|
43443
44291
|
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
@@ -43450,14 +44298,76 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
43450
44298
|
pendingForMainCount: queueStats.count,
|
|
43451
44299
|
droppedForMainCount: queueStats.droppedCount
|
|
43452
44300
|
};
|
|
44301
|
+
attendanceEvidence = {
|
|
44302
|
+
pendingForMainOldestAt: queueStats.oldestAt,
|
|
44303
|
+
hookSurfaceExists: queueStats.hookSurfaceExists,
|
|
44304
|
+
hookSurfaceAdvanced: queueStats.hookSurfaceAdvanced
|
|
44305
|
+
};
|
|
43453
44306
|
}
|
|
43454
44307
|
if (args.has("json")) {
|
|
43455
|
-
printJson(listenerStatusJson(status));
|
|
44308
|
+
printJson(listenerStatusJson(status, void 0, attendanceEvidence));
|
|
43456
44309
|
} else {
|
|
43457
|
-
process.stdout.write(`${renderListenerStatus(status)}
|
|
44310
|
+
process.stdout.write(`${renderListenerStatus(status, attendanceEvidence)}
|
|
43458
44311
|
`);
|
|
43459
44312
|
}
|
|
43460
44313
|
}
|
|
44314
|
+
async function runListenCanary(args) {
|
|
44315
|
+
args.assertShape([
|
|
44316
|
+
...TARGET_FLAGS,
|
|
44317
|
+
...CREDENTIAL_FLAGS,
|
|
44318
|
+
"workspace-id",
|
|
44319
|
+
"state-dir",
|
|
44320
|
+
"wait",
|
|
44321
|
+
"json"
|
|
44322
|
+
], 2);
|
|
44323
|
+
if (!hasAgentCredential(args)) {
|
|
44324
|
+
throw new Error(
|
|
44325
|
+
"listen canary requires --agent-token-file or --agent-token-stdin; credentials are never accepted on argv"
|
|
44326
|
+
);
|
|
44327
|
+
}
|
|
44328
|
+
const cloud = await target(args);
|
|
44329
|
+
const workspaceId2 = listenerUuid(
|
|
44330
|
+
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
44331
|
+
"workspace-id"
|
|
44332
|
+
);
|
|
44333
|
+
const agent = await agentCredential(args);
|
|
44334
|
+
if (agent.principalId === null) {
|
|
44335
|
+
throw new Error(
|
|
44336
|
+
"listen canary needs the complete JSON agent credential so it can address the agent and select its listener state"
|
|
44337
|
+
);
|
|
44338
|
+
}
|
|
44339
|
+
const principalId = listenerUuid(agent.principalId, "principal-id");
|
|
44340
|
+
const stateDirectory2 = listenerStateDirectory(args);
|
|
44341
|
+
const paths = listenerPaths({
|
|
44342
|
+
profileId: cloud.profileId,
|
|
44343
|
+
workspaceId: workspaceId2,
|
|
44344
|
+
principalId,
|
|
44345
|
+
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {}
|
|
44346
|
+
});
|
|
44347
|
+
const waitMs = parseWaitSeconds(args.optional("wait") ?? "10") * 1e3;
|
|
44348
|
+
const result = await runListenerAttendanceCanary({
|
|
44349
|
+
target: cloud,
|
|
44350
|
+
workspaceId: workspaceId2,
|
|
44351
|
+
principalId,
|
|
44352
|
+
paths,
|
|
44353
|
+
waitMs,
|
|
44354
|
+
// Canary must remain read-only apart from its one self-note. It therefore
|
|
44355
|
+
// uses the presented token and never enters the renewal/mint path.
|
|
44356
|
+
credential: async () => agent.token
|
|
44357
|
+
});
|
|
44358
|
+
if (args.has("json")) {
|
|
44359
|
+
printJson({
|
|
44360
|
+
workspaceId: workspaceId2,
|
|
44361
|
+
principalId,
|
|
44362
|
+
...result
|
|
44363
|
+
});
|
|
44364
|
+
return;
|
|
44365
|
+
}
|
|
44366
|
+
process.stdout.write(
|
|
44367
|
+
`${renderListenerAttendanceCanary(result, workspaceId2, principalId)}
|
|
44368
|
+
`
|
|
44369
|
+
);
|
|
44370
|
+
}
|
|
43461
44371
|
async function runListen(args) {
|
|
43462
44372
|
const command2 = args.positionals[1];
|
|
43463
44373
|
if (command2 === "start") {
|
|
@@ -43468,7 +44378,11 @@ async function runListen(args) {
|
|
|
43468
44378
|
await runListenStatusOrStop(args, command2);
|
|
43469
44379
|
return;
|
|
43470
44380
|
}
|
|
43471
|
-
|
|
44381
|
+
if (command2 === "canary") {
|
|
44382
|
+
await runListenCanary(args);
|
|
44383
|
+
return;
|
|
44384
|
+
}
|
|
44385
|
+
throw new UsageError("listen requires start, status, stop, or canary");
|
|
43472
44386
|
}
|
|
43473
44387
|
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
43474
44388
|
function scopedClaudeHookCommand(principalId) {
|
|
@@ -43477,6 +44391,46 @@ function scopedClaudeHookCommand(principalId) {
|
|
|
43477
44391
|
function isCommonSwarmClaudeHook(value) {
|
|
43478
44392
|
return typeof value === "string" && (value === CLAUDE_HOOK_COMMAND || /^cswarm hook check --principal-id [0-9a-f-]{36}$/.test(value));
|
|
43479
44393
|
}
|
|
44394
|
+
var ListenerUnattendedRefusedError = class extends Error {
|
|
44395
|
+
code = "listen_unattended_refused";
|
|
44396
|
+
constructor(principalId) {
|
|
44397
|
+
super(
|
|
44398
|
+
`listen_unattended_refused: --route main and --route split need an attendance surface for agent ${principalId}. Next: cswarm hook install claude --principal-id ${principalId} --write, then start a fresh session. Or use --route worker. Use --allow-unattended only when you accept a queue that may not wake a session.`
|
|
44399
|
+
);
|
|
44400
|
+
this.name = "ListenerUnattendedRefusedError";
|
|
44401
|
+
}
|
|
44402
|
+
};
|
|
44403
|
+
function settingsHaveScopedClaudeHook(settings, principalId) {
|
|
44404
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
|
|
44405
|
+
return false;
|
|
44406
|
+
}
|
|
44407
|
+
const promptHooks = settings.hooks.UserPromptSubmit;
|
|
44408
|
+
if (!Array.isArray(promptHooks)) return false;
|
|
44409
|
+
const expected = scopedClaudeHookCommand(principalId);
|
|
44410
|
+
return promptHooks.some((group) => {
|
|
44411
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) return false;
|
|
44412
|
+
const hooks = group.hooks;
|
|
44413
|
+
return Array.isArray(hooks) && hooks.some(
|
|
44414
|
+
(hook) => hook !== null && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === expected
|
|
44415
|
+
);
|
|
44416
|
+
});
|
|
44417
|
+
}
|
|
44418
|
+
async function listenerHasAttendanceSurface(instanceDirectory, cwd, principalId) {
|
|
44419
|
+
const surface = await new FileHookSurfaceStore(instanceDirectory).evidence();
|
|
44420
|
+
if (surface.exists) return true;
|
|
44421
|
+
const repositoryRoot = gitRepositoryRoot(cwd) ?? cwd;
|
|
44422
|
+
const settingsPaths = /* @__PURE__ */ new Set([
|
|
44423
|
+
(0, import_node_path21.join)(repositoryRoot, CLAUDE_PROJECT_SETTINGS_IGNORE_LINE),
|
|
44424
|
+
(0, import_node_path21.join)(repositoryRoot, CLAUDE_REPO_SETTINGS_IGNORE_LINE),
|
|
44425
|
+
userClaudeSettingsTarget().path
|
|
44426
|
+
]);
|
|
44427
|
+
for (const path of settingsPaths) {
|
|
44428
|
+
if (settingsHaveScopedClaudeHook(readClaudeSettings(path), principalId)) {
|
|
44429
|
+
return true;
|
|
44430
|
+
}
|
|
44431
|
+
}
|
|
44432
|
+
return false;
|
|
44433
|
+
}
|
|
43480
44434
|
function claudeUserPromptHookSnippet(principalId) {
|
|
43481
44435
|
return {
|
|
43482
44436
|
hooks: {
|
|
@@ -43495,7 +44449,9 @@ function claudeUserPromptHookSnippet(principalId) {
|
|
|
43495
44449
|
}
|
|
43496
44450
|
var CLAUDE_PROJECT_SETTINGS_IGNORE_LINE = ".claude/settings.local.json";
|
|
43497
44451
|
var CLAUDE_REPO_SETTINGS_IGNORE_LINE = ".claude/settings.json";
|
|
43498
|
-
|
|
44452
|
+
function claudeUserScopeWarning(settingsPath) {
|
|
44453
|
+
return `Warning: --user scope writes settings to ${(0, import_node_path21.dirname)(settingsPath)} and applies to every Claude Code session that reads that directory.`;
|
|
44454
|
+
}
|
|
43499
44455
|
function userClaudeSettingsTarget() {
|
|
43500
44456
|
const configured = process.env.CLAUDE_CONFIG_DIR;
|
|
43501
44457
|
const directory = configured && configured.length > 0 ? (0, import_node_path21.resolve)(configured) : (0, import_node_path21.join)((0, import_node_os10.homedir)(), ".claude");
|
|
@@ -43506,7 +44462,7 @@ function userClaudeSettingsTarget() {
|
|
|
43506
44462
|
};
|
|
43507
44463
|
}
|
|
43508
44464
|
function gitRepositoryRoot(cwd) {
|
|
43509
|
-
const result = (0,
|
|
44465
|
+
const result = (0, import_node_child_process9.spawnSync)(
|
|
43510
44466
|
"git",
|
|
43511
44467
|
["-C", cwd, "rev-parse", "--show-toplevel"],
|
|
43512
44468
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -43526,12 +44482,12 @@ function projectClaudeSettingsTarget(scope, ignoreLine) {
|
|
|
43526
44482
|
const base = root ?? process.cwd();
|
|
43527
44483
|
const path = (0, import_node_path21.join)(base, ignoreLine);
|
|
43528
44484
|
if (root === null) return { path, scope, projectRoot: base };
|
|
43529
|
-
const tracked = (0,
|
|
44485
|
+
const tracked = (0, import_node_child_process9.spawnSync)(
|
|
43530
44486
|
"git",
|
|
43531
44487
|
["-C", root, "ls-files", "--error-unmatch", "--", ignoreLine],
|
|
43532
44488
|
{ encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
|
|
43533
44489
|
);
|
|
43534
|
-
const ignored = (0,
|
|
44490
|
+
const ignored = (0, import_node_child_process9.spawnSync)(
|
|
43535
44491
|
"git",
|
|
43536
44492
|
["-C", root, "check-ignore", "--quiet", "--", ignoreLine],
|
|
43537
44493
|
{ encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
|
|
@@ -43713,7 +44669,7 @@ async function runHook(args) {
|
|
|
43713
44669
|
const settings = readClaudeSettings(path);
|
|
43714
44670
|
const updated = command2 === "install" ? installClaudeHook(settings, principalId) : uninstallClaudeHook(settings);
|
|
43715
44671
|
if (target2.scope === "user") {
|
|
43716
|
-
process.stdout.write(`${
|
|
44672
|
+
process.stdout.write(`${claudeUserScopeWarning(path)}
|
|
43717
44673
|
`);
|
|
43718
44674
|
}
|
|
43719
44675
|
(0, import_node_fs7.mkdirSync)((0, import_node_path21.dirname)(path), { recursive: true });
|
|
@@ -44280,7 +45236,7 @@ async function runSeed(args) {
|
|
|
44280
45236
|
if (!tokenOut || !(0, import_node_path21.isAbsolute)(tokenOut)) {
|
|
44281
45237
|
throw new Error("SEED_TOKEN_OUT must be an absolute path");
|
|
44282
45238
|
}
|
|
44283
|
-
const tokenFile = await (0,
|
|
45239
|
+
const tokenFile = await (0, import_promises12.open)(tokenOut, "wx", 384).catch((error) => {
|
|
44284
45240
|
if (error.code === "EEXIST") {
|
|
44285
45241
|
throw new Error("SEED_TOKEN_OUT already exists; refusing to overwrite it");
|
|
44286
45242
|
}
|
|
@@ -44319,7 +45275,7 @@ async function runSeed(args) {
|
|
|
44319
45275
|
tokenWritten = true;
|
|
44320
45276
|
}
|
|
44321
45277
|
await tokenFile.close();
|
|
44322
|
-
if (!tokenWritten) await (0,
|
|
45278
|
+
if (!tokenWritten) await (0, import_promises12.unlink)(tokenOut);
|
|
44323
45279
|
process.stdout.write(`${JSON.stringify({
|
|
44324
45280
|
userId: result.userId,
|
|
44325
45281
|
membershipRole: result.membershipRole,
|
|
@@ -44332,7 +45288,7 @@ async function runSeed(args) {
|
|
|
44332
45288
|
`);
|
|
44333
45289
|
} catch (error) {
|
|
44334
45290
|
await tokenFile.close().catch(() => void 0);
|
|
44335
|
-
if (!tokenWritten) await (0,
|
|
45291
|
+
if (!tokenWritten) await (0, import_promises12.unlink)(tokenOut).catch(() => void 0);
|
|
44336
45292
|
throw error;
|
|
44337
45293
|
}
|
|
44338
45294
|
}
|
|
@@ -44433,6 +45389,10 @@ async function main() {
|
|
|
44433
45389
|
await runWhoami(args);
|
|
44434
45390
|
return;
|
|
44435
45391
|
}
|
|
45392
|
+
if (verb === "resume") {
|
|
45393
|
+
await runResume(args);
|
|
45394
|
+
return;
|
|
45395
|
+
}
|
|
44436
45396
|
if (verb === "feedback") {
|
|
44437
45397
|
await runFeedback(args);
|
|
44438
45398
|
return;
|
|
@@ -44518,6 +45478,7 @@ function markRestartable(error) {
|
|
|
44518
45478
|
return error;
|
|
44519
45479
|
}
|
|
44520
45480
|
function exitCodeFor(error) {
|
|
45481
|
+
if (error instanceof NotifyStdoutClosedError) return EXIT_NOTIFY_ORPHANED;
|
|
44521
45482
|
return error instanceof Error ? restartableExit.get(error) ?? 1 : 1;
|
|
44522
45483
|
}
|
|
44523
45484
|
function safeParagraph(message) {
|
|
@@ -44574,6 +45535,7 @@ ${usage()}
|
|
|
44574
45535
|
// Annotate the CommonJS export names for ESM import in node:
|
|
44575
45536
|
0 && (module.exports = {
|
|
44576
45537
|
EXIT_RESTARTABLE,
|
|
45538
|
+
ListenerUnattendedRefusedError,
|
|
44577
45539
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
44578
45540
|
clampTurnBudgetToCredential,
|
|
44579
45541
|
claudeUserPromptHookSnippet,
|