commonswarm 0.1.44 → 0.1.46
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 +2716 -302
- 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,
|
|
@@ -13510,6 +13511,7 @@ __export(cli_exports, {
|
|
|
13510
13511
|
listenerFailureMessage: () => listenerFailureMessage,
|
|
13511
13512
|
listenerHostLimits: () => listenerHostLimits,
|
|
13512
13513
|
listenerPermissionMode: () => listenerPermissionMode,
|
|
13514
|
+
listenerProviderInstallEvidence: () => listenerProviderInstallEvidence,
|
|
13513
13515
|
listenerRouteConfiguration: () => listenerRouteConfiguration,
|
|
13514
13516
|
listenerStatusJson: () => listenerStatusJson,
|
|
13515
13517
|
renderListenerStatus: () => renderListenerStatus,
|
|
@@ -13520,13 +13522,13 @@ __export(cli_exports, {
|
|
|
13520
13522
|
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer
|
|
13521
13523
|
});
|
|
13522
13524
|
module.exports = __toCommonJS(cli_exports);
|
|
13523
|
-
var
|
|
13524
|
-
var
|
|
13525
|
+
var import_node_crypto21 = require("node:crypto");
|
|
13526
|
+
var import_node_child_process9 = require("node:child_process");
|
|
13525
13527
|
var import_node_fs7 = require("node:fs");
|
|
13526
|
-
var
|
|
13528
|
+
var import_promises12 = require("node:fs/promises");
|
|
13527
13529
|
var import_node_os10 = require("node:os");
|
|
13528
13530
|
var import_node_path21 = require("node:path");
|
|
13529
|
-
var
|
|
13531
|
+
var import_promises13 = require("node:readline/promises");
|
|
13530
13532
|
|
|
13531
13533
|
// src/cloud/auth.ts
|
|
13532
13534
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -22900,9 +22902,9 @@ var FileCommandRefused = class extends Error {
|
|
|
22900
22902
|
};
|
|
22901
22903
|
var FileTransportError = class extends Error {
|
|
22902
22904
|
/**
|
|
22903
|
-
* True when
|
|
22904
|
-
*
|
|
22905
|
-
*
|
|
22905
|
+
* True when the request did not complete: no response arrived, or an
|
|
22906
|
+
* idempotent read's body stalled. Reads may retry; writes reuse the same ids
|
|
22907
|
+
* because their outcome is unknown. A received refusal is never retried.
|
|
22906
22908
|
*/
|
|
22907
22909
|
constructor(message, noResponse = false) {
|
|
22908
22910
|
super(message);
|
|
@@ -23050,7 +23052,7 @@ async function getObject(target2, downloadPath, fetcher = fetch, options = {}) {
|
|
|
23050
23052
|
options
|
|
23051
23053
|
));
|
|
23052
23054
|
} catch {
|
|
23053
|
-
throw new FileTransportError("the download
|
|
23055
|
+
throw new FileTransportError("the download did not complete", true);
|
|
23054
23056
|
}
|
|
23055
23057
|
if (!response.ok || body === null) {
|
|
23056
23058
|
throw new FileTransportError(
|
|
@@ -23131,7 +23133,7 @@ async function listFilesAsAgent(target2, credential, workspaceId2, fetcher = fet
|
|
|
23131
23133
|
options
|
|
23132
23134
|
));
|
|
23133
23135
|
} catch {
|
|
23134
|
-
throw new FileTransportError("file list
|
|
23136
|
+
throw new FileTransportError("the file list did not complete", true);
|
|
23135
23137
|
}
|
|
23136
23138
|
if (!response.ok) {
|
|
23137
23139
|
throw new FileCommandRefused(
|
|
@@ -23176,7 +23178,7 @@ async function listFilesAsHuman(target2, accessToken, workspaceId2, fetcher = fe
|
|
|
23176
23178
|
options
|
|
23177
23179
|
));
|
|
23178
23180
|
} catch {
|
|
23179
|
-
throw new FileTransportError("file list
|
|
23181
|
+
throw new FileTransportError("the file list did not complete", true);
|
|
23180
23182
|
}
|
|
23181
23183
|
if (!response.ok) {
|
|
23182
23184
|
throw new FileCommandRefused(
|
|
@@ -23366,8 +23368,7 @@ function assertOwnedByCurrentUser(uid2) {
|
|
|
23366
23368
|
throw new Error("credential path is not owned by the current user");
|
|
23367
23369
|
}
|
|
23368
23370
|
}
|
|
23369
|
-
async function
|
|
23370
|
-
let created = false;
|
|
23371
|
+
async function existingSecureDirectory(path) {
|
|
23371
23372
|
try {
|
|
23372
23373
|
const info = await (0, import_promises.lstat)(path);
|
|
23373
23374
|
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
@@ -23380,20 +23381,22 @@ async function secureDirectory(path) {
|
|
|
23380
23381
|
);
|
|
23381
23382
|
}
|
|
23382
23383
|
} catch (error) {
|
|
23383
|
-
if (error.code
|
|
23384
|
-
|
|
23385
|
-
await (0, import_promises.chmod)(path, 448);
|
|
23386
|
-
created = true;
|
|
23384
|
+
if (error.code === "ENOENT") return false;
|
|
23385
|
+
throw error;
|
|
23387
23386
|
}
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
}
|
|
23387
|
+
return true;
|
|
23388
|
+
}
|
|
23389
|
+
async function secureDirectory(path) {
|
|
23390
|
+
if (await existingSecureDirectory(path)) return;
|
|
23391
|
+
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
23392
|
+
await (0, import_promises.chmod)(path, 448);
|
|
23393
|
+
const info = await (0, import_promises.lstat)(path);
|
|
23394
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
23395
|
+
throw new Error(`credential directory is not a real directory: ${path}`);
|
|
23396
|
+
}
|
|
23397
|
+
assertOwnedByCurrentUser(info.uid);
|
|
23398
|
+
if (mode(info.mode) !== 448) {
|
|
23399
|
+
throw new Error(`credential directory could not be secured to mode 0700: ${path}`);
|
|
23397
23400
|
}
|
|
23398
23401
|
}
|
|
23399
23402
|
async function ensureSecureStateDirectory(path) {
|
|
@@ -23555,6 +23558,20 @@ async function readSecureJsonFile(path, maxBytes) {
|
|
|
23555
23558
|
throw error;
|
|
23556
23559
|
}
|
|
23557
23560
|
}
|
|
23561
|
+
async function readSecureJsonFileIfPresent(path, maxBytes) {
|
|
23562
|
+
if (!await existingSecureDirectory((0, import_node_path.dirname)(path))) return null;
|
|
23563
|
+
try {
|
|
23564
|
+
await secureCredentialFile(path);
|
|
23565
|
+
const raw = await (0, import_promises.readFile)(path, "utf8");
|
|
23566
|
+
if (Buffer.byteLength(raw, "utf8") > maxBytes) {
|
|
23567
|
+
throw new Error("stored record is larger than this store accepts");
|
|
23568
|
+
}
|
|
23569
|
+
return raw;
|
|
23570
|
+
} catch (error) {
|
|
23571
|
+
if (error.code === "ENOENT") return null;
|
|
23572
|
+
throw error;
|
|
23573
|
+
}
|
|
23574
|
+
}
|
|
23558
23575
|
async function deleteSecureJsonFile(path) {
|
|
23559
23576
|
await secureDirectory((0, import_node_path.dirname)(path));
|
|
23560
23577
|
try {
|
|
@@ -23832,8 +23849,7 @@ function parseStoredCurrentTarget(raw) {
|
|
|
23832
23849
|
throw new Error("stored current target is malformed");
|
|
23833
23850
|
}
|
|
23834
23851
|
const record = value;
|
|
23835
|
-
|
|
23836
|
-
if (keys.length !== 3 || keys[0] !== "anonKey" || keys[1] !== "url" || keys[2] !== "version" || record.version !== 1 || typeof record.url !== "string" || typeof record.anonKey !== "string") {
|
|
23852
|
+
if (record.version !== 1 || typeof record.url !== "string" || typeof record.anonKey !== "string") {
|
|
23837
23853
|
throw new Error("stored current target is malformed");
|
|
23838
23854
|
}
|
|
23839
23855
|
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();
|
|
@@ -28734,10 +28750,19 @@ var SIGNAL_BODY_DISPLAY_MAX = 8e3;
|
|
|
28734
28750
|
var SIGNAL_ABOUT_DISPLAY_MAX = 500;
|
|
28735
28751
|
var SIGNAL_READ_TIMEOUT_MS = 3e4;
|
|
28736
28752
|
var SignalReadTimeoutError = class extends Error {
|
|
28737
|
-
constructor(message = "signal read timed out") {
|
|
28753
|
+
constructor(message = "signal read timed out", phase = "response") {
|
|
28738
28754
|
super(message);
|
|
28755
|
+
this.phase = phase;
|
|
28739
28756
|
this.name = "SignalReadTimeoutError";
|
|
28740
28757
|
}
|
|
28758
|
+
phase;
|
|
28759
|
+
};
|
|
28760
|
+
var SignalHostPortsExhaustedError = class extends Error {
|
|
28761
|
+
code = "EADDRNOTAVAIL";
|
|
28762
|
+
constructor() {
|
|
28763
|
+
super("the host could not allocate an outbound source port");
|
|
28764
|
+
this.name = "SignalHostPortsExhaustedError";
|
|
28765
|
+
}
|
|
28741
28766
|
};
|
|
28742
28767
|
var SIGNAL_WAIT_MIN_SECONDS = 1;
|
|
28743
28768
|
var SIGNAL_WAIT_MAX_SECONDS = 300;
|
|
@@ -28776,9 +28801,17 @@ var plainHttpRetryAfterMs = /* @__PURE__ */ new WeakMap();
|
|
|
28776
28801
|
var plainHttpStatus = /* @__PURE__ */ new WeakMap();
|
|
28777
28802
|
var plainHttpEnvelope = /* @__PURE__ */ new WeakMap();
|
|
28778
28803
|
var plainTransportErrors = /* @__PURE__ */ new WeakSet();
|
|
28779
|
-
|
|
28804
|
+
var plainTransportFailureCodes = /* @__PURE__ */ new WeakMap();
|
|
28805
|
+
var plainMalformedErrors = /* @__PURE__ */ new WeakSet();
|
|
28806
|
+
function plainTransportError(failureCode2 = "no_response") {
|
|
28780
28807
|
const error = new Error("signal read could not reach the cloud service");
|
|
28781
28808
|
plainTransportErrors.add(error);
|
|
28809
|
+
plainTransportFailureCodes.set(error, failureCode2);
|
|
28810
|
+
return error;
|
|
28811
|
+
}
|
|
28812
|
+
function plainMalformedError(message) {
|
|
28813
|
+
const error = new Error(message);
|
|
28814
|
+
plainMalformedErrors.add(error);
|
|
28782
28815
|
return error;
|
|
28783
28816
|
}
|
|
28784
28817
|
function checkedUuid2(value, field) {
|
|
@@ -28983,7 +29016,62 @@ function followErrorEnvelope(error) {
|
|
|
28983
29016
|
return EMPTY_SERVER_ERROR_ENVELOPE;
|
|
28984
29017
|
}
|
|
28985
29018
|
function isTransportFollowMessage(error) {
|
|
28986
|
-
return error instanceof SignalTransportError || error instanceof Error && plainTransportErrors.has(error);
|
|
29019
|
+
return error instanceof SignalTransportError || error instanceof SignalHostPortsExhaustedError || error instanceof Error && plainTransportErrors.has(error);
|
|
29020
|
+
}
|
|
29021
|
+
function safeConstructorName(error) {
|
|
29022
|
+
if (error === null || typeof error !== "object") return typeof error;
|
|
29023
|
+
const name = error.constructor?.name;
|
|
29024
|
+
if (typeof name !== "string" || name.length === 0) return "Unknown";
|
|
29025
|
+
return name.replace(/[^A-Za-z0-9_$-]+/g, "_").slice(0, 96) || "Unknown";
|
|
29026
|
+
}
|
|
29027
|
+
function classifySignalReadFailure(error) {
|
|
29028
|
+
if (error instanceof SignalHostPortsExhaustedError || error !== null && typeof error === "object" && error.code === "EADDRNOTAVAIL") {
|
|
29029
|
+
return {
|
|
29030
|
+
code: "host_ports_exhausted",
|
|
29031
|
+
httpStatus: null,
|
|
29032
|
+
errorConstructor: null
|
|
29033
|
+
};
|
|
29034
|
+
}
|
|
29035
|
+
const http = followHttpDetails(error);
|
|
29036
|
+
if (http !== null) {
|
|
29037
|
+
return {
|
|
29038
|
+
code: "http_status",
|
|
29039
|
+
httpStatus: http.status,
|
|
29040
|
+
errorConstructor: null
|
|
29041
|
+
};
|
|
29042
|
+
}
|
|
29043
|
+
if (error instanceof SignalReadTimeoutError) {
|
|
29044
|
+
return {
|
|
29045
|
+
code: error.phase === "body" ? "body_timeout" : "no_response",
|
|
29046
|
+
httpStatus: null,
|
|
29047
|
+
errorConstructor: null
|
|
29048
|
+
};
|
|
29049
|
+
}
|
|
29050
|
+
if (error instanceof Error && plainTransportErrors.has(error)) {
|
|
29051
|
+
return {
|
|
29052
|
+
code: plainTransportFailureCodes.get(error) ?? "no_response",
|
|
29053
|
+
httpStatus: null,
|
|
29054
|
+
errorConstructor: null
|
|
29055
|
+
};
|
|
29056
|
+
}
|
|
29057
|
+
if (error instanceof SignalTransportError) {
|
|
29058
|
+
return { code: "no_response", httpStatus: null, errorConstructor: null };
|
|
29059
|
+
}
|
|
29060
|
+
if (error instanceof SignalMalformedError || error instanceof Error && plainMalformedErrors.has(error)) {
|
|
29061
|
+
return {
|
|
29062
|
+
code: "malformed_response",
|
|
29063
|
+
httpStatus: null,
|
|
29064
|
+
errorConstructor: null
|
|
29065
|
+
};
|
|
29066
|
+
}
|
|
29067
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
29068
|
+
return { code: "aborted", httpStatus: null, errorConstructor: null };
|
|
29069
|
+
}
|
|
29070
|
+
return {
|
|
29071
|
+
code: "unclassified",
|
|
29072
|
+
httpStatus: null,
|
|
29073
|
+
errorConstructor: safeConstructorName(error)
|
|
29074
|
+
};
|
|
28987
29075
|
}
|
|
28988
29076
|
function isRestartableReadError(error) {
|
|
28989
29077
|
if (error instanceof SignalReadTimeoutError) return true;
|
|
@@ -29035,6 +29123,7 @@ async function fetchSignalRead(fetcher, input, init, timeoutMs = SIGNAL_READ_TIM
|
|
|
29035
29123
|
}
|
|
29036
29124
|
const deadlineController = new AbortController();
|
|
29037
29125
|
let timedOut = false;
|
|
29126
|
+
let responseReceived = false;
|
|
29038
29127
|
const signal = init.signal ? AbortSignal.any([init.signal, deadlineController.signal]) : deadlineController.signal;
|
|
29039
29128
|
let onAbort = () => {
|
|
29040
29129
|
};
|
|
@@ -29062,22 +29151,31 @@ async function fetchSignalRead(fetcher, input, init, timeoutMs = SIGNAL_READ_TIM
|
|
|
29062
29151
|
signal
|
|
29063
29152
|
});
|
|
29064
29153
|
} catch (error) {
|
|
29065
|
-
if (signal.aborted || timedOut
|
|
29154
|
+
if (signal.aborted || timedOut) {
|
|
29066
29155
|
return "timeout";
|
|
29067
29156
|
}
|
|
29157
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
29158
|
+
if (error !== null && typeof error === "object" && error.code === "EADDRNOTAVAIL") {
|
|
29159
|
+
throw new SignalHostPortsExhaustedError();
|
|
29160
|
+
}
|
|
29068
29161
|
return null;
|
|
29069
29162
|
}
|
|
29163
|
+
responseReceived = true;
|
|
29070
29164
|
if (signal.aborted || timedOut) return "timeout";
|
|
29071
29165
|
try {
|
|
29072
29166
|
return { response, body: await response.json() };
|
|
29073
|
-
} catch {
|
|
29167
|
+
} catch (error) {
|
|
29074
29168
|
if (signal.aborted || timedOut) return "timeout";
|
|
29169
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
29075
29170
|
return { response, body: null };
|
|
29076
29171
|
}
|
|
29077
29172
|
})();
|
|
29078
29173
|
const raced = await Promise.race([read, aborted]);
|
|
29079
29174
|
if (raced === "timeout") {
|
|
29080
|
-
throw new SignalReadTimeoutError(
|
|
29175
|
+
throw new SignalReadTimeoutError(
|
|
29176
|
+
"signal read timed out",
|
|
29177
|
+
responseReceived ? "body" : "response"
|
|
29178
|
+
);
|
|
29081
29179
|
}
|
|
29082
29180
|
return raced;
|
|
29083
29181
|
} finally {
|
|
@@ -29106,7 +29204,9 @@ async function fetchSignalReadRetrying(fetcher, input, init, timeoutMs = SIGNAL_
|
|
|
29106
29204
|
function mapReadFailure(error, waitBound) {
|
|
29107
29205
|
if (error instanceof SignalReadTimeoutError) {
|
|
29108
29206
|
if (waitBound) throw error;
|
|
29109
|
-
throw plainTransportError(
|
|
29207
|
+
throw plainTransportError(
|
|
29208
|
+
error.phase === "body" ? "body_timeout" : "no_response"
|
|
29209
|
+
);
|
|
29110
29210
|
}
|
|
29111
29211
|
throw error;
|
|
29112
29212
|
}
|
|
@@ -29162,7 +29262,7 @@ async function humanSignals(target2, credential, query, options) {
|
|
|
29162
29262
|
throwSignalHttp(response, body);
|
|
29163
29263
|
}
|
|
29164
29264
|
if (!Array.isArray(body)) {
|
|
29165
|
-
throw
|
|
29265
|
+
throw plainMalformedError("signal read returned malformed JSON");
|
|
29166
29266
|
}
|
|
29167
29267
|
const parsed = body.map((value) => parseSignalRecord(value));
|
|
29168
29268
|
return sortSignals(rowsAfterCursor(parsed, query.after), ascending);
|
|
@@ -29230,7 +29330,7 @@ async function agentSignalPage(target2, credential, query, options, allowLegacyC
|
|
|
29230
29330
|
const { response, body } = result;
|
|
29231
29331
|
if (!response.ok) throwSignalHttp(response, body);
|
|
29232
29332
|
if (!body || typeof body !== "object" || Array.isArray(body) || !Array.isArray(body.signals)) {
|
|
29233
|
-
throw
|
|
29333
|
+
throw plainMalformedError("signal read returned malformed JSON");
|
|
29234
29334
|
}
|
|
29235
29335
|
const capabilities = signalReadCapabilities(
|
|
29236
29336
|
body.capabilities
|
|
@@ -29720,6 +29820,7 @@ function resolveRefusalToleranceMs(raw, warn = () => {
|
|
|
29720
29820
|
}
|
|
29721
29821
|
function isRetryableFollowError(error) {
|
|
29722
29822
|
if (serverRefusedRetry(followErrorEnvelope(error))) return false;
|
|
29823
|
+
if (error instanceof SignalHostPortsExhaustedError) return true;
|
|
29723
29824
|
if (error instanceof SignalReadTimeoutError) return true;
|
|
29724
29825
|
if (isTransportFollowMessage(error)) return true;
|
|
29725
29826
|
const http = followHttpDetails(error);
|
|
@@ -29948,6 +30049,16 @@ var CURSOR_MAX_BYTES = 4 * 1024;
|
|
|
29948
30049
|
var ARRIVAL_SNIPPET_MAX = 180;
|
|
29949
30050
|
var ARRIVAL_WATCH_POLL_MS = 25e3;
|
|
29950
30051
|
var ARRIVAL_RETRY_NOTICE_THRESHOLD_MS = 6e4;
|
|
30052
|
+
var EXIT_NOTIFY_ORPHANED = 74;
|
|
30053
|
+
var NotifyStdoutClosedError = class extends Error {
|
|
30054
|
+
name = "NotifyStdoutClosedError";
|
|
30055
|
+
code = "notify_stdout_closed";
|
|
30056
|
+
constructor() {
|
|
30057
|
+
super(
|
|
30058
|
+
"[notify_stdout_closed] inbox --notify lost its stdout reader and stopped before advancing its cursor. Start one fresh watcher under a live Monitor."
|
|
30059
|
+
);
|
|
30060
|
+
}
|
|
30061
|
+
};
|
|
29951
30062
|
function createArrivalRetryNoticePolicy(thresholdMs = ARRIVAL_RETRY_NOTICE_THRESHOLD_MS) {
|
|
29952
30063
|
let firstFailureAt = null;
|
|
29953
30064
|
let failureEmitted = false;
|
|
@@ -30000,9 +30111,8 @@ function parseCursor(raw, workspaceId2, principalId) {
|
|
|
30000
30111
|
throw new Error("stored arrival cursor is malformed");
|
|
30001
30112
|
}
|
|
30002
30113
|
const row = value;
|
|
30003
|
-
const keys = Object.keys(row).sort();
|
|
30004
30114
|
const cursor = row.cursor;
|
|
30005
|
-
if (
|
|
30115
|
+
if (row.version !== 1 || row.workspace_id !== workspaceId2.toLowerCase() || row.principal_id !== principalId.toLowerCase() || !(cursor === null || typeof cursor === "object" && !Array.isArray(cursor) && typeof cursor.created_at === "string" && Number.isFinite(Date.parse(cursor.created_at)) && typeof cursor.id === "string" && UUID_RE10.test(cursor.id))) {
|
|
30006
30116
|
throw new Error("stored arrival cursor is malformed");
|
|
30007
30117
|
}
|
|
30008
30118
|
if (cursor === null) return null;
|
|
@@ -30065,6 +30175,33 @@ function formatArrivalNotification(notification) {
|
|
|
30065
30175
|
const attachmentCopy = notification.attachment_count === 0 ? "" : ` \u2014 ${notification.attachment_count} attachment${notification.attachment_count === 1 ? "" : "s"}`;
|
|
30066
30176
|
return `CommonSwarm from ${notification.sender_kind} ${notification.sender}: ${notification.snippet}${attachmentCopy} \u2014 reply: ${notification.reply_command}`;
|
|
30067
30177
|
}
|
|
30178
|
+
function notifyWriteError(error) {
|
|
30179
|
+
return error.code === "EPIPE" ? new NotifyStdoutClosedError() : error;
|
|
30180
|
+
}
|
|
30181
|
+
async function writeArrivalMonitorLine(line, stream2 = process.stdout) {
|
|
30182
|
+
await new Promise((resolve2, reject) => {
|
|
30183
|
+
let settled = false;
|
|
30184
|
+
const finish = (error) => {
|
|
30185
|
+
if (settled) return;
|
|
30186
|
+
settled = true;
|
|
30187
|
+
if (error) {
|
|
30188
|
+
setImmediate(() => stream2.off("error", onError));
|
|
30189
|
+
reject(notifyWriteError(error));
|
|
30190
|
+
} else {
|
|
30191
|
+
stream2.off("error", onError);
|
|
30192
|
+
resolve2();
|
|
30193
|
+
}
|
|
30194
|
+
};
|
|
30195
|
+
const onError = (error) => finish(error);
|
|
30196
|
+
stream2.once("error", onError);
|
|
30197
|
+
try {
|
|
30198
|
+
stream2.write(`${line}
|
|
30199
|
+
`, (error) => finish(error));
|
|
30200
|
+
} catch (error) {
|
|
30201
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
30202
|
+
}
|
|
30203
|
+
});
|
|
30204
|
+
}
|
|
30068
30205
|
function cursorOf(signal) {
|
|
30069
30206
|
return { created_at: signal.created_at, id: signal.id };
|
|
30070
30207
|
}
|
|
@@ -30275,7 +30412,11 @@ function parseDeliveryReceipt(value) {
|
|
|
30275
30412
|
"protocol",
|
|
30276
30413
|
"delivery receipt returned a malformed last_error_code"
|
|
30277
30414
|
);
|
|
30278
|
-
})()
|
|
30415
|
+
})(),
|
|
30416
|
+
pending_for_main_count: Object.hasOwn(row, "pending_for_main_count") ? row.pending_for_main_count === null ? null : nonNegativeInteger(
|
|
30417
|
+
row.pending_for_main_count,
|
|
30418
|
+
"pending_for_main_count"
|
|
30419
|
+
) : null
|
|
30279
30420
|
};
|
|
30280
30421
|
}
|
|
30281
30422
|
function parseUntrackedAgent(value) {
|
|
@@ -30629,9 +30770,10 @@ function renderSignalReceiptReport(report, nowMs = Date.now()) {
|
|
|
30629
30770
|
].join("\n");
|
|
30630
30771
|
}
|
|
30631
30772
|
if (state === "queued") {
|
|
30773
|
+
const queueCount = receipt.pending_for_main_count;
|
|
30632
30774
|
return [
|
|
30633
|
-
`Queued for agent ${receipt.recipient_agent_principal_id}'s interactive session ${relativeAge(receipt.acked_at, nowMs)}
|
|
30634
|
-
|
|
30775
|
+
`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)` : ""}.`,
|
|
30776
|
+
`Ask the agent's operator to check its listener with: ${listenerStatusCommand(report, receipt)}`,
|
|
30635
30777
|
`Check again with: ${receiptCheckCommand(report)}`
|
|
30636
30778
|
].join("\n");
|
|
30637
30779
|
}
|
|
@@ -30686,7 +30828,8 @@ function signalReceiptJsonPayload(report, nowMs = Date.now()) {
|
|
|
30686
30828
|
acked_at: receipt.acked_at,
|
|
30687
30829
|
attempt_count: receipt.attempt_count,
|
|
30688
30830
|
lease_expiry_count: receipt.lease_expiry_count,
|
|
30689
|
-
last_error_code: receipt.last_error_code
|
|
30831
|
+
last_error_code: receipt.last_error_code,
|
|
30832
|
+
pending_for_main_count: receipt.pending_for_main_count ?? null
|
|
30690
30833
|
}
|
|
30691
30834
|
)
|
|
30692
30835
|
};
|
|
@@ -30696,11 +30839,7 @@ function signalReceiptJsonPayload(report, nowMs = Date.now()) {
|
|
|
30696
30839
|
var import_node_child_process3 = require("node:child_process");
|
|
30697
30840
|
var import_node_crypto12 = require("node:crypto");
|
|
30698
30841
|
|
|
30699
|
-
// src/host/
|
|
30700
|
-
var RING_CAPACITY_BYTES = 4096;
|
|
30701
|
-
var TAIL_MAX_CHARS = 2048;
|
|
30702
|
-
var STDERR_EXIT_GRACE_MS = 100;
|
|
30703
|
-
var STDERR_READABLE_END_GRACE_MS = STDERR_EXIT_GRACE_MS + 50;
|
|
30842
|
+
// src/host/credential-redaction.ts
|
|
30704
30843
|
var EXOTIC_SEPARATORS = "\\u00a0\\u1680\\u2000-\\u200d\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\u202f\\u205f\\u3000\\ufeff";
|
|
30705
30844
|
var SEPARATOR_CLASS_SOURCE = "\\t\\n\\x0b\\f\\r " + EXOTIC_SEPARATORS;
|
|
30706
30845
|
var ANSI_ESCAPE_GLOBAL_RE2 = new RegExp("\\u001b\\[[0-?]*[ -\\/]*[@-~]", "g");
|
|
@@ -30712,8 +30851,17 @@ var CREDENTIAL_PREFIX_RE = new RegExp(
|
|
|
30712
30851
|
`swm_(?:agt|inv|cap)_[^${SEPARATOR_CLASS_SOURCE}]*`,
|
|
30713
30852
|
"gi"
|
|
30714
30853
|
);
|
|
30854
|
+
function redactCredentialText(value) {
|
|
30855
|
+
return value.replace(ANSI_ESCAPE_GLOBAL_RE2, "").replace(CONTROL_AND_SEPARATOR_STRIP_RE, "").replace(CREDENTIAL_PREFIX_RE, "[redacted-credential]");
|
|
30856
|
+
}
|
|
30857
|
+
|
|
30858
|
+
// src/host/stderr-tail.ts
|
|
30859
|
+
var RING_CAPACITY_BYTES = 4096;
|
|
30860
|
+
var TAIL_MAX_CHARS = 2048;
|
|
30861
|
+
var STDERR_EXIT_GRACE_MS = 100;
|
|
30862
|
+
var STDERR_READABLE_END_GRACE_MS = STDERR_EXIT_GRACE_MS + 50;
|
|
30715
30863
|
function sanitizeStderrTail(raw) {
|
|
30716
|
-
return raw
|
|
30864
|
+
return redactCredentialText(raw).slice(-TAIL_MAX_CHARS).trim();
|
|
30717
30865
|
}
|
|
30718
30866
|
function attachStderrTailRing(stderr) {
|
|
30719
30867
|
const chunks = [];
|
|
@@ -30799,7 +30947,7 @@ var CLAUDE_ACP_MIN_VERSION = "0.64.2";
|
|
|
30799
30947
|
var CLAUDE_ACP_LAST_MEASURED_VERSION = "0.64.2";
|
|
30800
30948
|
var CLAUDE_PERMISSION_MODE_ID = "default";
|
|
30801
30949
|
var CODEX_ACP_MIN_VERSION = "1.1.9";
|
|
30802
|
-
var CODEX_ACP_LAST_MEASURED_VERSION = "1.
|
|
30950
|
+
var CODEX_ACP_LAST_MEASURED_VERSION = "1.8.0";
|
|
30803
30951
|
var CODEX_PERMISSION_MODE_ID = "read-only";
|
|
30804
30952
|
var ACP_PROTOCOL_VERSION = 1;
|
|
30805
30953
|
var OPENCODE_FORCED_PERMISSION_TOOLS = [
|
|
@@ -30925,7 +31073,7 @@ function permissionDecisionToResult(decision) {
|
|
|
30925
31073
|
var SECRET_VALUE_RE = /(?:(?:api[_-]?key|token|secret|password|authorization|bearer)\s*[:=]\s*)(["']?)([^\s"'\\]{8,})\1/gi;
|
|
30926
31074
|
var JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
|
|
30927
31075
|
function redactString(value) {
|
|
30928
|
-
return value.replace(SECRET_VALUE_RE, (_m, q) => `redacted=${q}***${q}`).replace(JWT_RE, "[redacted-jwt]");
|
|
31076
|
+
return redactCredentialText(value).replace(SECRET_VALUE_RE, (_m, q) => `redacted=${q}***${q}`).replace(JWT_RE, "[redacted-jwt]");
|
|
30929
31077
|
}
|
|
30930
31078
|
function redactUnknown(value, depth = 0) {
|
|
30931
31079
|
if (depth > 6) return "[truncated]";
|
|
@@ -31041,10 +31189,14 @@ var AcpVersionBelowFloorError = class extends AcpVersionError {
|
|
|
31041
31189
|
actual;
|
|
31042
31190
|
};
|
|
31043
31191
|
var AcpPermissionCanaryError = class extends AcpHostError {
|
|
31044
|
-
constructor(message) {
|
|
31192
|
+
constructor(message, reasonCode = null, minimumRequiredVersion = null) {
|
|
31045
31193
|
super("permission_canary_failed", message);
|
|
31194
|
+
this.reasonCode = reasonCode;
|
|
31195
|
+
this.minimumRequiredVersion = minimumRequiredVersion;
|
|
31046
31196
|
this.name = "AcpPermissionCanaryError";
|
|
31047
31197
|
}
|
|
31198
|
+
reasonCode;
|
|
31199
|
+
minimumRequiredVersion;
|
|
31048
31200
|
};
|
|
31049
31201
|
var AcpPromptsBlockedError = class extends AcpHostError {
|
|
31050
31202
|
constructor() {
|
|
@@ -31513,7 +31665,8 @@ var AcpHostSession = class _AcpHostSession {
|
|
|
31513
31665
|
}
|
|
31514
31666
|
const detail = last?.reason ?? "permission-boundary canary failed: need host reject + correlated terminal tool status";
|
|
31515
31667
|
throw new AcpPermissionCanaryError(
|
|
31516
|
-
total === 1 ? detail : `${detail} (failed ${total} attempts)
|
|
31668
|
+
total === 1 ? detail : `${detail} (failed ${total} attempts)`,
|
|
31669
|
+
last?.reasonCode ?? null
|
|
31517
31670
|
);
|
|
31518
31671
|
}
|
|
31519
31672
|
/** Test/helper: force-enable prompts without canary (never used by production open path). */
|
|
@@ -31558,7 +31711,8 @@ var AcpHostSession = class _AcpHostSession {
|
|
|
31558
31711
|
passed: false,
|
|
31559
31712
|
sawPermissionRequest: this.canaryState.sawPermissionRequest,
|
|
31560
31713
|
sawDeniedToolResult: this.canaryState.sawDeniedToolResult,
|
|
31561
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
31714
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
31715
|
+
...err instanceof AcpHostError ? { reasonCode: err.code } : {}
|
|
31562
31716
|
};
|
|
31563
31717
|
}
|
|
31564
31718
|
}
|
|
@@ -32635,6 +32789,7 @@ async function openOpenCodeAcpSession(options) {
|
|
|
32635
32789
|
|
|
32636
32790
|
// src/host/claude.ts
|
|
32637
32791
|
var import_node_child_process4 = require("node:child_process");
|
|
32792
|
+
var import_node_module = require("node:module");
|
|
32638
32793
|
var import_node_fs4 = require("node:fs");
|
|
32639
32794
|
var import_node_path7 = require("node:path");
|
|
32640
32795
|
var CHILD_EXIT_WAIT_MS2 = 3e3;
|
|
@@ -32745,6 +32900,51 @@ function parseClaudeVersionOutput(stdout) {
|
|
|
32745
32900
|
function parseClaudeCodeVersionOutput(stdout) {
|
|
32746
32901
|
return parseProviderVersionOutput(stdout, /\bClaude Code\b/i, false);
|
|
32747
32902
|
}
|
|
32903
|
+
function semanticVersion(value) {
|
|
32904
|
+
if (typeof value !== "string") return null;
|
|
32905
|
+
return parseProviderVersionOutput(`${value}
|
|
32906
|
+
`, /\bnever-a-product-name\b/i);
|
|
32907
|
+
}
|
|
32908
|
+
function readPackageAtOrAbove(entrypoint, expectedName) {
|
|
32909
|
+
let directory = (0, import_node_path7.dirname)(entrypoint);
|
|
32910
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
32911
|
+
const path = (0, import_node_path7.join)(directory, "package.json");
|
|
32912
|
+
try {
|
|
32913
|
+
const row = JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
32914
|
+
if (row && typeof row === "object" && !Array.isArray(row) && row.name === expectedName) {
|
|
32915
|
+
return { path, row };
|
|
32916
|
+
}
|
|
32917
|
+
} catch {
|
|
32918
|
+
}
|
|
32919
|
+
const parent = (0, import_node_path7.dirname)(directory);
|
|
32920
|
+
if (parent === directory) break;
|
|
32921
|
+
directory = parent;
|
|
32922
|
+
}
|
|
32923
|
+
return null;
|
|
32924
|
+
}
|
|
32925
|
+
function measureClaudeBundleVersions(executable) {
|
|
32926
|
+
const adapter = readPackageAtOrAbove(
|
|
32927
|
+
executable,
|
|
32928
|
+
"@agentclientprotocol/claude-agent-acp"
|
|
32929
|
+
);
|
|
32930
|
+
if (!adapter) return { agentSdkVersion: null, claudeCodeVersion: null };
|
|
32931
|
+
try {
|
|
32932
|
+
const sdkEntrypoint = (0, import_node_module.createRequire)(adapter.path).resolve(
|
|
32933
|
+
"@anthropic-ai/claude-agent-sdk"
|
|
32934
|
+
);
|
|
32935
|
+
const sdk = readPackageAtOrAbove(
|
|
32936
|
+
sdkEntrypoint,
|
|
32937
|
+
"@anthropic-ai/claude-agent-sdk"
|
|
32938
|
+
);
|
|
32939
|
+
if (!sdk) return { agentSdkVersion: null, claudeCodeVersion: null };
|
|
32940
|
+
return {
|
|
32941
|
+
agentSdkVersion: semanticVersion(sdk.row.version),
|
|
32942
|
+
claudeCodeVersion: semanticVersion(sdk.row.claudeCodeVersion)
|
|
32943
|
+
};
|
|
32944
|
+
} catch {
|
|
32945
|
+
return { agentSdkVersion: null, claudeCodeVersion: null };
|
|
32946
|
+
}
|
|
32947
|
+
}
|
|
32748
32948
|
async function readClaudeVersionOutput(executable, options) {
|
|
32749
32949
|
const timeoutMs = options?.timeoutMs ?? ACP_VERSION_CHECK_TIMEOUT_MS;
|
|
32750
32950
|
const env = options?.env ?? sanitizeChildEnv(process.env);
|
|
@@ -32768,24 +32968,21 @@ async function readClaudeVersionOutput(executable, options) {
|
|
|
32768
32968
|
);
|
|
32769
32969
|
});
|
|
32770
32970
|
}
|
|
32771
|
-
async function
|
|
32772
|
-
const
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
...options?.onNewerVersion ? { onNewerVersion: options.onNewerVersion } : {}
|
|
32787
|
-
});
|
|
32788
|
-
return version3;
|
|
32971
|
+
async function inspectClaudeBridgeExecutable(executable = "claude-agent-acp", options) {
|
|
32972
|
+
const resolved = resolveClaudeExecutable(
|
|
32973
|
+
executable,
|
|
32974
|
+
options?.pathEnv,
|
|
32975
|
+
options?.platform
|
|
32976
|
+
);
|
|
32977
|
+
const output = await readClaudeVersionOutput(resolved, options);
|
|
32978
|
+
const bundle = measureClaudeBundleVersions(resolved);
|
|
32979
|
+
return {
|
|
32980
|
+
executable: resolved,
|
|
32981
|
+
providerVersion: parseClaudeVersionOutput(output),
|
|
32982
|
+
lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
|
|
32983
|
+
bundledAgentSdkVersion: bundle.agentSdkVersion,
|
|
32984
|
+
bundledClaudeCodeVersion: bundle.claudeCodeVersion
|
|
32985
|
+
};
|
|
32789
32986
|
}
|
|
32790
32987
|
function buildClaudeAcpArgs() {
|
|
32791
32988
|
return [];
|
|
@@ -32850,29 +33047,57 @@ async function openClaudeAcpSession(options) {
|
|
|
32850
33047
|
let executable;
|
|
32851
33048
|
let env = baseEnv;
|
|
32852
33049
|
let claudeCodeExecutable;
|
|
33050
|
+
let providerVersion;
|
|
33051
|
+
let bundleVersions = {
|
|
33052
|
+
agentSdkVersion: null,
|
|
33053
|
+
claudeCodeVersion: null
|
|
33054
|
+
};
|
|
33055
|
+
const reportRuntime = (resolved, version3) => {
|
|
33056
|
+
bundleVersions = measureClaudeBundleVersions(resolved);
|
|
33057
|
+
options.onRuntimeNotice?.({
|
|
33058
|
+
executable: resolved,
|
|
33059
|
+
providerVersion: version3,
|
|
33060
|
+
lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
|
|
33061
|
+
bundledAgentSdkVersion: bundleVersions.agentSdkVersion,
|
|
33062
|
+
bundledClaudeCodeVersion: bundleVersions.claudeCodeVersion
|
|
33063
|
+
});
|
|
33064
|
+
};
|
|
33065
|
+
const admitBridgeVersion = (resolved, version3) => {
|
|
33066
|
+
providerVersion = version3;
|
|
33067
|
+
reportRuntime(resolved, version3);
|
|
33068
|
+
assertProviderVersionFloor({
|
|
33069
|
+
provider: "claude-agent-acp",
|
|
33070
|
+
version: version3,
|
|
33071
|
+
minimumVersion: CLAUDE_ACP_MIN_VERSION,
|
|
33072
|
+
lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
|
|
33073
|
+
...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
|
|
33074
|
+
});
|
|
33075
|
+
};
|
|
32853
33076
|
if (requestedExecutable) {
|
|
32854
33077
|
const output = await readClaudeVersionOutput(requestedExecutable, {
|
|
32855
33078
|
env: baseEnv
|
|
32856
33079
|
});
|
|
32857
33080
|
const bridgeVersion = parseClaudeVersionOutput(output);
|
|
32858
33081
|
if (bridgeVersion) {
|
|
32859
|
-
assertProviderVersionFloor({
|
|
32860
|
-
provider: "claude-agent-acp",
|
|
32861
|
-
version: bridgeVersion,
|
|
32862
|
-
minimumVersion: CLAUDE_ACP_MIN_VERSION,
|
|
32863
|
-
lastMeasuredVersion: CLAUDE_ACP_LAST_MEASURED_VERSION,
|
|
32864
|
-
...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
|
|
32865
|
-
});
|
|
32866
33082
|
executable = requestedExecutable;
|
|
33083
|
+
admitBridgeVersion(executable, bridgeVersion);
|
|
32867
33084
|
} else if (parseClaudeCodeVersionOutput(output)) {
|
|
32868
33085
|
claudeCodeExecutable = requestedExecutable;
|
|
32869
33086
|
executable = resolvePackagedClaudeBridge(resolvedPathEnv);
|
|
32870
33087
|
env = buildClaudeChildEnv(parentEnv, claudeCodeExecutable);
|
|
32871
|
-
await
|
|
32872
|
-
env
|
|
32873
|
-
...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
|
|
33088
|
+
const bridgeOutput = await readClaudeVersionOutput(executable, {
|
|
33089
|
+
env
|
|
32874
33090
|
});
|
|
33091
|
+
const packagedVersion = parseClaudeVersionOutput(bridgeOutput);
|
|
33092
|
+
if (!packagedVersion) {
|
|
33093
|
+
reportRuntime(executable, null);
|
|
33094
|
+
throw new AcpVersionParseError(
|
|
33095
|
+
`could not parse claude-agent-acp version from: ${bridgeOutput.trim().slice(0, 200)}`
|
|
33096
|
+
);
|
|
33097
|
+
}
|
|
33098
|
+
admitBridgeVersion(executable, packagedVersion);
|
|
32875
33099
|
} else {
|
|
33100
|
+
reportRuntime(requestedExecutable, null);
|
|
32876
33101
|
throw new AcpVersionError(
|
|
32877
33102
|
`could not identify Claude executable from: ${output.trim().slice(0, 200)}`
|
|
32878
33103
|
);
|
|
@@ -32883,10 +33108,19 @@ async function openClaudeAcpSession(options) {
|
|
|
32883
33108
|
resolvedPathEnv
|
|
32884
33109
|
);
|
|
32885
33110
|
if (!options.skipVersionCheck) {
|
|
32886
|
-
await
|
|
32887
|
-
env: baseEnv
|
|
32888
|
-
...options.onVersionNotice ? { onNewerVersion: options.onVersionNotice } : {}
|
|
33111
|
+
const output = await readClaudeVersionOutput(executable, {
|
|
33112
|
+
env: baseEnv
|
|
32889
33113
|
});
|
|
33114
|
+
const version3 = parseClaudeVersionOutput(output);
|
|
33115
|
+
if (!version3) {
|
|
33116
|
+
reportRuntime(executable, null);
|
|
33117
|
+
throw new AcpVersionParseError(
|
|
33118
|
+
`could not parse claude-agent-acp version from: ${output.trim().slice(0, 200)}`
|
|
33119
|
+
);
|
|
33120
|
+
}
|
|
33121
|
+
admitBridgeVersion(executable, version3);
|
|
33122
|
+
} else {
|
|
33123
|
+
reportRuntime(executable, null);
|
|
32890
33124
|
}
|
|
32891
33125
|
}
|
|
32892
33126
|
if (options.signal?.aborted) {
|
|
@@ -32978,7 +33212,17 @@ async function openClaudeAcpSession(options) {
|
|
|
32978
33212
|
})();
|
|
32979
33213
|
return closePromise;
|
|
32980
33214
|
};
|
|
32981
|
-
return {
|
|
33215
|
+
return {
|
|
33216
|
+
session,
|
|
33217
|
+
child,
|
|
33218
|
+
executable,
|
|
33219
|
+
args,
|
|
33220
|
+
env,
|
|
33221
|
+
...providerVersion ? { providerVersion } : {},
|
|
33222
|
+
...bundleVersions.agentSdkVersion ? { bundledAgentSdkVersion: bundleVersions.agentSdkVersion } : {},
|
|
33223
|
+
...bundleVersions.claudeCodeVersion ? { bundledClaudeCodeVersion: bundleVersions.claudeCodeVersion } : {},
|
|
33224
|
+
close
|
|
33225
|
+
};
|
|
32982
33226
|
} catch (error) {
|
|
32983
33227
|
removeAbortListener();
|
|
32984
33228
|
transport.close();
|
|
@@ -33098,6 +33342,17 @@ async function assertCodexVersionFloor(executable, options) {
|
|
|
33098
33342
|
});
|
|
33099
33343
|
const version3 = parseCodexVersionOutput(stdout);
|
|
33100
33344
|
if (!version3) {
|
|
33345
|
+
const codexCliVersion = parseProviderVersionOutput(
|
|
33346
|
+
stdout,
|
|
33347
|
+
/\bcodex-cli\b/i,
|
|
33348
|
+
false
|
|
33349
|
+
);
|
|
33350
|
+
if (codexCliVersion) {
|
|
33351
|
+
throw new AcpVersionError(
|
|
33352
|
+
"this is the Codex CLI; --codex-executable takes the codex-acp bridge (npm i -g @agentclientprotocol/codex-acp)",
|
|
33353
|
+
"executable_not_bridge"
|
|
33354
|
+
);
|
|
33355
|
+
}
|
|
33101
33356
|
throw new AcpVersionParseError(
|
|
33102
33357
|
`could not parse codex-acp version from: ${stdout.trim().slice(0, 200)}`
|
|
33103
33358
|
);
|
|
@@ -33163,10 +33418,13 @@ async function openCodexAcpSession(options) {
|
|
|
33163
33418
|
typeof pathEnv === "string" ? pathEnv : void 0
|
|
33164
33419
|
);
|
|
33165
33420
|
const env = buildCodexChildEnv(parentEnv);
|
|
33421
|
+
let providerVersion;
|
|
33166
33422
|
if (!options.skipVersionCheck) {
|
|
33167
|
-
await assertCodexVersionFloor(executable, {
|
|
33168
|
-
|
|
33169
|
-
|
|
33423
|
+
providerVersion = await assertCodexVersionFloor(executable, { env });
|
|
33424
|
+
options.onVersionNotice?.({
|
|
33425
|
+
provider: "codex-acp",
|
|
33426
|
+
runningVersion: providerVersion,
|
|
33427
|
+
lastMeasuredVersion: CODEX_ACP_LAST_MEASURED_VERSION
|
|
33170
33428
|
});
|
|
33171
33429
|
}
|
|
33172
33430
|
if (options.signal?.aborted) {
|
|
@@ -33258,7 +33516,15 @@ async function openCodexAcpSession(options) {
|
|
|
33258
33516
|
})();
|
|
33259
33517
|
return closePromise;
|
|
33260
33518
|
};
|
|
33261
|
-
return {
|
|
33519
|
+
return {
|
|
33520
|
+
session,
|
|
33521
|
+
child,
|
|
33522
|
+
executable,
|
|
33523
|
+
args,
|
|
33524
|
+
env,
|
|
33525
|
+
...providerVersion ? { providerVersion } : {},
|
|
33526
|
+
close
|
|
33527
|
+
};
|
|
33262
33528
|
} catch (error) {
|
|
33263
33529
|
removeAbortListener();
|
|
33264
33530
|
transport.close();
|
|
@@ -33808,6 +34074,18 @@ var V1_EFFECT_KEYS = /* @__PURE__ */ new Set([
|
|
|
33808
34074
|
"updatedAt"
|
|
33809
34075
|
]);
|
|
33810
34076
|
var V2_EFFECT_KEYS = /* @__PURE__ */ new Set([...V1_EFFECT_KEYS, "signalKind"]);
|
|
34077
|
+
var EFFECT_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
34078
|
+
"lease_id",
|
|
34079
|
+
"leaseId",
|
|
34080
|
+
"listenerBearer",
|
|
34081
|
+
"bearer",
|
|
34082
|
+
"token",
|
|
34083
|
+
"prompt",
|
|
34084
|
+
"ackCommandId",
|
|
34085
|
+
"ack_command_id",
|
|
34086
|
+
"claimCommandId",
|
|
34087
|
+
"claim_command_id"
|
|
34088
|
+
]);
|
|
33811
34089
|
function defaultListenerStateDirectory() {
|
|
33812
34090
|
return process.env.XDG_STATE_HOME ? (0, import_node_path9.join)(process.env.XDG_STATE_HOME, "cswarm", "listeners") : (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".cswarm", "listeners");
|
|
33813
34091
|
}
|
|
@@ -33826,9 +34104,9 @@ function integer(value) {
|
|
|
33826
34104
|
function nullableString2(value, max) {
|
|
33827
34105
|
return value === null || typeof value === "string" && value.length <= max;
|
|
33828
34106
|
}
|
|
33829
|
-
function
|
|
34107
|
+
function rejectSensitiveKeys(row) {
|
|
33830
34108
|
for (const key2 of Object.keys(row)) {
|
|
33831
|
-
if (
|
|
34109
|
+
if (EFFECT_SENSITIVE_KEYS.has(key2)) {
|
|
33832
34110
|
throw new Error("stored listener effect is malformed");
|
|
33833
34111
|
}
|
|
33834
34112
|
}
|
|
@@ -33844,17 +34122,16 @@ function parseListenerEffectRecord(raw, expectedId) {
|
|
|
33844
34122
|
throw new Error("stored listener effect is malformed");
|
|
33845
34123
|
}
|
|
33846
34124
|
const row = value;
|
|
34125
|
+
rejectSensitiveKeys(row);
|
|
33847
34126
|
if (typeof row.version !== "number" || row.version !== 1 && row.version !== 2 || typeof row.signalId !== "string" || row.signalId.toLowerCase() !== expectedId || !UUID_RE13.test(row.signalId)) {
|
|
33848
34127
|
throw new Error("stored listener effect is malformed");
|
|
33849
34128
|
}
|
|
33850
34129
|
if (row.version === 1) {
|
|
33851
|
-
rejectUnknownKeys(row, V1_EFFECT_KEYS);
|
|
33852
34130
|
if ("signalKind" in row || row.state === "observed" || row.state === "routed_main") {
|
|
33853
34131
|
throw new Error("stored listener effect is malformed");
|
|
33854
34132
|
}
|
|
33855
34133
|
return upcastV1Ask(row);
|
|
33856
34134
|
}
|
|
33857
|
-
rejectUnknownKeys(row, V2_EFFECT_KEYS);
|
|
33858
34135
|
return parseV2Record(row);
|
|
33859
34136
|
}
|
|
33860
34137
|
function upcastV1Ask(row) {
|
|
@@ -34307,6 +34584,7 @@ var GrokListenerModel = class {
|
|
|
34307
34584
|
...this.options.effort ? { effort: this.options.effort } : {},
|
|
34308
34585
|
...this.options.env ? { env: this.options.env } : {},
|
|
34309
34586
|
...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
|
|
34587
|
+
...this.options.events ? { events: this.options.events } : {},
|
|
34310
34588
|
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
34311
34589
|
clientName: "cswarm-listener"
|
|
34312
34590
|
});
|
|
@@ -34747,6 +35025,7 @@ var OpenCodeListenerModel = class {
|
|
|
34747
35025
|
...this.options.model ? { model: this.options.model } : {},
|
|
34748
35026
|
...this.options.env ? { env: this.options.env } : {},
|
|
34749
35027
|
...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
|
|
35028
|
+
...this.options.events ? { events: this.options.events } : {},
|
|
34750
35029
|
...this.options.allowMissingAuth === true ? { allowMissingAuth: true } : {},
|
|
34751
35030
|
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
34752
35031
|
clientName: "cswarm-listener"
|
|
@@ -34850,6 +35129,35 @@ var import_node_crypto16 = require("node:crypto");
|
|
|
34850
35129
|
var import_promises7 = require("node:fs/promises");
|
|
34851
35130
|
var import_node_os8 = require("node:os");
|
|
34852
35131
|
var import_node_path13 = require("node:path");
|
|
35132
|
+
var CLAUDE_CODE_VERSION_REQUIRED_RE = /\bClaude Code (\d+\.\d+\.\d+) does not support this model; version (\d+\.\d+\.\d+) or newer is required\b/;
|
|
35133
|
+
var CLAUDE_AUTH_FAILURE_RE = /\b(?:authentication failed|authentication required|not authenticated|OAuth (?:sign-in|login|token)|keychain\/OAuth|please (?:log|sign) in)\b/i;
|
|
35134
|
+
var CLAUDE_CANARY_TIMEOUT_RE = /^ACP request timed out: session\/prompt(?: \(failed \d+ attempts\))?$/;
|
|
35135
|
+
function classifyClaudeCanaryFailure(detail, typedReasonCode) {
|
|
35136
|
+
const recorded = detail?.trim() ?? "";
|
|
35137
|
+
const demanded = CLAUDE_CODE_VERSION_REQUIRED_RE.exec(recorded);
|
|
35138
|
+
if (demanded?.[2]) {
|
|
35139
|
+
return {
|
|
35140
|
+
code: "claude_bridge_version_required",
|
|
35141
|
+
minimumRequiredVersion: demanded[2]
|
|
35142
|
+
};
|
|
35143
|
+
}
|
|
35144
|
+
if (typedReasonCode === "claude_canary_timeout" || typedReasonCode === "timeout" || (typedReasonCode === null || typedReasonCode === void 0) && CLAUDE_CANARY_TIMEOUT_RE.test(recorded)) {
|
|
35145
|
+
return { code: "claude_canary_timeout", minimumRequiredVersion: null };
|
|
35146
|
+
}
|
|
35147
|
+
if (typedReasonCode === "claude_canary_auth_failed") {
|
|
35148
|
+
return { code: "claude_canary_auth_failed", minimumRequiredVersion: null };
|
|
35149
|
+
}
|
|
35150
|
+
if (typedReasonCode === "claude_bridge_version_required") {
|
|
35151
|
+
return {
|
|
35152
|
+
code: "claude_bridge_version_required",
|
|
35153
|
+
minimumRequiredVersion: demanded?.[2] ?? null
|
|
35154
|
+
};
|
|
35155
|
+
}
|
|
35156
|
+
if ((typedReasonCode === "rpc_error" || typedReasonCode === null || typedReasonCode === void 0) && CLAUDE_AUTH_FAILURE_RE.test(recorded)) {
|
|
35157
|
+
return { code: "claude_canary_auth_failed", minimumRequiredVersion: null };
|
|
35158
|
+
}
|
|
35159
|
+
return { code: "claude_canary_unknown", minimumRequiredVersion: null };
|
|
35160
|
+
}
|
|
34853
35161
|
var ClaudeListenerClosedDuringOpen = class extends Error {
|
|
34854
35162
|
constructor() {
|
|
34855
35163
|
super("listener model closed while the Claude worker was opening");
|
|
@@ -34963,6 +35271,8 @@ var ClaudeListenerModel = class {
|
|
|
34963
35271
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
34964
35272
|
...this.options.env ? { env: this.options.env } : {},
|
|
34965
35273
|
...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
|
|
35274
|
+
...this.options.onRuntimeNotice ? { onRuntimeNotice: this.options.onRuntimeNotice } : {},
|
|
35275
|
+
...this.options.events ? { events: this.options.events } : {},
|
|
34966
35276
|
signal: controller.signal,
|
|
34967
35277
|
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
34968
35278
|
clientName: "cswarm-listener"
|
|
@@ -34991,6 +35301,7 @@ var ClaudeListenerModel = class {
|
|
|
34991
35301
|
(0, import_node_os8.tmpdir)(),
|
|
34992
35302
|
`cswarm-claude-permission-canary-${process.pid}-${(0, import_node_crypto16.randomUUID)()}`
|
|
34993
35303
|
);
|
|
35304
|
+
let canaryError;
|
|
34994
35305
|
let sentinelCreated = false;
|
|
34995
35306
|
try {
|
|
34996
35307
|
await handle.session.enablePromptsAfterCanary({
|
|
@@ -34998,6 +35309,8 @@ var ClaudeListenerModel = class {
|
|
|
34998
35309
|
probeText: `Create the file ${sentinelPath} using the Write tool with content CSWARM_CANARY_NOOP. You must use the Write tool. Do nothing else.`,
|
|
34999
35310
|
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
35000
35311
|
});
|
|
35312
|
+
} catch (error) {
|
|
35313
|
+
canaryError = error;
|
|
35001
35314
|
} finally {
|
|
35002
35315
|
try {
|
|
35003
35316
|
await (0, import_promises7.lstat)(sentinelPath);
|
|
@@ -35009,9 +35322,22 @@ var ClaudeListenerModel = class {
|
|
|
35009
35322
|
}
|
|
35010
35323
|
if (sentinelCreated) {
|
|
35011
35324
|
throw new AcpPermissionCanaryError(
|
|
35012
|
-
"Claude bridge wrote the permission canary sentinel before denial"
|
|
35325
|
+
"Claude bridge wrote the permission canary sentinel before denial",
|
|
35326
|
+
"claude_canary_write_not_blocked"
|
|
35327
|
+
);
|
|
35328
|
+
}
|
|
35329
|
+
if (canaryError instanceof AcpPermissionCanaryError) {
|
|
35330
|
+
const shape = classifyClaudeCanaryFailure(
|
|
35331
|
+
canaryError.message,
|
|
35332
|
+
canaryError.reasonCode
|
|
35333
|
+
);
|
|
35334
|
+
throw new AcpPermissionCanaryError(
|
|
35335
|
+
canaryError.message,
|
|
35336
|
+
shape.code,
|
|
35337
|
+
shape.minimumRequiredVersion
|
|
35013
35338
|
);
|
|
35014
35339
|
}
|
|
35340
|
+
if (canaryError !== void 0) throw canaryError;
|
|
35015
35341
|
}
|
|
35016
35342
|
};
|
|
35017
35343
|
|
|
@@ -35026,6 +35352,10 @@ var CodexListenerClosedDuringOpen = class extends Error {
|
|
|
35026
35352
|
this.name = "CodexListenerClosedDuringOpen";
|
|
35027
35353
|
}
|
|
35028
35354
|
};
|
|
35355
|
+
function pathIsInsideOrEqual(ancestor, candidate) {
|
|
35356
|
+
const fromAncestor = (0, import_node_path14.relative)(ancestor, candidate);
|
|
35357
|
+
return fromAncestor === "" || fromAncestor !== ".." && !fromAncestor.startsWith(`..${import_node_path14.sep}`) && !(0, import_node_path14.isAbsolute)(fromAncestor);
|
|
35358
|
+
}
|
|
35029
35359
|
var CodexListenerModel = class {
|
|
35030
35360
|
constructor(options) {
|
|
35031
35361
|
this.options = options;
|
|
@@ -35133,6 +35463,7 @@ var CodexListenerModel = class {
|
|
|
35133
35463
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
35134
35464
|
...this.options.env ? { env: this.options.env } : {},
|
|
35135
35465
|
...this.options.onVersionNotice ? { onVersionNotice: this.options.onVersionNotice } : {},
|
|
35466
|
+
...this.options.events ? { events: this.options.events } : {},
|
|
35136
35467
|
signal: controller.signal,
|
|
35137
35468
|
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
35138
35469
|
clientName: "cswarm-listener"
|
|
@@ -35157,10 +35488,25 @@ var CodexListenerModel = class {
|
|
|
35157
35488
|
}
|
|
35158
35489
|
/** Force Codex's measured shell permission path without changing worker cwd. */
|
|
35159
35490
|
async enablePromptsAfterCodexCanary(handle) {
|
|
35491
|
+
const configuredHome = this.options.env?.HOME;
|
|
35492
|
+
const home = configuredHome && (0, import_node_path14.isAbsolute)(configuredHome) ? configuredHome : (0, import_node_os9.homedir)();
|
|
35493
|
+
const sentinelDirectory = (0, import_node_path14.join)(home, ".cswarm", "canary");
|
|
35494
|
+
await (0, import_promises8.mkdir)(sentinelDirectory, { recursive: true, mode: 448 });
|
|
35495
|
+
await (0, import_promises8.chmod)(sentinelDirectory, 448);
|
|
35496
|
+
const [workerCwd, canaryDirectory] = await Promise.all([
|
|
35497
|
+
(0, import_promises8.realpath)(this.options.cwd),
|
|
35498
|
+
(0, import_promises8.realpath)(sentinelDirectory)
|
|
35499
|
+
]);
|
|
35500
|
+
if (pathIsInsideOrEqual(workerCwd, canaryDirectory)) {
|
|
35501
|
+
throw new AcpPermissionCanaryError(
|
|
35502
|
+
`canary_path_inside_cwd: ${canaryDirectory} is inside listener cwd ${workerCwd}. Next: pass a --cwd that is not your home directory`
|
|
35503
|
+
);
|
|
35504
|
+
}
|
|
35160
35505
|
const sentinelPath = (0, import_node_path14.join)(
|
|
35161
|
-
|
|
35506
|
+
canaryDirectory,
|
|
35162
35507
|
`cswarm-codex-permission-canary-${process.pid}-${(0, import_node_crypto17.randomUUID)()}`
|
|
35163
35508
|
);
|
|
35509
|
+
let canaryError;
|
|
35164
35510
|
let sentinelCreated = false;
|
|
35165
35511
|
try {
|
|
35166
35512
|
await handle.session.enablePromptsAfterCanary({
|
|
@@ -35168,6 +35514,8 @@ var CodexListenerModel = class {
|
|
|
35168
35514
|
probeText: `Use a shell command to create ${sentinelPath} with content CSWARM_CANARY_NOOP. You must use the shell. Do nothing else.`,
|
|
35169
35515
|
...this.options.onCanaryAttempt ? { onAttempt: this.options.onCanaryAttempt } : {}
|
|
35170
35516
|
});
|
|
35517
|
+
} catch (error) {
|
|
35518
|
+
canaryError = error;
|
|
35171
35519
|
} finally {
|
|
35172
35520
|
try {
|
|
35173
35521
|
await (0, import_promises8.lstat)(sentinelPath);
|
|
@@ -35177,11 +35525,39 @@ var CodexListenerModel = class {
|
|
|
35177
35525
|
if (error.code !== "ENOENT") throw error;
|
|
35178
35526
|
}
|
|
35179
35527
|
}
|
|
35528
|
+
const observation = handle.session.canaryObservation;
|
|
35529
|
+
const sawPermissionRequest = observation?.sawPermissionRequest === true;
|
|
35530
|
+
const bridgeVersion = handle.providerVersion ?? handle.session.info?.agentVersion ?? "unknown";
|
|
35531
|
+
if (sentinelCreated && !sawPermissionRequest) {
|
|
35532
|
+
throw new AcpPermissionCanaryError(
|
|
35533
|
+
`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}`
|
|
35534
|
+
);
|
|
35535
|
+
}
|
|
35180
35536
|
if (sentinelCreated) {
|
|
35181
35537
|
throw new AcpPermissionCanaryError(
|
|
35182
|
-
|
|
35538
|
+
`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
35539
|
);
|
|
35184
35540
|
}
|
|
35541
|
+
if (canaryError instanceof AcpPermissionCanaryError) {
|
|
35542
|
+
if (canaryError.reasonCode === "timeout") {
|
|
35543
|
+
throw new AcpPermissionCanaryError(
|
|
35544
|
+
`canary_timeout: ${canaryError.message}. Next: retry to run a fresh bounded permission canary`,
|
|
35545
|
+
"timeout"
|
|
35546
|
+
);
|
|
35547
|
+
}
|
|
35548
|
+
if (canaryError.reasonCode !== null) {
|
|
35549
|
+
throw new AcpPermissionCanaryError(
|
|
35550
|
+
`canary_bridge_error: codex-acp ${bridgeVersion} returned ${canaryError.message}. Next: resolve the quoted bridge error, then retry`,
|
|
35551
|
+
canaryError.reasonCode
|
|
35552
|
+
);
|
|
35553
|
+
}
|
|
35554
|
+
if (!sawPermissionRequest) {
|
|
35555
|
+
throw new AcpPermissionCanaryError(
|
|
35556
|
+
`canary_no_tool_call: codex-acp ${bridgeVersion} did not request permission or create ${sentinelPath}. Next: retry to re-sample the model's shell choice`
|
|
35557
|
+
);
|
|
35558
|
+
}
|
|
35559
|
+
}
|
|
35560
|
+
if (canaryError !== void 0) throw canaryError;
|
|
35185
35561
|
}
|
|
35186
35562
|
};
|
|
35187
35563
|
|
|
@@ -35775,6 +36151,21 @@ var UUID_RE15 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-
|
|
|
35775
36151
|
var MAX_QUEUE_BYTES = 1024 * 1024;
|
|
35776
36152
|
var QUEUE_FILE = "pending-for-main.json";
|
|
35777
36153
|
var QUEUE_LOCK = "pending-for-main";
|
|
36154
|
+
var QUEUE_KEYS = /* @__PURE__ */ new Set(["version", "entries", "droppedCount"]);
|
|
36155
|
+
var ENTRY_KEYS = /* @__PURE__ */ new Set([
|
|
36156
|
+
"signalId",
|
|
36157
|
+
"workspaceId",
|
|
36158
|
+
"principalId",
|
|
36159
|
+
"fromId",
|
|
36160
|
+
"fromKind",
|
|
36161
|
+
"kind",
|
|
36162
|
+
"senderName",
|
|
36163
|
+
"body",
|
|
36164
|
+
"attachmentCount",
|
|
36165
|
+
"createdAt",
|
|
36166
|
+
"queuedAt",
|
|
36167
|
+
"observationPending"
|
|
36168
|
+
]);
|
|
35778
36169
|
var LISTENER_MAIN_QUEUE_MAX = 200;
|
|
35779
36170
|
var LISTENER_DEFER_OVER_MIN = 1;
|
|
35780
36171
|
var LISTENER_DEFER_OVER_MAX = 1e4;
|
|
@@ -35805,26 +36196,12 @@ function decideListenerRoute(route, threshold, bodyLength) {
|
|
|
35805
36196
|
function checkedTimestamp2(value) {
|
|
35806
36197
|
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
35807
36198
|
}
|
|
35808
|
-
function parseEntry(value) {
|
|
36199
|
+
function parseEntry(value, rejectUnknownKeys) {
|
|
35809
36200
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
35810
36201
|
throw new Error("stored pending-for-main entry is malformed");
|
|
35811
36202
|
}
|
|
35812
36203
|
const row = value;
|
|
35813
|
-
|
|
35814
|
-
"signalId",
|
|
35815
|
-
"workspaceId",
|
|
35816
|
-
"principalId",
|
|
35817
|
-
"fromId",
|
|
35818
|
-
"fromKind",
|
|
35819
|
-
"kind",
|
|
35820
|
-
"senderName",
|
|
35821
|
-
"body",
|
|
35822
|
-
"attachmentCount",
|
|
35823
|
-
"createdAt",
|
|
35824
|
-
"queuedAt",
|
|
35825
|
-
"observationPending"
|
|
35826
|
-
]);
|
|
35827
|
-
if (Object.keys(row).some((key2) => !allowed.has(key2))) {
|
|
36204
|
+
if (rejectUnknownKeys && Object.keys(row).some((key2) => !ENTRY_KEYS.has(key2))) {
|
|
35828
36205
|
throw new Error("stored pending-for-main entry is malformed");
|
|
35829
36206
|
}
|
|
35830
36207
|
if (typeof row.signalId !== "string" || !UUID_RE15.test(row.signalId) || typeof row.workspaceId !== "string" || !UUID_RE15.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE15.test(row.principalId) || typeof row.fromId !== "string" || !UUID_RE15.test(row.fromId) || row.fromKind !== "user" && row.fromKind !== "agent" || !(row.kind === void 0 || row.kind === "ask" || row.kind === "note") || !(row.senderName === null || typeof row.senderName === "string" && row.senderName.length <= 200) || typeof row.body !== "string" || row.body.length < 1 || !(row.attachmentCount === void 0 || typeof row.attachmentCount === "number" && Number.isSafeInteger(row.attachmentCount) && row.attachmentCount >= 1 && row.attachmentCount <= 8) || !checkedTimestamp2(row.createdAt) || !checkedTimestamp2(row.queuedAt) || !(row.observationPending === void 0 || row.observationPending === true)) {
|
|
@@ -35845,7 +36222,7 @@ function parseEntry(value) {
|
|
|
35845
36222
|
...row.observationPending === true ? { observationPending: true } : {}
|
|
35846
36223
|
};
|
|
35847
36224
|
}
|
|
35848
|
-
function parseFile(raw) {
|
|
36225
|
+
function parseFile(raw, rejectUnknownKeys = false) {
|
|
35849
36226
|
let value;
|
|
35850
36227
|
try {
|
|
35851
36228
|
value = JSON.parse(raw);
|
|
@@ -35856,12 +36233,10 @@ function parseFile(raw) {
|
|
|
35856
36233
|
throw new Error("stored pending-for-main queue is malformed");
|
|
35857
36234
|
}
|
|
35858
36235
|
const row = value;
|
|
35859
|
-
if (Object.keys(row).some(
|
|
35860
|
-
(key2) => key2 !== "version" && key2 !== "entries" && key2 !== "droppedCount"
|
|
35861
|
-
) || row.version !== 1 || !Array.isArray(row.entries) || row.entries.length > LISTENER_MAIN_QUEUE_MAX || !(row.droppedCount === void 0 || typeof row.droppedCount === "number" && Number.isSafeInteger(row.droppedCount) && row.droppedCount >= 0)) {
|
|
36236
|
+
if (rejectUnknownKeys && Object.keys(row).some((key2) => !QUEUE_KEYS.has(key2)) || row.version !== 1 || !Array.isArray(row.entries) || row.entries.length > LISTENER_MAIN_QUEUE_MAX || !(row.droppedCount === void 0 || typeof row.droppedCount === "number" && Number.isSafeInteger(row.droppedCount) && row.droppedCount >= 0)) {
|
|
35862
36237
|
throw new Error("stored pending-for-main queue is malformed");
|
|
35863
36238
|
}
|
|
35864
|
-
const entries = row.entries.map(parseEntry);
|
|
36239
|
+
const entries = row.entries.map((entry) => parseEntry(entry, rejectUnknownKeys));
|
|
35865
36240
|
if (new Set(entries.map((entry) => entry.signalId)).size !== entries.length) {
|
|
35866
36241
|
throw new Error("stored pending-for-main queue repeats a signal");
|
|
35867
36242
|
}
|
|
@@ -35882,7 +36257,7 @@ var FilePendingMainQueue = class {
|
|
|
35882
36257
|
return raw === null ? { version: 1, entries: [], droppedCount: 0 } : parseFile(raw);
|
|
35883
36258
|
}
|
|
35884
36259
|
async writeUnlocked(file) {
|
|
35885
|
-
const canonical = parseFile(JSON.stringify(file));
|
|
36260
|
+
const canonical = parseFile(JSON.stringify(file), true);
|
|
35886
36261
|
await writeSecureJsonFile(this.path, JSON.stringify(canonical));
|
|
35887
36262
|
}
|
|
35888
36263
|
async read() {
|
|
@@ -35896,7 +36271,7 @@ var FilePendingMainQueue = class {
|
|
|
35896
36271
|
return { count: file.entries.length, droppedCount: file.droppedCount };
|
|
35897
36272
|
}
|
|
35898
36273
|
async enqueue(entry) {
|
|
35899
|
-
const checked = parseEntry(entry);
|
|
36274
|
+
const checked = parseEntry(entry, true);
|
|
35900
36275
|
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
35901
36276
|
const file = await this.readUnlocked();
|
|
35902
36277
|
if (file.entries.some((item) => item.signalId === checked.signalId)) {
|
|
@@ -35955,7 +36330,7 @@ function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
|
|
|
35955
36330
|
createdAt: signal.created_at,
|
|
35956
36331
|
queuedAt: new Date(now).toISOString(),
|
|
35957
36332
|
...options.observationPending ? { observationPending: true } : {}
|
|
35958
|
-
});
|
|
36333
|
+
}, true);
|
|
35959
36334
|
}
|
|
35960
36335
|
|
|
35961
36336
|
// src/listener/runtime.ts
|
|
@@ -35968,6 +36343,7 @@ var LISTENER_REPLY_ONLY_MINIMUM_MS = SIGNAL_REQUEST_TIMEOUT_MS + LISTENER_ACK_ON
|
|
|
35968
36343
|
var LISTENER_PROMPT_START_MINIMUM_MS = SIGNAL_READ_TIMEOUT_MS + ACP_DEFAULT_REQUEST_TIMEOUT_MS + LISTENER_REPLY_ONLY_MINIMUM_MS;
|
|
35969
36344
|
var LISTENER_DELIVERY_RETRY_INITIAL_MS = 500;
|
|
35970
36345
|
var LISTENER_DELIVERY_RETRY_MAX_MS = 3e4;
|
|
36346
|
+
var LISTENER_HOST_PORTS_PROBE_MS = 6e4;
|
|
35971
36347
|
var UUID_RE16 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35972
36348
|
var ListenerCapabilityError = class extends Error {
|
|
35973
36349
|
code;
|
|
@@ -36402,6 +36778,8 @@ async function runListenerRuntime(options) {
|
|
|
36402
36778
|
let ready = false;
|
|
36403
36779
|
let deliveryMode = null;
|
|
36404
36780
|
let readAttempt = 0;
|
|
36781
|
+
let readEpisodeStartedAtMs = null;
|
|
36782
|
+
let readEpisodeAttempts = 0;
|
|
36405
36783
|
const onAbort = () => {
|
|
36406
36784
|
options.model.cancel();
|
|
36407
36785
|
};
|
|
@@ -36499,6 +36877,18 @@ async function runListenerRuntime(options) {
|
|
|
36499
36877
|
}
|
|
36500
36878
|
});
|
|
36501
36879
|
requireCapabilities(page);
|
|
36880
|
+
if (ready && readEpisodeStartedAtMs !== null) {
|
|
36881
|
+
const recoveredAtMs = now();
|
|
36882
|
+
options.onEvent?.({
|
|
36883
|
+
type: "read_recovered",
|
|
36884
|
+
attempts: readEpisodeAttempts,
|
|
36885
|
+
durationMs: Math.max(0, recoveredAtMs - readEpisodeStartedAtMs),
|
|
36886
|
+
startedAt: new Date(readEpisodeStartedAtMs).toISOString(),
|
|
36887
|
+
ts: new Date(recoveredAtMs).toISOString()
|
|
36888
|
+
});
|
|
36889
|
+
readEpisodeStartedAtMs = null;
|
|
36890
|
+
readEpisodeAttempts = 0;
|
|
36891
|
+
}
|
|
36502
36892
|
const nextMode = classifyDeliveryMode(page, durableConfigured);
|
|
36503
36893
|
if (nextMode !== deliveryMode) {
|
|
36504
36894
|
deliveryMode = nextMode;
|
|
@@ -36519,19 +36909,25 @@ async function runListenerRuntime(options) {
|
|
|
36519
36909
|
stop = { reason: "credential", error: asError2(error) };
|
|
36520
36910
|
break;
|
|
36521
36911
|
}
|
|
36522
|
-
|
|
36523
|
-
|
|
36524
|
-
break;
|
|
36525
|
-
}
|
|
36526
|
-
if (isRetryableFollowError(error)) {
|
|
36912
|
+
const failure = classifySignalReadFailure(error);
|
|
36913
|
+
if (isRetryableFollowError(error) || failure.code === "aborted" || failure.code === "host_ports_exhausted") {
|
|
36527
36914
|
readAttempt += 1;
|
|
36528
|
-
const delayMs = nextFollowBackoffMs(readAttempt, null, random);
|
|
36915
|
+
const delayMs = failure.code === "host_ports_exhausted" ? LISTENER_HOST_PORTS_PROBE_MS : nextFollowBackoffMs(readAttempt, null, random);
|
|
36529
36916
|
if (ready) {
|
|
36917
|
+
const failedAtMs = now();
|
|
36918
|
+
if (readEpisodeStartedAtMs === null) {
|
|
36919
|
+
readEpisodeStartedAtMs = failedAtMs;
|
|
36920
|
+
readEpisodeAttempts = 0;
|
|
36921
|
+
}
|
|
36922
|
+
readEpisodeAttempts += 1;
|
|
36530
36923
|
options.onEvent?.({
|
|
36531
36924
|
type: "read_retry",
|
|
36532
36925
|
attempt: readAttempt,
|
|
36926
|
+
episodeAttempt: readEpisodeAttempts,
|
|
36927
|
+
episodeStartedAt: new Date(readEpisodeStartedAtMs).toISOString(),
|
|
36928
|
+
failure,
|
|
36533
36929
|
delayMs,
|
|
36534
|
-
ts:
|
|
36930
|
+
ts: new Date(failedAtMs).toISOString()
|
|
36535
36931
|
});
|
|
36536
36932
|
}
|
|
36537
36933
|
await sleep2(delayMs, abort);
|
|
@@ -36554,6 +36950,7 @@ async function runListenerRuntime(options) {
|
|
|
36554
36950
|
type: "ready",
|
|
36555
36951
|
workspaceId: options.workspaceId,
|
|
36556
36952
|
principalId: options.principalId,
|
|
36953
|
+
cadenceMs: pollMs,
|
|
36557
36954
|
ts: eventTime(now)
|
|
36558
36955
|
});
|
|
36559
36956
|
if (options.declareModel !== void 0) {
|
|
@@ -37083,27 +37480,275 @@ async function runListenerRuntime(options) {
|
|
|
37083
37480
|
return stop ?? { reason: "cancelled" };
|
|
37084
37481
|
}
|
|
37085
37482
|
|
|
37086
|
-
// src/listener/
|
|
37087
|
-
var
|
|
37088
|
-
var
|
|
37089
|
-
var
|
|
37090
|
-
var
|
|
37091
|
-
var
|
|
37092
|
-
var
|
|
37093
|
-
var
|
|
37094
|
-
var
|
|
37095
|
-
|
|
37096
|
-
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37100
|
-
|
|
37101
|
-
|
|
37102
|
-
|
|
37103
|
-
function
|
|
37104
|
-
|
|
37105
|
-
|
|
37106
|
-
|
|
37483
|
+
// src/listener/read-health.ts
|
|
37484
|
+
var HOUR_MS = 60 * 6e4;
|
|
37485
|
+
var MINUTE_MS = 6e4;
|
|
37486
|
+
var HEALTH_WINDOW_MS = 24 * HOUR_MS;
|
|
37487
|
+
var LISTENER_READ_RETRY_HOUR_CAP = 25;
|
|
37488
|
+
var LISTENER_READ_RETRY_MINUTE_CAP = 61;
|
|
37489
|
+
var LISTENER_CLAIM_HOUR_CAP = 25;
|
|
37490
|
+
var LISTENER_THROUGHPUT_LAPSE_RATIO = 0.5;
|
|
37491
|
+
var FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
37492
|
+
"http_status",
|
|
37493
|
+
"no_response",
|
|
37494
|
+
"body_timeout",
|
|
37495
|
+
"malformed_response",
|
|
37496
|
+
"aborted",
|
|
37497
|
+
"host_ports_exhausted",
|
|
37498
|
+
"unclassified"
|
|
37499
|
+
]);
|
|
37500
|
+
function emptyListenerReadHealth() {
|
|
37501
|
+
return {
|
|
37502
|
+
currentEpisodeStartedAt: null,
|
|
37503
|
+
currentEpisodeAttempts: 0,
|
|
37504
|
+
currentReasonCode: null,
|
|
37505
|
+
currentHttpStatus: null,
|
|
37506
|
+
currentErrorConstructor: null,
|
|
37507
|
+
retryHours: [],
|
|
37508
|
+
retryMinutes: [],
|
|
37509
|
+
claimCadenceMs: null,
|
|
37510
|
+
claimHours: []
|
|
37511
|
+
};
|
|
37512
|
+
}
|
|
37513
|
+
function bucketStart(ts, sizeMs) {
|
|
37514
|
+
const time = Date.parse(ts);
|
|
37515
|
+
return new Date(Math.floor(time / sizeMs) * sizeMs).toISOString();
|
|
37516
|
+
}
|
|
37517
|
+
function trimNewest(rows3, cap) {
|
|
37518
|
+
return rows3.sort((left, right) => Date.parse(left.hourStart) - Date.parse(right.hourStart)).slice(-cap);
|
|
37519
|
+
}
|
|
37520
|
+
function recordRetryHour(rows3, ts, episodeStarted) {
|
|
37521
|
+
const hourStart = bucketStart(ts, HOUR_MS);
|
|
37522
|
+
const next = rows3.map((row) => ({ ...row }));
|
|
37523
|
+
const existing = next.find((row) => row.hourStart === hourStart);
|
|
37524
|
+
if (existing) {
|
|
37525
|
+
existing.retries += 1;
|
|
37526
|
+
if (episodeStarted) existing.episodes += 1;
|
|
37527
|
+
} else {
|
|
37528
|
+
next.push({
|
|
37529
|
+
hourStart,
|
|
37530
|
+
retries: 1,
|
|
37531
|
+
episodes: episodeStarted ? 1 : 0,
|
|
37532
|
+
longestEpisodeAttempts: 0,
|
|
37533
|
+
longestEpisodeDurationMs: 0
|
|
37534
|
+
});
|
|
37535
|
+
}
|
|
37536
|
+
return trimNewest(next, LISTENER_READ_RETRY_HOUR_CAP);
|
|
37537
|
+
}
|
|
37538
|
+
function recordRetryMinute(rows3, ts) {
|
|
37539
|
+
const minuteStart = bucketStart(ts, MINUTE_MS);
|
|
37540
|
+
const next = rows3.map((row) => ({ ...row }));
|
|
37541
|
+
const existing = next.find((row) => row.minuteStart === minuteStart);
|
|
37542
|
+
if (existing) {
|
|
37543
|
+
existing.retries += 1;
|
|
37544
|
+
} else {
|
|
37545
|
+
next.push({ minuteStart, retries: 1 });
|
|
37546
|
+
}
|
|
37547
|
+
return next.sort(
|
|
37548
|
+
(left, right) => Date.parse(left.minuteStart) - Date.parse(right.minuteStart)
|
|
37549
|
+
).slice(-LISTENER_READ_RETRY_MINUTE_CAP);
|
|
37550
|
+
}
|
|
37551
|
+
function recordListenerReadRetry(health, input) {
|
|
37552
|
+
return {
|
|
37553
|
+
...health,
|
|
37554
|
+
currentEpisodeStartedAt: input.episodeStartedAt,
|
|
37555
|
+
currentEpisodeAttempts: input.episodeAttempt,
|
|
37556
|
+
currentReasonCode: input.failure.code,
|
|
37557
|
+
currentHttpStatus: input.failure.httpStatus,
|
|
37558
|
+
currentErrorConstructor: input.failure.errorConstructor,
|
|
37559
|
+
retryHours: recordRetryHour(
|
|
37560
|
+
health.retryHours,
|
|
37561
|
+
input.ts,
|
|
37562
|
+
input.episodeAttempt === 1
|
|
37563
|
+
),
|
|
37564
|
+
retryMinutes: recordRetryMinute(health.retryMinutes, input.ts)
|
|
37565
|
+
};
|
|
37566
|
+
}
|
|
37567
|
+
function recordListenerReadRecovery(health, input) {
|
|
37568
|
+
const hourStart = bucketStart(input.startedAt, HOUR_MS);
|
|
37569
|
+
const retryHours = health.retryHours.map((row) => ({ ...row }));
|
|
37570
|
+
const hour = retryHours.find((row) => row.hourStart === hourStart);
|
|
37571
|
+
if (hour) {
|
|
37572
|
+
if (input.durationMs > hour.longestEpisodeDurationMs || input.durationMs === hour.longestEpisodeDurationMs && input.attempts > hour.longestEpisodeAttempts) {
|
|
37573
|
+
hour.longestEpisodeAttempts = input.attempts;
|
|
37574
|
+
hour.longestEpisodeDurationMs = input.durationMs;
|
|
37575
|
+
}
|
|
37576
|
+
}
|
|
37577
|
+
return {
|
|
37578
|
+
...health,
|
|
37579
|
+
currentEpisodeStartedAt: null,
|
|
37580
|
+
currentEpisodeAttempts: 0,
|
|
37581
|
+
currentReasonCode: null,
|
|
37582
|
+
currentHttpStatus: null,
|
|
37583
|
+
currentErrorConstructor: null,
|
|
37584
|
+
retryHours: trimNewest(retryHours, LISTENER_READ_RETRY_HOUR_CAP)
|
|
37585
|
+
};
|
|
37586
|
+
}
|
|
37587
|
+
function recordListenerClaimCadence(health, cadenceMs) {
|
|
37588
|
+
return { ...health, claimCadenceMs: cadenceMs };
|
|
37589
|
+
}
|
|
37590
|
+
function recordListenerClaim(health, ts) {
|
|
37591
|
+
const hourStart = bucketStart(ts, HOUR_MS);
|
|
37592
|
+
const claimHours = health.claimHours.map((row) => ({ ...row }));
|
|
37593
|
+
const hour = claimHours.find((row) => row.hourStart === hourStart);
|
|
37594
|
+
if (hour) hour.claims += 1;
|
|
37595
|
+
else claimHours.push({ hourStart, claims: 1 });
|
|
37596
|
+
return {
|
|
37597
|
+
...health,
|
|
37598
|
+
claimHours: trimNewest(claimHours, LISTENER_CLAIM_HOUR_CAP)
|
|
37599
|
+
};
|
|
37600
|
+
}
|
|
37601
|
+
function hasExpectedKeys(value, expected, rejectUnknownKeys) {
|
|
37602
|
+
const actual = Object.keys(value);
|
|
37603
|
+
const allowed = new Set(expected);
|
|
37604
|
+
return expected.every((key2) => actual.includes(key2)) && (!rejectUnknownKeys || actual.every((key2) => allowed.has(key2)));
|
|
37605
|
+
}
|
|
37606
|
+
function validTimestamp(value) {
|
|
37607
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
37608
|
+
}
|
|
37609
|
+
function validCount(value) {
|
|
37610
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
37611
|
+
}
|
|
37612
|
+
function parseListenerReadHealth(value, rejectUnknownKeys = false) {
|
|
37613
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
37614
|
+
const row = value;
|
|
37615
|
+
if (!hasExpectedKeys(row, [
|
|
37616
|
+
"currentEpisodeStartedAt",
|
|
37617
|
+
"currentEpisodeAttempts",
|
|
37618
|
+
"currentReasonCode",
|
|
37619
|
+
"currentHttpStatus",
|
|
37620
|
+
"currentErrorConstructor",
|
|
37621
|
+
"retryHours",
|
|
37622
|
+
"retryMinutes",
|
|
37623
|
+
"claimCadenceMs",
|
|
37624
|
+
"claimHours"
|
|
37625
|
+
], rejectUnknownKeys)) return null;
|
|
37626
|
+
if (!(row.currentEpisodeStartedAt === null || validTimestamp(row.currentEpisodeStartedAt)) || !validCount(row.currentEpisodeAttempts) || !(row.currentReasonCode === null || typeof row.currentReasonCode === "string" && FAILURE_CODES.has(row.currentReasonCode)) || !(row.currentHttpStatus === null || typeof row.currentHttpStatus === "number" && Number.isSafeInteger(row.currentHttpStatus) && row.currentHttpStatus >= 100 && row.currentHttpStatus <= 599) || !(row.currentErrorConstructor === null || typeof row.currentErrorConstructor === "string" && /^[A-Za-z0-9_$-]{1,96}$/.test(row.currentErrorConstructor)) || !(row.claimCadenceMs === null || typeof row.claimCadenceMs === "number" && Number.isSafeInteger(row.claimCadenceMs) && row.claimCadenceMs >= 1) || !Array.isArray(row.retryHours) || row.retryHours.length > LISTENER_READ_RETRY_HOUR_CAP || !Array.isArray(row.retryMinutes) || row.retryMinutes.length > LISTENER_READ_RETRY_MINUTE_CAP || !Array.isArray(row.claimHours) || row.claimHours.length > LISTENER_CLAIM_HOUR_CAP) return null;
|
|
37627
|
+
if (row.currentEpisodeStartedAt === null !== (row.currentEpisodeAttempts === 0) || row.currentEpisodeStartedAt === null !== (row.currentReasonCode === null) || row.currentReasonCode === "http_status" !== (row.currentHttpStatus !== null) || row.currentReasonCode === "unclassified" !== (row.currentErrorConstructor !== null)) return null;
|
|
37628
|
+
for (const value2 of row.retryHours) {
|
|
37629
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
|
|
37630
|
+
const hour = value2;
|
|
37631
|
+
if (!hasExpectedKeys(hour, [
|
|
37632
|
+
"hourStart",
|
|
37633
|
+
"retries",
|
|
37634
|
+
"episodes",
|
|
37635
|
+
"longestEpisodeAttempts",
|
|
37636
|
+
"longestEpisodeDurationMs"
|
|
37637
|
+
], rejectUnknownKeys) || !validTimestamp(hour.hourStart) || !validCount(hour.retries) || !validCount(hour.episodes) || !validCount(hour.longestEpisodeAttempts) || !validCount(hour.longestEpisodeDurationMs)) return null;
|
|
37638
|
+
}
|
|
37639
|
+
for (const value2 of row.retryMinutes) {
|
|
37640
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
|
|
37641
|
+
const minute = value2;
|
|
37642
|
+
if (!hasExpectedKeys(minute, ["minuteStart", "retries"], rejectUnknownKeys) || !validTimestamp(minute.minuteStart) || !validCount(minute.retries)) return null;
|
|
37643
|
+
}
|
|
37644
|
+
for (const value2 of row.claimHours) {
|
|
37645
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
|
|
37646
|
+
const hour = value2;
|
|
37647
|
+
if (!hasExpectedKeys(hour, ["hourStart", "claims"], rejectUnknownKeys) || !validTimestamp(hour.hourStart) || !validCount(hour.claims)) return null;
|
|
37648
|
+
}
|
|
37649
|
+
return {
|
|
37650
|
+
currentEpisodeStartedAt: row.currentEpisodeStartedAt,
|
|
37651
|
+
currentEpisodeAttempts: row.currentEpisodeAttempts,
|
|
37652
|
+
currentReasonCode: row.currentReasonCode,
|
|
37653
|
+
currentHttpStatus: row.currentHttpStatus,
|
|
37654
|
+
currentErrorConstructor: row.currentErrorConstructor,
|
|
37655
|
+
retryHours: row.retryHours.map((hour) => ({
|
|
37656
|
+
hourStart: hour.hourStart,
|
|
37657
|
+
retries: hour.retries,
|
|
37658
|
+
episodes: hour.episodes,
|
|
37659
|
+
longestEpisodeAttempts: hour.longestEpisodeAttempts,
|
|
37660
|
+
longestEpisodeDurationMs: hour.longestEpisodeDurationMs
|
|
37661
|
+
})),
|
|
37662
|
+
retryMinutes: row.retryMinutes.map((minute) => ({
|
|
37663
|
+
minuteStart: minute.minuteStart,
|
|
37664
|
+
retries: minute.retries
|
|
37665
|
+
})),
|
|
37666
|
+
claimCadenceMs: row.claimCadenceMs,
|
|
37667
|
+
claimHours: row.claimHours.map((hour) => ({
|
|
37668
|
+
hourStart: hour.hourStart,
|
|
37669
|
+
claims: hour.claims
|
|
37670
|
+
}))
|
|
37671
|
+
};
|
|
37672
|
+
}
|
|
37673
|
+
function summarizeListenerReadHealth(health, readyAt, nowMs) {
|
|
37674
|
+
const windowStart = nowMs - HEALTH_WINDOW_MS;
|
|
37675
|
+
const retryHours = health.retryHours.filter(
|
|
37676
|
+
(row) => Date.parse(row.hourStart) + HOUR_MS > windowStart && Date.parse(row.hourStart) <= nowMs
|
|
37677
|
+
);
|
|
37678
|
+
let episodesLast24h = retryHours.reduce((sum, row) => sum + row.episodes, 0);
|
|
37679
|
+
let longestEpisodeAttemptsLast24h = 0;
|
|
37680
|
+
let longestEpisodeDurationMsLast24h = 0;
|
|
37681
|
+
for (const row of retryHours) {
|
|
37682
|
+
if (row.longestEpisodeDurationMs > longestEpisodeDurationMsLast24h || row.longestEpisodeDurationMs === longestEpisodeDurationMsLast24h && row.longestEpisodeAttempts > longestEpisodeAttemptsLast24h) {
|
|
37683
|
+
longestEpisodeAttemptsLast24h = row.longestEpisodeAttempts;
|
|
37684
|
+
longestEpisodeDurationMsLast24h = row.longestEpisodeDurationMs;
|
|
37685
|
+
}
|
|
37686
|
+
}
|
|
37687
|
+
const currentStartedMs = health.currentEpisodeStartedAt === null ? null : Date.parse(health.currentEpisodeStartedAt);
|
|
37688
|
+
const currentEpisodeDurationMs = currentStartedMs === null ? null : Math.max(0, nowMs - currentStartedMs);
|
|
37689
|
+
if (currentStartedMs !== null && currentStartedMs >= windowStart && currentEpisodeDurationMs !== null && (currentEpisodeDurationMs > longestEpisodeDurationMsLast24h || currentEpisodeDurationMs === longestEpisodeDurationMsLast24h && health.currentEpisodeAttempts > longestEpisodeAttemptsLast24h)) {
|
|
37690
|
+
longestEpisodeAttemptsLast24h = health.currentEpisodeAttempts;
|
|
37691
|
+
longestEpisodeDurationMsLast24h = currentEpisodeDurationMs;
|
|
37692
|
+
}
|
|
37693
|
+
const rollingMinuteStart = Math.floor((nowMs - HOUR_MS) / MINUTE_MS) * MINUTE_MS;
|
|
37694
|
+
const retriesLastHour = health.retryMinutes.reduce((sum, row) => Date.parse(row.minuteStart) >= rollingMinuteStart && Date.parse(row.minuteStart) <= nowMs ? sum + row.retries : sum, 0);
|
|
37695
|
+
const claimThroughputHours = [];
|
|
37696
|
+
if (health.claimCadenceMs !== null && readyAt !== null) {
|
|
37697
|
+
const readyMs = Date.parse(readyAt);
|
|
37698
|
+
const firstFullHour = Math.ceil(readyMs / HOUR_MS) * HOUR_MS;
|
|
37699
|
+
const currentHour = Math.floor(nowMs / HOUR_MS) * HOUR_MS;
|
|
37700
|
+
const first = Math.max(firstFullHour, currentHour - HEALTH_WINDOW_MS);
|
|
37701
|
+
const claimsByHour = new Map(
|
|
37702
|
+
health.claimHours.map((row) => [row.hourStart, row.claims])
|
|
37703
|
+
);
|
|
37704
|
+
const expectedClaims = HOUR_MS / health.claimCadenceMs;
|
|
37705
|
+
for (let hour = first; hour < currentHour; hour += HOUR_MS) {
|
|
37706
|
+
const hourStart = new Date(hour).toISOString();
|
|
37707
|
+
const claims = claimsByHour.get(hourStart) ?? 0;
|
|
37708
|
+
claimThroughputHours.push({
|
|
37709
|
+
hourStart,
|
|
37710
|
+
claims,
|
|
37711
|
+
expectedClaims,
|
|
37712
|
+
ratio: claims / expectedClaims
|
|
37713
|
+
});
|
|
37714
|
+
}
|
|
37715
|
+
}
|
|
37716
|
+
const throughputLapseHours = claimThroughputHours.filter(
|
|
37717
|
+
(hour) => hour.ratio < LISTENER_THROUGHPUT_LAPSE_RATIO
|
|
37718
|
+
);
|
|
37719
|
+
return {
|
|
37720
|
+
currentEpisodeDurationMs,
|
|
37721
|
+
episodesLast24h,
|
|
37722
|
+
longestEpisodeAttemptsLast24h,
|
|
37723
|
+
longestEpisodeDurationMsLast24h,
|
|
37724
|
+
retriesLastHour,
|
|
37725
|
+
retryHours,
|
|
37726
|
+
claimThroughputHours,
|
|
37727
|
+
throughputLapseHours
|
|
37728
|
+
};
|
|
37729
|
+
}
|
|
37730
|
+
|
|
37731
|
+
// src/listener/control.ts
|
|
37732
|
+
var import_node_net = require("node:net");
|
|
37733
|
+
var import_promises9 = require("node:fs/promises");
|
|
37734
|
+
var import_node_path16 = require("node:path");
|
|
37735
|
+
var UUID_RE17 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
37736
|
+
var SEMVER_RE2 = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
37737
|
+
var MAX_STATUS_BYTES = 32 * 1024;
|
|
37738
|
+
var MAX_CONTROL_BYTES = 8 * 1024;
|
|
37739
|
+
var CONTROL_TIMEOUT_MS = 2e3;
|
|
37740
|
+
var START_LOCK_WAIT_MS = 2e3;
|
|
37741
|
+
var START_LOCK_STALE_MS = 1e4;
|
|
37742
|
+
var ListenerAlreadyRunningError = class extends Error {
|
|
37743
|
+
constructor() {
|
|
37744
|
+
super("a listener is already running for this agent principal");
|
|
37745
|
+
this.name = "ListenerAlreadyRunningError";
|
|
37746
|
+
}
|
|
37747
|
+
};
|
|
37748
|
+
function listenerPaths(options) {
|
|
37749
|
+
const root = options.stateDirectory ?? defaultListenerStateDirectory();
|
|
37750
|
+
if (!(0, import_node_path16.isAbsolute)(root)) {
|
|
37751
|
+
throw new Error("listener state directory must be absolute");
|
|
37107
37752
|
}
|
|
37108
37753
|
const key2 = listenerInstanceKey(options);
|
|
37109
37754
|
const instanceDirectory = (0, import_node_path16.join)(root, key2);
|
|
@@ -37136,8 +37781,14 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
37136
37781
|
"lastSignalId",
|
|
37137
37782
|
"lastErrorCode",
|
|
37138
37783
|
"lastErrorDetail",
|
|
37784
|
+
"lastErrorReasonCode",
|
|
37785
|
+
"providerExecutable",
|
|
37139
37786
|
"providerVersion",
|
|
37140
37787
|
"providerLastMeasuredVersion",
|
|
37788
|
+
"providerBundledAgentSdkVersion",
|
|
37789
|
+
"providerBundledClaudeCodeVersion",
|
|
37790
|
+
"providerMinimumRequiredVersion",
|
|
37791
|
+
"cswarmVersion",
|
|
37141
37792
|
"lastWorkerStderrTail",
|
|
37142
37793
|
"logPath",
|
|
37143
37794
|
"deliveryMode",
|
|
@@ -37149,7 +37800,10 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
37149
37800
|
"routeMode",
|
|
37150
37801
|
"deferOverChars",
|
|
37151
37802
|
"pendingForMainCount",
|
|
37152
|
-
"droppedForMainCount"
|
|
37803
|
+
"droppedForMainCount",
|
|
37804
|
+
"readHealth",
|
|
37805
|
+
"connectionsOpened",
|
|
37806
|
+
"connectionReuseRatio"
|
|
37153
37807
|
]);
|
|
37154
37808
|
var STATUS_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
37155
37809
|
"leaseId",
|
|
@@ -37175,7 +37829,7 @@ var STATUS_DELIVERY_KEYS = [
|
|
|
37175
37829
|
"lastClaimAt",
|
|
37176
37830
|
"lastAckAt"
|
|
37177
37831
|
];
|
|
37178
|
-
function parseStatus(raw) {
|
|
37832
|
+
function parseStatus(raw, rejectUnknownKeys = false) {
|
|
37179
37833
|
let value;
|
|
37180
37834
|
try {
|
|
37181
37835
|
value = JSON.parse(raw);
|
|
@@ -37190,14 +37844,15 @@ function parseStatus(raw) {
|
|
|
37190
37844
|
if (STATUS_SENSITIVE_KEYS.has(key2)) {
|
|
37191
37845
|
throw new Error("stored listener status contains a forbidden field");
|
|
37192
37846
|
}
|
|
37193
|
-
if (!STATUS_ALLOWED_KEYS.has(key2)) {
|
|
37847
|
+
if (rejectUnknownKeys && !STATUS_ALLOWED_KEYS.has(key2)) {
|
|
37194
37848
|
throw new Error("stored listener status is malformed");
|
|
37195
37849
|
}
|
|
37196
37850
|
}
|
|
37197
37851
|
const nullableUuid3 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE17.test(candidate);
|
|
37198
37852
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
37199
37853
|
const nullableTimestamp3 = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
37200
|
-
|
|
37854
|
+
const readHealth = row.readHealth === void 0 ? void 0 : parseListenerReadHealth(row.readHealth, rejectUnknownKeys);
|
|
37855
|
+
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.lastErrorReasonCode === void 0 || row.lastErrorReasonCode === null || typeof row.lastErrorReasonCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorReasonCode)) || !(row.providerExecutable === void 0 || row.providerExecutable === null || typeof row.providerExecutable === "string" && (0, import_node_path16.isAbsolute)(row.providerExecutable)) || !(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.providerBundledAgentSdkVersion === void 0 || row.providerBundledAgentSdkVersion === null || typeof row.providerBundledAgentSdkVersion === "string" && SEMVER_RE2.test(row.providerBundledAgentSdkVersion)) || !(row.providerBundledClaudeCodeVersion === void 0 || row.providerBundledClaudeCodeVersion === null || typeof row.providerBundledClaudeCodeVersion === "string" && SEMVER_RE2.test(row.providerBundledClaudeCodeVersion)) || !(row.providerMinimumRequiredVersion === void 0 || row.providerMinimumRequiredVersion === null || typeof row.providerMinimumRequiredVersion === "string" && SEMVER_RE2.test(row.providerMinimumRequiredVersion)) || !(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) || readHealth === null || !(row.connectionsOpened === void 0 || typeof row.connectionsOpened === "number" && Number.isSafeInteger(row.connectionsOpened) && row.connectionsOpened >= 0) || !(row.connectionReuseRatio === void 0 || typeof row.connectionReuseRatio === "number" && Number.isFinite(row.connectionReuseRatio) && row.connectionReuseRatio >= 0)) {
|
|
37201
37856
|
throw new Error("stored listener status is malformed");
|
|
37202
37857
|
}
|
|
37203
37858
|
const routeMode = row.routeMode ?? "worker";
|
|
@@ -37205,8 +37860,11 @@ function parseStatus(raw) {
|
|
|
37205
37860
|
if (routeMode === "split" && deferOverChars === null || routeMode !== "split" && deferOverChars !== null) {
|
|
37206
37861
|
throw new Error("stored listener status routing fields are malformed");
|
|
37207
37862
|
}
|
|
37863
|
+
const knownRow = Object.fromEntries(
|
|
37864
|
+
Object.entries(row).filter(([key2]) => STATUS_ALLOWED_KEYS.has(key2))
|
|
37865
|
+
);
|
|
37208
37866
|
return {
|
|
37209
|
-
...
|
|
37867
|
+
...knownRow,
|
|
37210
37868
|
deliveryMode: row.deliveryMode ?? null,
|
|
37211
37869
|
pendingDeliveryCount: row.pendingDeliveryCount ?? null,
|
|
37212
37870
|
lastTerminalDeliveryFailureCount: row.lastTerminalDeliveryFailureCount ?? null,
|
|
@@ -37217,14 +37875,19 @@ function parseStatus(raw) {
|
|
|
37217
37875
|
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
|
|
37218
37876
|
providerVersion: row.providerVersion ?? null,
|
|
37219
37877
|
providerLastMeasuredVersion: row.providerLastMeasuredVersion ?? null,
|
|
37878
|
+
...row.cswarmVersion === void 0 ? {} : { cswarmVersion: row.cswarmVersion },
|
|
37220
37879
|
routeMode,
|
|
37221
37880
|
deferOverChars,
|
|
37222
37881
|
pendingForMainCount: row.pendingForMainCount ?? 0,
|
|
37223
|
-
droppedForMainCount: row.droppedForMainCount ?? 0
|
|
37882
|
+
droppedForMainCount: row.droppedForMainCount ?? 0,
|
|
37883
|
+
...readHealth === void 0 ? {} : { readHealth }
|
|
37224
37884
|
};
|
|
37225
37885
|
}
|
|
37226
37886
|
async function writeListenerStatus(paths, status) {
|
|
37227
37887
|
const serialized = JSON.stringify(status);
|
|
37888
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_STATUS_BYTES) {
|
|
37889
|
+
throw new Error("listener status is too large");
|
|
37890
|
+
}
|
|
37228
37891
|
const parsed = JSON.parse(serialized);
|
|
37229
37892
|
for (const key2 of STATUS_DELIVERY_KEYS) {
|
|
37230
37893
|
if (!(key2 in parsed)) {
|
|
@@ -37234,13 +37897,17 @@ async function writeListenerStatus(paths, status) {
|
|
|
37234
37897
|
if (!("lastErrorDetail" in parsed)) {
|
|
37235
37898
|
throw new Error("listener status is missing local error detail metadata");
|
|
37236
37899
|
}
|
|
37237
|
-
parseStatus(serialized);
|
|
37900
|
+
parseStatus(serialized, true);
|
|
37238
37901
|
await writeSecureJsonFile(paths.statusPath, serialized);
|
|
37239
37902
|
}
|
|
37240
37903
|
async function readListenerStatus(paths) {
|
|
37241
37904
|
const raw = await readSecureJsonFile(paths.statusPath, MAX_STATUS_BYTES);
|
|
37242
37905
|
return raw === null ? null : parseStatus(raw);
|
|
37243
37906
|
}
|
|
37907
|
+
async function readListenerStatusIfPresent(paths) {
|
|
37908
|
+
const raw = await readSecureJsonFileIfPresent(paths.statusPath, MAX_STATUS_BYTES);
|
|
37909
|
+
return raw === null ? null : parseStatus(raw);
|
|
37910
|
+
}
|
|
37244
37911
|
async function appendListenerEvent(paths, event) {
|
|
37245
37912
|
const allowed = /* @__PURE__ */ new Set([
|
|
37246
37913
|
"ts",
|
|
@@ -37255,6 +37922,12 @@ async function appendListenerEvent(paths, event) {
|
|
|
37255
37922
|
"passed",
|
|
37256
37923
|
"reason",
|
|
37257
37924
|
"delay_ms",
|
|
37925
|
+
"reason_code",
|
|
37926
|
+
"http_status",
|
|
37927
|
+
"error_constructor",
|
|
37928
|
+
"episode_attempt",
|
|
37929
|
+
"attempts",
|
|
37930
|
+
"duration_ms",
|
|
37258
37931
|
"index",
|
|
37259
37932
|
"delivery_mode",
|
|
37260
37933
|
"pending_delivery_count",
|
|
@@ -37293,6 +37966,29 @@ async function appendListenerEvent(paths, event) {
|
|
|
37293
37966
|
if ((key2 === "attempt" || key2 === "total") && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 1)) {
|
|
37294
37967
|
throw new Error("listener event attempt count is not allowed");
|
|
37295
37968
|
}
|
|
37969
|
+
if ((key2 === "episode_attempt" || key2 === "attempts") && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 1)) {
|
|
37970
|
+
throw new Error("listener event episode count is not allowed");
|
|
37971
|
+
}
|
|
37972
|
+
if (key2 === "duration_ms" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
|
|
37973
|
+
throw new Error("listener event episode duration is not allowed");
|
|
37974
|
+
}
|
|
37975
|
+
if (key2 === "http_status" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 100 && value <= 599)) {
|
|
37976
|
+
throw new Error("listener event HTTP status is not allowed");
|
|
37977
|
+
}
|
|
37978
|
+
if (key2 === "reason_code" && !(typeof value === "string" && [
|
|
37979
|
+
"http_status",
|
|
37980
|
+
"no_response",
|
|
37981
|
+
"body_timeout",
|
|
37982
|
+
"malformed_response",
|
|
37983
|
+
"aborted",
|
|
37984
|
+
"host_ports_exhausted",
|
|
37985
|
+
"unclassified"
|
|
37986
|
+
].includes(value))) {
|
|
37987
|
+
throw new Error("listener event read reason code is not allowed");
|
|
37988
|
+
}
|
|
37989
|
+
if (key2 === "error_constructor" && !(typeof value === "string" && /^[A-Za-z0-9_$-]{1,96}$/.test(value))) {
|
|
37990
|
+
throw new Error("listener event error constructor is not allowed");
|
|
37991
|
+
}
|
|
37296
37992
|
if (key2 === "passed" && typeof value !== "boolean") {
|
|
37297
37993
|
throw new Error("listener event canary result is not allowed");
|
|
37298
37994
|
}
|
|
@@ -37336,6 +38032,12 @@ async function appendListenerEvent(paths, event) {
|
|
|
37336
38032
|
if (event.event === "listener_canary_attempt" && (typeof event.attempt !== "number" || typeof event.total !== "number" || event.attempt > event.total || typeof event.passed !== "boolean" || !(event.reason === null || typeof event.reason === "string"))) {
|
|
37337
38033
|
throw new Error("listener canary attempt event is incomplete");
|
|
37338
38034
|
}
|
|
38035
|
+
if (event.event === "listener_read_retry" && (typeof event.reason_code !== "string" || typeof event.episode_attempt !== "number" || event.reason_code === "http_status" !== (typeof event.http_status === "number") || event.reason_code === "unclassified" !== (typeof event.error_constructor === "string"))) {
|
|
38036
|
+
throw new Error("listener read retry event is incomplete");
|
|
38037
|
+
}
|
|
38038
|
+
if (event.event === "listener_read_recovered" && (typeof event.attempts !== "number" || typeof event.duration_ms !== "number")) {
|
|
38039
|
+
throw new Error("listener read recovery event is incomplete");
|
|
38040
|
+
}
|
|
37339
38041
|
await ensureSecureStateDirectory(paths.instanceDirectory);
|
|
37340
38042
|
const serialized = `${JSON.stringify(event)}
|
|
37341
38043
|
`;
|
|
@@ -37620,6 +38322,28 @@ function localDiagnostic(message, maxChars) {
|
|
|
37620
38322
|
function safeErrorDetail(error) {
|
|
37621
38323
|
return localDiagnostic(error.message, 2048);
|
|
37622
38324
|
}
|
|
38325
|
+
function providerStatusFields(notice) {
|
|
38326
|
+
if (!notice) return {};
|
|
38327
|
+
return {
|
|
38328
|
+
providerExecutable: notice.executable ?? null,
|
|
38329
|
+
providerVersion: notice.runningVersion,
|
|
38330
|
+
providerLastMeasuredVersion: notice.runningVersion === null ? null : notice.lastMeasuredVersion,
|
|
38331
|
+
providerBundledAgentSdkVersion: notice.bundledAgentSdkVersion ?? null,
|
|
38332
|
+
providerBundledClaudeCodeVersion: notice.bundledClaudeCodeVersion ?? null
|
|
38333
|
+
};
|
|
38334
|
+
}
|
|
38335
|
+
function providerFailureFields(error) {
|
|
38336
|
+
if (!(error instanceof AcpPermissionCanaryError)) {
|
|
38337
|
+
return {
|
|
38338
|
+
lastErrorReasonCode: null,
|
|
38339
|
+
providerMinimumRequiredVersion: null
|
|
38340
|
+
};
|
|
38341
|
+
}
|
|
38342
|
+
return {
|
|
38343
|
+
lastErrorReasonCode: error.reasonCode,
|
|
38344
|
+
providerMinimumRequiredVersion: error.minimumRequiredVersion
|
|
38345
|
+
};
|
|
38346
|
+
}
|
|
37623
38347
|
var TAIL_SERIALIZED_BUDGET_BYTES = 3e3;
|
|
37624
38348
|
function fitWorkerStderrTailForLog(tail) {
|
|
37625
38349
|
let fitted = tail.trim();
|
|
@@ -37646,6 +38370,7 @@ async function runListenerSupervisor(options) {
|
|
|
37646
38370
|
version: 1,
|
|
37647
38371
|
instanceId: proposedInstanceId,
|
|
37648
38372
|
provider: options.provider ?? "grok",
|
|
38373
|
+
...options.cswarmVersion ? { cswarmVersion: options.cswarmVersion } : {},
|
|
37649
38374
|
...options.permissionMode ? { permissionMode: options.permissionMode } : {},
|
|
37650
38375
|
profileId: options.profileId,
|
|
37651
38376
|
workspaceId: options.workspaceId.toLowerCase(),
|
|
@@ -37659,8 +38384,13 @@ async function runListenerSupervisor(options) {
|
|
|
37659
38384
|
lastSignalId: null,
|
|
37660
38385
|
lastErrorCode: null,
|
|
37661
38386
|
lastErrorDetail: null,
|
|
38387
|
+
lastErrorReasonCode: null,
|
|
38388
|
+
providerExecutable: null,
|
|
37662
38389
|
providerVersion: null,
|
|
37663
38390
|
providerLastMeasuredVersion: null,
|
|
38391
|
+
providerBundledAgentSdkVersion: null,
|
|
38392
|
+
providerBundledClaudeCodeVersion: null,
|
|
38393
|
+
providerMinimumRequiredVersion: null,
|
|
37664
38394
|
lastWorkerStderrTail: null,
|
|
37665
38395
|
deliveryMode: null,
|
|
37666
38396
|
pendingDeliveryCount: null,
|
|
@@ -37672,14 +38402,27 @@ async function runListenerSupervisor(options) {
|
|
|
37672
38402
|
deferOverChars: options.deferOverChars ?? null,
|
|
37673
38403
|
pendingForMainCount: 0,
|
|
37674
38404
|
droppedForMainCount: 0,
|
|
38405
|
+
readHealth: emptyListenerReadHealth(),
|
|
38406
|
+
connectionsOpened: 0,
|
|
38407
|
+
connectionReuseRatio: 0,
|
|
37675
38408
|
logPath: options.paths.logPath
|
|
37676
38409
|
};
|
|
37677
38410
|
let writes = Promise.resolve();
|
|
37678
38411
|
const chain = (work) => {
|
|
37679
38412
|
writes = writes.then(work).catch(() => void 0);
|
|
37680
38413
|
};
|
|
38414
|
+
const statusSnapshot = () => {
|
|
38415
|
+
const metrics = options.getConnectionMetrics?.();
|
|
38416
|
+
return {
|
|
38417
|
+
...structuredClone(status),
|
|
38418
|
+
...metrics ? {
|
|
38419
|
+
connectionsOpened: metrics.connectionsOpened,
|
|
38420
|
+
connectionReuseRatio: metrics.connectionReuseRatio
|
|
38421
|
+
} : {}
|
|
38422
|
+
};
|
|
38423
|
+
};
|
|
37681
38424
|
const persist = () => {
|
|
37682
|
-
const snapshot =
|
|
38425
|
+
const snapshot = statusSnapshot();
|
|
37683
38426
|
chain(() => writeListenerStatus(options.paths, snapshot));
|
|
37684
38427
|
};
|
|
37685
38428
|
const log = (event) => {
|
|
@@ -37697,7 +38440,7 @@ async function runListenerSupervisor(options) {
|
|
|
37697
38440
|
const prepare = options.prepare;
|
|
37698
38441
|
const control = await startListenerControlServer({
|
|
37699
38442
|
paths: options.paths,
|
|
37700
|
-
status:
|
|
38443
|
+
status: statusSnapshot,
|
|
37701
38444
|
stop: () => {
|
|
37702
38445
|
if (status.state === "stopped" || status.state === "failed") return;
|
|
37703
38446
|
transition("stopping");
|
|
@@ -37735,9 +38478,16 @@ async function runListenerSupervisor(options) {
|
|
|
37735
38478
|
readyAt: event.ts,
|
|
37736
38479
|
lastErrorCode: null,
|
|
37737
38480
|
lastErrorDetail: null,
|
|
38481
|
+
lastErrorReasonCode: null,
|
|
37738
38482
|
lastWorkerStderrTail: null,
|
|
37739
|
-
|
|
37740
|
-
|
|
38483
|
+
providerMinimumRequiredVersion: null,
|
|
38484
|
+
...providerStatusFields(versionNotice),
|
|
38485
|
+
...event.cadenceMs === void 0 ? {} : {
|
|
38486
|
+
readHealth: recordListenerClaimCadence(
|
|
38487
|
+
status.readHealth ?? emptyListenerReadHealth(),
|
|
38488
|
+
event.cadenceMs
|
|
38489
|
+
)
|
|
38490
|
+
}
|
|
37741
38491
|
});
|
|
37742
38492
|
log({ ts: event.ts, event: "listener_ready" });
|
|
37743
38493
|
return;
|
|
@@ -37779,14 +38529,54 @@ async function runListenerSupervisor(options) {
|
|
|
37779
38529
|
return;
|
|
37780
38530
|
}
|
|
37781
38531
|
if (event.type === "read_retry") {
|
|
38532
|
+
status = {
|
|
38533
|
+
...status,
|
|
38534
|
+
readHealth: recordListenerReadRetry(
|
|
38535
|
+
status.readHealth ?? emptyListenerReadHealth(),
|
|
38536
|
+
{
|
|
38537
|
+
ts: event.ts,
|
|
38538
|
+
episodeStartedAt: event.episodeStartedAt,
|
|
38539
|
+
episodeAttempt: event.episodeAttempt,
|
|
38540
|
+
failure: event.failure
|
|
38541
|
+
}
|
|
38542
|
+
),
|
|
38543
|
+
updatedAt: event.ts
|
|
38544
|
+
};
|
|
38545
|
+
persist();
|
|
37782
38546
|
log({
|
|
37783
38547
|
ts: event.ts,
|
|
37784
38548
|
event: "listener_read_retry",
|
|
37785
38549
|
attempt: event.attempt,
|
|
38550
|
+
episode_attempt: event.episodeAttempt,
|
|
38551
|
+
reason_code: event.failure.code,
|
|
38552
|
+
...event.failure.httpStatus === null ? {} : { http_status: event.failure.httpStatus },
|
|
38553
|
+
...event.failure.errorConstructor === null ? {} : { error_constructor: event.failure.errorConstructor },
|
|
37786
38554
|
delay_ms: event.delayMs
|
|
37787
38555
|
});
|
|
37788
38556
|
return;
|
|
37789
38557
|
}
|
|
38558
|
+
if (event.type === "read_recovered") {
|
|
38559
|
+
status = {
|
|
38560
|
+
...status,
|
|
38561
|
+
readHealth: recordListenerReadRecovery(
|
|
38562
|
+
status.readHealth ?? emptyListenerReadHealth(),
|
|
38563
|
+
{
|
|
38564
|
+
startedAt: event.startedAt,
|
|
38565
|
+
attempts: event.attempts,
|
|
38566
|
+
durationMs: event.durationMs
|
|
38567
|
+
}
|
|
38568
|
+
),
|
|
38569
|
+
updatedAt: event.ts
|
|
38570
|
+
};
|
|
38571
|
+
persist();
|
|
38572
|
+
log({
|
|
38573
|
+
ts: event.ts,
|
|
38574
|
+
event: "listener_read_recovered",
|
|
38575
|
+
attempts: event.attempts,
|
|
38576
|
+
duration_ms: event.durationMs
|
|
38577
|
+
});
|
|
38578
|
+
return;
|
|
38579
|
+
}
|
|
37790
38580
|
if (event.type === "malformed_row") {
|
|
37791
38581
|
log({
|
|
37792
38582
|
ts: event.ts,
|
|
@@ -37823,6 +38613,10 @@ async function runListenerSupervisor(options) {
|
|
|
37823
38613
|
if (event.type === "delivery_claim") {
|
|
37824
38614
|
status = {
|
|
37825
38615
|
...status,
|
|
38616
|
+
readHealth: recordListenerClaim(
|
|
38617
|
+
status.readHealth ?? emptyListenerReadHealth(),
|
|
38618
|
+
event.ts
|
|
38619
|
+
),
|
|
37826
38620
|
pendingDeliveryCount: event.pendingDeliveryCount,
|
|
37827
38621
|
lastClaimAt: event.ts,
|
|
37828
38622
|
updatedAt: event.ts
|
|
@@ -37947,9 +38741,9 @@ async function runListenerSupervisor(options) {
|
|
|
37947
38741
|
readyAt: null,
|
|
37948
38742
|
lastErrorCode: restartCode,
|
|
37949
38743
|
lastErrorDetail: safeErrorDetail(stop.error),
|
|
38744
|
+
...providerFailureFields(stop.error),
|
|
37950
38745
|
lastWorkerStderrTail: restartStderrTail,
|
|
37951
|
-
|
|
37952
|
-
providerLastMeasuredVersion: null
|
|
38746
|
+
...providerStatusFields(options.getProviderVersionNotice?.() ?? null)
|
|
37953
38747
|
});
|
|
37954
38748
|
await restartSleep(delayMs, controller.signal);
|
|
37955
38749
|
if (controller.signal.aborted) {
|
|
@@ -37963,7 +38757,9 @@ async function runListenerSupervisor(options) {
|
|
|
37963
38757
|
stoppedAt,
|
|
37964
38758
|
lastErrorCode: null,
|
|
37965
38759
|
lastErrorDetail: null,
|
|
37966
|
-
|
|
38760
|
+
lastErrorReasonCode: null,
|
|
38761
|
+
lastWorkerStderrTail: null,
|
|
38762
|
+
providerMinimumRequiredVersion: null
|
|
37967
38763
|
});
|
|
37968
38764
|
log({ ts: stoppedAt, event: "listener_stopped" });
|
|
37969
38765
|
} else {
|
|
@@ -37973,6 +38769,8 @@ async function runListenerSupervisor(options) {
|
|
|
37973
38769
|
stoppedAt,
|
|
37974
38770
|
lastErrorCode: code,
|
|
37975
38771
|
lastErrorDetail: safeErrorDetail(stop.error),
|
|
38772
|
+
...providerFailureFields(stop.error),
|
|
38773
|
+
...providerStatusFields(options.getProviderVersionNotice?.() ?? null),
|
|
37976
38774
|
lastWorkerStderrTail: failedStderrTail
|
|
37977
38775
|
});
|
|
37978
38776
|
log({
|
|
@@ -37997,6 +38795,10 @@ async function runListenerSupervisor(options) {
|
|
|
37997
38795
|
lastErrorDetail: safeErrorDetail(
|
|
37998
38796
|
error instanceof Error ? error : new Error(String(error))
|
|
37999
38797
|
),
|
|
38798
|
+
...providerFailureFields(
|
|
38799
|
+
error instanceof Error ? error : new Error(String(error))
|
|
38800
|
+
),
|
|
38801
|
+
...providerStatusFields(options.getProviderVersionNotice?.() ?? null),
|
|
38000
38802
|
lastWorkerStderrTail: failedStderrTail
|
|
38001
38803
|
});
|
|
38002
38804
|
log({
|
|
@@ -38009,7 +38811,7 @@ async function runListenerSupervisor(options) {
|
|
|
38009
38811
|
await writes.catch(() => void 0);
|
|
38010
38812
|
await control.close().catch(() => void 0);
|
|
38011
38813
|
}
|
|
38012
|
-
return
|
|
38814
|
+
return statusSnapshot();
|
|
38013
38815
|
}
|
|
38014
38816
|
async function effectiveListenerStatus(paths) {
|
|
38015
38817
|
try {
|
|
@@ -38242,7 +39044,7 @@ function assertPlainObject(input, allowedKeys, requiredKeys, errMessage = "deliv
|
|
|
38242
39044
|
throw new Error(errMessage);
|
|
38243
39045
|
}
|
|
38244
39046
|
}
|
|
38245
|
-
function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
39047
|
+
function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId, rejectUnknownKeys = false) {
|
|
38246
39048
|
if (Buffer.byteLength(raw, "utf8") > MAX_JOURNAL_BYTES) {
|
|
38247
39049
|
throw new Error("stored delivery journal is malformed");
|
|
38248
39050
|
}
|
|
@@ -38263,7 +39065,7 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
38263
39065
|
throw new Error("stored delivery journal is malformed");
|
|
38264
39066
|
}
|
|
38265
39067
|
const ownProps = Object.getOwnPropertyNames(value);
|
|
38266
|
-
if (
|
|
39068
|
+
if ([...ALLOWED_TOP_KEYS].some((key2) => !ownProps.includes(key2)) || rejectUnknownKeys && !ownProps.every((key2) => ALLOWED_TOP_KEYS.has(key2))) {
|
|
38267
39069
|
throw new Error("stored delivery journal is malformed");
|
|
38268
39070
|
}
|
|
38269
39071
|
for (const prop of ownProps) {
|
|
@@ -38297,8 +39099,16 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
38297
39099
|
if (!isValidIsoTimestamp(row.updatedAt)) {
|
|
38298
39100
|
throw new Error("stored delivery journal is malformed");
|
|
38299
39101
|
}
|
|
39102
|
+
const base = {
|
|
39103
|
+
version: 1,
|
|
39104
|
+
workspaceId: row.workspaceId,
|
|
39105
|
+
principalId: row.principalId,
|
|
39106
|
+
listenerInstanceId: row.listenerInstanceId,
|
|
39107
|
+
nextClaimOrdinal: row.nextClaimOrdinal,
|
|
39108
|
+
updatedAt: row.updatedAt
|
|
39109
|
+
};
|
|
38300
39110
|
if (row.active === null) {
|
|
38301
|
-
return
|
|
39111
|
+
return { ...base, active: null };
|
|
38302
39112
|
}
|
|
38303
39113
|
if (typeof row.active !== "object" || row.active === null || Array.isArray(row.active)) {
|
|
38304
39114
|
throw new Error("stored delivery journal is malformed");
|
|
@@ -38311,8 +39121,10 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
38311
39121
|
throw new Error("stored delivery journal is malformed");
|
|
38312
39122
|
}
|
|
38313
39123
|
const activeProps = Object.getOwnPropertyNames(row.active);
|
|
38314
|
-
const
|
|
38315
|
-
|
|
39124
|
+
const requiredActiveKeys = [...ALLOWED_ACTIVE_KEYS].filter(
|
|
39125
|
+
(key2) => key2 !== "signalFingerprint"
|
|
39126
|
+
);
|
|
39127
|
+
if (requiredActiveKeys.some((key2) => !activeProps.includes(key2)) || rejectUnknownKeys && !activeProps.every((key2) => ALLOWED_ACTIVE_KEYS.has(key2))) {
|
|
38316
39128
|
throw new Error("stored delivery journal is malformed");
|
|
38317
39129
|
}
|
|
38318
39130
|
for (const prop of activeProps) {
|
|
@@ -38380,7 +39192,7 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
38380
39192
|
throw new Error("stored delivery journal is malformed");
|
|
38381
39193
|
}
|
|
38382
39194
|
const ackProps = Object.getOwnPropertyNames(active.ack);
|
|
38383
|
-
if (
|
|
39195
|
+
if ([...ALLOWED_ACK_KEYS].some((key2) => !ackProps.includes(key2)) || rejectUnknownKeys && !ackProps.every((key2) => ALLOWED_ACK_KEYS.has(key2))) {
|
|
38384
39196
|
throw new Error("stored delivery journal is malformed");
|
|
38385
39197
|
}
|
|
38386
39198
|
for (const prop of ackProps) {
|
|
@@ -38389,32 +39201,52 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
38389
39201
|
throw new Error("stored delivery journal is malformed");
|
|
38390
39202
|
}
|
|
38391
39203
|
}
|
|
38392
|
-
const
|
|
38393
|
-
if (typeof
|
|
39204
|
+
const ack2 = active.ack;
|
|
39205
|
+
if (typeof ack2.commandId !== "string" || !COMMAND_ID_RE3.test(ack2.commandId)) {
|
|
38394
39206
|
throw new Error("stored delivery journal is malformed");
|
|
38395
39207
|
}
|
|
38396
39208
|
const expectedAckCmdId = ackCommandId(active.leaseId);
|
|
38397
|
-
if (
|
|
39209
|
+
if (ack2.commandId !== expectedAckCmdId) {
|
|
38398
39210
|
throw new Error("stored delivery journal is malformed");
|
|
38399
39211
|
}
|
|
38400
|
-
if (typeof
|
|
39212
|
+
if (typeof ack2.outcome !== "string" || !ALLOWED_OUTCOMES.has(ack2.outcome)) {
|
|
38401
39213
|
throw new Error("stored delivery journal is malformed");
|
|
38402
39214
|
}
|
|
38403
|
-
if (
|
|
38404
|
-
if (typeof
|
|
39215
|
+
if (ack2.outcome === "failed_terminal") {
|
|
39216
|
+
if (typeof ack2.lastErrorCode !== "string" || !ALLOWED_ERROR_CODES.has(ack2.lastErrorCode)) {
|
|
38405
39217
|
throw new Error("stored delivery journal is malformed");
|
|
38406
39218
|
}
|
|
38407
39219
|
} else {
|
|
38408
|
-
if (
|
|
39220
|
+
if (ack2.lastErrorCode !== null) {
|
|
38409
39221
|
throw new Error("stored delivery journal is malformed");
|
|
38410
39222
|
}
|
|
38411
39223
|
}
|
|
38412
|
-
if (!isValidIsoTimestamp(
|
|
39224
|
+
if (!isValidIsoTimestamp(ack2.preparedAt)) {
|
|
38413
39225
|
throw new Error("stored delivery journal is malformed");
|
|
38414
39226
|
}
|
|
38415
39227
|
}
|
|
38416
39228
|
}
|
|
38417
|
-
|
|
39229
|
+
const ack = active.ack;
|
|
39230
|
+
return {
|
|
39231
|
+
...base,
|
|
39232
|
+
active: {
|
|
39233
|
+
phase: active.phase,
|
|
39234
|
+
claimOrdinal: active.claimOrdinal,
|
|
39235
|
+
claimCommandId: active.claimCommandId,
|
|
39236
|
+
claimCreatedAt: active.claimCreatedAt,
|
|
39237
|
+
claimLastAttemptAt: active.claimLastAttemptAt,
|
|
39238
|
+
signalId: active.signalId,
|
|
39239
|
+
leaseId: active.leaseId,
|
|
39240
|
+
leasedUntil: active.leasedUntil,
|
|
39241
|
+
...active.signalFingerprint === void 0 ? {} : { signalFingerprint: active.signalFingerprint },
|
|
39242
|
+
ack: ack === null ? null : {
|
|
39243
|
+
commandId: ack.commandId,
|
|
39244
|
+
outcome: ack.outcome,
|
|
39245
|
+
lastErrorCode: ack.lastErrorCode,
|
|
39246
|
+
preparedAt: ack.preparedAt
|
|
39247
|
+
}
|
|
39248
|
+
}
|
|
39249
|
+
};
|
|
38418
39250
|
}
|
|
38419
39251
|
var FileListenerDeliveryJournal = class {
|
|
38420
39252
|
instanceDirectory;
|
|
@@ -38478,7 +39310,8 @@ var FileListenerDeliveryJournal = class {
|
|
|
38478
39310
|
parseJournalRecord(
|
|
38479
39311
|
serialized,
|
|
38480
39312
|
this.options.workspaceId,
|
|
38481
|
-
this.options.principalId
|
|
39313
|
+
this.options.principalId,
|
|
39314
|
+
true
|
|
38482
39315
|
);
|
|
38483
39316
|
await writeSecureJsonFile(this.journalPath, serialized);
|
|
38484
39317
|
}
|
|
@@ -38728,7 +39561,8 @@ async function openListenerDeliveryJournal(options) {
|
|
|
38728
39561
|
parseJournalRecord(
|
|
38729
39562
|
serialized2,
|
|
38730
39563
|
workspaceIdSnapshot,
|
|
38731
|
-
principalIdSnapshot
|
|
39564
|
+
principalIdSnapshot,
|
|
39565
|
+
true
|
|
38732
39566
|
);
|
|
38733
39567
|
await writeSecureJsonFile(journal.journalPath, serialized2);
|
|
38734
39568
|
return {
|
|
@@ -38762,7 +39596,8 @@ async function openListenerDeliveryJournal(options) {
|
|
|
38762
39596
|
parseJournalRecord(
|
|
38763
39597
|
serialized,
|
|
38764
39598
|
workspaceIdSnapshot,
|
|
38765
|
-
principalIdSnapshot
|
|
39599
|
+
principalIdSnapshot,
|
|
39600
|
+
true
|
|
38766
39601
|
);
|
|
38767
39602
|
await writeSecureJsonFile(journal.journalPath, serialized);
|
|
38768
39603
|
return {
|
|
@@ -38918,7 +39753,7 @@ function parseState(raw) {
|
|
|
38918
39753
|
}
|
|
38919
39754
|
const row = value;
|
|
38920
39755
|
const topicVersions = row.topicVersions;
|
|
38921
|
-
if (
|
|
39756
|
+
if (row.version !== 1 || typeof row.principalId !== "string" || !UUID_RE20.test(row.principalId) || !topicVersions || typeof topicVersions !== "object" || Array.isArray(topicVersions) || Object.keys(topicVersions).length > MAX_BRAIN_DIGEST_TOPICS) {
|
|
38922
39757
|
throw new Error("stored brain digest state is malformed");
|
|
38923
39758
|
}
|
|
38924
39759
|
for (const [topic, version3] of Object.entries(topicVersions)) {
|
|
@@ -38950,6 +39785,9 @@ function newestFirst(topics) {
|
|
|
38950
39785
|
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || left.topic.localeCompare(right.topic)
|
|
38951
39786
|
);
|
|
38952
39787
|
}
|
|
39788
|
+
function changedTopics(previous, topics) {
|
|
39789
|
+
return previous === null ? newestFirst(topics).slice(0, BRAIN_DIGEST_TOPIC_LIMIT) : topics.filter((topic) => previous.topicVersions[topic.topic] !== topic.version);
|
|
39790
|
+
}
|
|
38953
39791
|
function renderBrainDigest(topicCount, topics) {
|
|
38954
39792
|
if (topics.length === 0) return null;
|
|
38955
39793
|
const names = newestFirst(topics).map((topic) => `${topic.topic} v${topic.version}`).join(", ");
|
|
@@ -38967,6 +39805,19 @@ var FileBrainDigestStore = class {
|
|
|
38967
39805
|
instanceDirectory;
|
|
38968
39806
|
location;
|
|
38969
39807
|
principalId;
|
|
39808
|
+
/** Read the shared high-water without advancing it. */
|
|
39809
|
+
async preview(topics) {
|
|
39810
|
+
const raw = await readSecureJsonFileIfPresent(
|
|
39811
|
+
this.location,
|
|
39812
|
+
MAX_BRAIN_DIGEST_STATE_BYTES
|
|
39813
|
+
);
|
|
39814
|
+
const previous = raw === null ? null : parseState(raw);
|
|
39815
|
+
if (previous !== null && previous.principalId !== this.principalId) {
|
|
39816
|
+
throw new Error("stored brain digest state belongs to another principal");
|
|
39817
|
+
}
|
|
39818
|
+
currentState(this.principalId, topics);
|
|
39819
|
+
return renderBrainDigest(topics.length, changedTopics(previous, topics));
|
|
39820
|
+
}
|
|
38970
39821
|
async consume(topics) {
|
|
38971
39822
|
return await withFileLock(this.instanceDirectory, BRAIN_DIGEST_LOCK, async () => {
|
|
38972
39823
|
const raw = await readSecureJsonFile(
|
|
@@ -38978,9 +39829,7 @@ var FileBrainDigestStore = class {
|
|
|
38978
39829
|
throw new Error("stored brain digest state belongs to another principal");
|
|
38979
39830
|
}
|
|
38980
39831
|
const next = currentState(this.principalId, topics);
|
|
38981
|
-
const changed = previous
|
|
38982
|
-
(topic) => previous.topicVersions[topic.topic] !== topic.version
|
|
38983
|
-
);
|
|
39832
|
+
const changed = changedTopics(previous, topics);
|
|
38984
39833
|
const serialized = JSON.stringify(next);
|
|
38985
39834
|
if (Buffer.byteLength(serialized, "utf8") > MAX_BRAIN_DIGEST_STATE_BYTES) {
|
|
38986
39835
|
throw new Error("brain digest state is larger than this store accepts");
|
|
@@ -39016,7 +39865,26 @@ function exactKeys2(row, keys) {
|
|
|
39016
39865
|
const expected = new Set(keys);
|
|
39017
39866
|
return Object.keys(row).length === expected.size && Object.keys(row).every((key2) => expected.has(key2));
|
|
39018
39867
|
}
|
|
39019
|
-
function
|
|
39868
|
+
function hasRequiredKeys(row, keys) {
|
|
39869
|
+
return keys.every((key2) => Object.hasOwn(row, key2));
|
|
39870
|
+
}
|
|
39871
|
+
var LISTENER_CREDENTIAL_KEYS = [
|
|
39872
|
+
"version",
|
|
39873
|
+
"profileId",
|
|
39874
|
+
"targetUrl",
|
|
39875
|
+
"anonKey",
|
|
39876
|
+
"workspaceId",
|
|
39877
|
+
"principalId",
|
|
39878
|
+
"credential",
|
|
39879
|
+
"updatedAt"
|
|
39880
|
+
];
|
|
39881
|
+
var HOOK_SURFACE_KEYS = /* @__PURE__ */ new Set([
|
|
39882
|
+
"version",
|
|
39883
|
+
"surfacedSignalIds",
|
|
39884
|
+
"reportedDroppedCount",
|
|
39885
|
+
"credentialFailureReported"
|
|
39886
|
+
]);
|
|
39887
|
+
function parseListenerCredential(raw, rejectUnknownKeys = false) {
|
|
39020
39888
|
let value;
|
|
39021
39889
|
try {
|
|
39022
39890
|
value = JSON.parse(raw);
|
|
@@ -39027,16 +39895,7 @@ function parseListenerCredential(raw) {
|
|
|
39027
39895
|
throw new Error("stored listener hook credential is malformed");
|
|
39028
39896
|
}
|
|
39029
39897
|
const row = value;
|
|
39030
|
-
if (!exactKeys2(row, [
|
|
39031
|
-
"version",
|
|
39032
|
-
"profileId",
|
|
39033
|
-
"targetUrl",
|
|
39034
|
-
"anonKey",
|
|
39035
|
-
"workspaceId",
|
|
39036
|
-
"principalId",
|
|
39037
|
-
"credential",
|
|
39038
|
-
"updatedAt"
|
|
39039
|
-
]) || row.version !== 1 || typeof row.profileId !== "string" || !/^[0-9a-f]{24}$/.test(row.profileId) || typeof row.targetUrl !== "string" || typeof row.anonKey !== "string" || row.anonKey.length < 1 || row.anonKey.length > 4096 || typeof row.workspaceId !== "string" || !UUID_RE21.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE21.test(row.principalId) || typeof row.credential !== "string" || !TOKEN_RE.test(row.credential) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt))) {
|
|
39898
|
+
if (!(rejectUnknownKeys ? exactKeys2(row, LISTENER_CREDENTIAL_KEYS) : hasRequiredKeys(row, LISTENER_CREDENTIAL_KEYS)) || row.version !== 1 || typeof row.profileId !== "string" || !/^[0-9a-f]{24}$/.test(row.profileId) || typeof row.targetUrl !== "string" || typeof row.anonKey !== "string" || row.anonKey.length < 1 || row.anonKey.length > 4096 || typeof row.workspaceId !== "string" || !UUID_RE21.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE21.test(row.principalId) || typeof row.credential !== "string" || !TOKEN_RE.test(row.credential) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt))) {
|
|
39040
39899
|
throw new Error("stored listener hook credential is malformed");
|
|
39041
39900
|
}
|
|
39042
39901
|
const target2 = cloudTarget(row.targetUrl, row.anonKey);
|
|
@@ -39067,7 +39926,7 @@ async function writeListenerCredentialState(instanceDirectory, input) {
|
|
|
39067
39926
|
principalId: input.principalId,
|
|
39068
39927
|
credential: input.credential,
|
|
39069
39928
|
updatedAt: new Date(input.now ?? Date.now()).toISOString()
|
|
39070
|
-
}));
|
|
39929
|
+
}), true);
|
|
39071
39930
|
await writeSecureJsonFile(
|
|
39072
39931
|
(0, import_node_path20.join)(instanceDirectory, LISTENER_CREDENTIAL_FILE),
|
|
39073
39932
|
JSON.stringify(record)
|
|
@@ -39083,7 +39942,7 @@ async function readListenerCredentialState(instanceDirectory) {
|
|
|
39083
39942
|
);
|
|
39084
39943
|
return raw === null ? null : parseListenerCredential(raw);
|
|
39085
39944
|
}
|
|
39086
|
-
function parseSurface(raw) {
|
|
39945
|
+
function parseSurface(raw, rejectUnknownKeys = false) {
|
|
39087
39946
|
let value;
|
|
39088
39947
|
try {
|
|
39089
39948
|
value = JSON.parse(raw);
|
|
@@ -39094,9 +39953,7 @@ function parseSurface(raw) {
|
|
|
39094
39953
|
throw new Error("stored listener hook surface state is malformed");
|
|
39095
39954
|
}
|
|
39096
39955
|
const row = value;
|
|
39097
|
-
if (Object.keys(row).some(
|
|
39098
|
-
(key2) => key2 !== "version" && key2 !== "surfacedSignalIds" && key2 !== "reportedDroppedCount" && key2 !== "credentialFailureReported"
|
|
39099
|
-
) || row.version !== 1 || !Array.isArray(row.surfacedSignalIds) || row.surfacedSignalIds.length > HOOK_SURFACED_IDS_MAX || row.surfacedSignalIds.some((id) => typeof id !== "string" || !UUID_RE21.test(id)) || !(row.reportedDroppedCount === void 0 || typeof row.reportedDroppedCount === "number" && Number.isSafeInteger(row.reportedDroppedCount) && row.reportedDroppedCount >= 0) || !(row.credentialFailureReported === void 0 || typeof row.credentialFailureReported === "boolean")) {
|
|
39956
|
+
if (rejectUnknownKeys && Object.keys(row).some((key2) => !HOOK_SURFACE_KEYS.has(key2)) || row.version !== 1 || !Array.isArray(row.surfacedSignalIds) || row.surfacedSignalIds.length > HOOK_SURFACED_IDS_MAX || row.surfacedSignalIds.some((id) => typeof id !== "string" || !UUID_RE21.test(id)) || !(row.reportedDroppedCount === void 0 || typeof row.reportedDroppedCount === "number" && Number.isSafeInteger(row.reportedDroppedCount) && row.reportedDroppedCount >= 0) || !(row.credentialFailureReported === void 0 || typeof row.credentialFailureReported === "boolean")) {
|
|
39100
39957
|
throw new Error("stored listener hook surface state is malformed");
|
|
39101
39958
|
}
|
|
39102
39959
|
const ids = row.surfacedSignalIds.map((id) => String(id).toLowerCase());
|
|
@@ -39120,6 +39977,31 @@ var FileHookSurfaceStore = class {
|
|
|
39120
39977
|
}
|
|
39121
39978
|
instanceDirectory;
|
|
39122
39979
|
path;
|
|
39980
|
+
/** Preview unseen hook rows without taking a write lock or advancing state. */
|
|
39981
|
+
async previewUnseen(items) {
|
|
39982
|
+
const raw = await readSecureJsonFileIfPresent(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
39983
|
+
const seen = new Set(raw === null ? [] : parseSurface(raw).surfacedSignalIds);
|
|
39984
|
+
const unseen = [];
|
|
39985
|
+
for (const item of items) {
|
|
39986
|
+
const signalId = item.signalId.toLowerCase();
|
|
39987
|
+
if (!UUID_RE21.test(signalId) || seen.has(signalId)) continue;
|
|
39988
|
+
seen.add(signalId);
|
|
39989
|
+
unseen.push(item);
|
|
39990
|
+
}
|
|
39991
|
+
return unseen;
|
|
39992
|
+
}
|
|
39993
|
+
/** Read attendance evidence without advancing the hook high-water. */
|
|
39994
|
+
async evidence() {
|
|
39995
|
+
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
39996
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
39997
|
+
if (raw === null) return { exists: false, surfacedSignalIds: [] };
|
|
39998
|
+
const state = parseSurface(raw);
|
|
39999
|
+
return {
|
|
40000
|
+
exists: true,
|
|
40001
|
+
surfacedSignalIds: state.surfacedSignalIds
|
|
40002
|
+
};
|
|
40003
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
40004
|
+
}
|
|
39123
40005
|
async stage(items, droppedCount) {
|
|
39124
40006
|
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
39125
40007
|
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
@@ -39165,7 +40047,7 @@ var FileHookSurfaceStore = class {
|
|
|
39165
40047
|
surfacedSignalIds: [...seen].slice(-HOOK_SURFACED_IDS_MAX),
|
|
39166
40048
|
reportedDroppedCount: options.droppedCount ?? state.reportedDroppedCount,
|
|
39167
40049
|
credentialFailureReported: options.credentialFailureReported ?? state.credentialFailureReported
|
|
39168
|
-
})))
|
|
40050
|
+
}), true))
|
|
39169
40051
|
);
|
|
39170
40052
|
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
39171
40053
|
}
|
|
@@ -39188,7 +40070,7 @@ function parseGlobalState(raw) {
|
|
|
39188
40070
|
throw new Error("stored hook cooldown state is malformed");
|
|
39189
40071
|
}
|
|
39190
40072
|
const row = value;
|
|
39191
|
-
if (!
|
|
40073
|
+
if (!hasRequiredKeys(row, ["version", "lastCheckAt"]) || row.version !== 1 || typeof row.lastCheckAt !== "number" || !Number.isSafeInteger(row.lastCheckAt) || row.lastCheckAt < 0) {
|
|
39192
40074
|
throw new Error("stored hook cooldown state is malformed");
|
|
39193
40075
|
}
|
|
39194
40076
|
return { version: 1, lastCheckAt: row.lastCheckAt };
|
|
@@ -39643,6 +40525,950 @@ async function runListenerHookCheck(options = {}) {
|
|
|
39643
40525
|
}
|
|
39644
40526
|
}
|
|
39645
40527
|
|
|
40528
|
+
// src/listener/attendance-canary.ts
|
|
40529
|
+
var import_promises11 = require("node:fs/promises");
|
|
40530
|
+
var LOG_TAIL_BYTES = 256 * 1024;
|
|
40531
|
+
function agentReceipt(receipts, principalId) {
|
|
40532
|
+
for (const receipt of receipts) {
|
|
40533
|
+
if ("recipient_agent_principal_id" in receipt && receipt.recipient_agent_principal_id === principalId) {
|
|
40534
|
+
return receipt;
|
|
40535
|
+
}
|
|
40536
|
+
}
|
|
40537
|
+
return null;
|
|
40538
|
+
}
|
|
40539
|
+
async function readLogTail(path) {
|
|
40540
|
+
let handle;
|
|
40541
|
+
try {
|
|
40542
|
+
handle = await (0, import_promises11.open)(path, "r");
|
|
40543
|
+
} catch (error) {
|
|
40544
|
+
if (error.code === "ENOENT") return "";
|
|
40545
|
+
throw error;
|
|
40546
|
+
}
|
|
40547
|
+
try {
|
|
40548
|
+
const size2 = (await handle.stat()).size;
|
|
40549
|
+
const start = Math.max(0, size2 - LOG_TAIL_BYTES);
|
|
40550
|
+
const buffer2 = Buffer.alloc(size2 - start);
|
|
40551
|
+
await handle.read(buffer2, 0, buffer2.length, start);
|
|
40552
|
+
let text = buffer2.toString("utf8");
|
|
40553
|
+
if (start > 0) {
|
|
40554
|
+
const newline = text.indexOf("\n");
|
|
40555
|
+
text = newline < 0 ? "" : text.slice(newline + 1);
|
|
40556
|
+
}
|
|
40557
|
+
return text;
|
|
40558
|
+
} finally {
|
|
40559
|
+
await handle.close();
|
|
40560
|
+
}
|
|
40561
|
+
}
|
|
40562
|
+
async function logEvidence(path, signalId) {
|
|
40563
|
+
let claimedAt = null;
|
|
40564
|
+
let routeDecision = null;
|
|
40565
|
+
let routedAt = null;
|
|
40566
|
+
for (const line of (await readLogTail(path)).split("\n")) {
|
|
40567
|
+
if (line.length === 0) continue;
|
|
40568
|
+
let value;
|
|
40569
|
+
try {
|
|
40570
|
+
value = JSON.parse(line);
|
|
40571
|
+
} catch {
|
|
40572
|
+
continue;
|
|
40573
|
+
}
|
|
40574
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
40575
|
+
const row = value;
|
|
40576
|
+
if (row.signal_id !== signalId || typeof row.ts !== "string") continue;
|
|
40577
|
+
if (row.event === "listener_delivery_claim") claimedAt = row.ts;
|
|
40578
|
+
if (row.event === "listener_routing_decision" && (row.route_decision === "main" || row.route_decision === "worker")) {
|
|
40579
|
+
routeDecision = row.route_decision;
|
|
40580
|
+
routedAt = row.ts;
|
|
40581
|
+
}
|
|
40582
|
+
}
|
|
40583
|
+
return { claimedAt, routeDecision, routedAt };
|
|
40584
|
+
}
|
|
40585
|
+
async function runListenerAttendanceCanary(options) {
|
|
40586
|
+
const now = options.now ?? Date.now;
|
|
40587
|
+
const sleep2 = options.sleep ?? ((milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds)));
|
|
40588
|
+
const startedAt = now();
|
|
40589
|
+
const deadlineMs = startedAt + options.waitMs;
|
|
40590
|
+
const client = new ThinCommandClient(options.target, options.fetcher, {
|
|
40591
|
+
signalRequestTimeoutMs: options.waitMs
|
|
40592
|
+
});
|
|
40593
|
+
const posted = await client.sendSignal({
|
|
40594
|
+
workspaceId: options.workspaceId,
|
|
40595
|
+
credential: await options.credential(),
|
|
40596
|
+
command: {
|
|
40597
|
+
kind: "post_signal",
|
|
40598
|
+
signal_kind: "note",
|
|
40599
|
+
body: "CommonSwarm listener attendance canary. No reply is needed.",
|
|
40600
|
+
to_user_id: null,
|
|
40601
|
+
to_agent_principal_id: options.principalId,
|
|
40602
|
+
in_reply_to: null,
|
|
40603
|
+
about: null,
|
|
40604
|
+
until_ms: 10 * 6e4
|
|
40605
|
+
}
|
|
40606
|
+
});
|
|
40607
|
+
const signalId = posted.response.signal.id;
|
|
40608
|
+
let claimedAt = null;
|
|
40609
|
+
let routeDecision = null;
|
|
40610
|
+
let routedAt = null;
|
|
40611
|
+
let pendingForMainCount = null;
|
|
40612
|
+
let surfacedAt = null;
|
|
40613
|
+
let observedAt = null;
|
|
40614
|
+
let receiptReadErrorCode = null;
|
|
40615
|
+
while (true) {
|
|
40616
|
+
const log = await logEvidence(options.paths.logPath, signalId);
|
|
40617
|
+
claimedAt ??= log.claimedAt;
|
|
40618
|
+
routeDecision ??= log.routeDecision;
|
|
40619
|
+
routedAt ??= log.routedAt;
|
|
40620
|
+
const hook = await new FileHookSurfaceStore(
|
|
40621
|
+
options.paths.instanceDirectory
|
|
40622
|
+
).evidence();
|
|
40623
|
+
if (hook.surfacedSignalIds.includes(signalId)) {
|
|
40624
|
+
surfacedAt ??= new Date(now()).toISOString();
|
|
40625
|
+
}
|
|
40626
|
+
if (now() >= deadlineMs) break;
|
|
40627
|
+
try {
|
|
40628
|
+
const report = await readAgentDeliveryReceipts(
|
|
40629
|
+
options.target,
|
|
40630
|
+
await options.credential(),
|
|
40631
|
+
options.workspaceId,
|
|
40632
|
+
signalId,
|
|
40633
|
+
{
|
|
40634
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
40635
|
+
deadlineMs,
|
|
40636
|
+
now
|
|
40637
|
+
}
|
|
40638
|
+
);
|
|
40639
|
+
receiptReadErrorCode = null;
|
|
40640
|
+
const receipt = agentReceipt(report.receipts, options.principalId);
|
|
40641
|
+
if (receipt !== null) {
|
|
40642
|
+
claimedAt ??= receipt.delivered_at;
|
|
40643
|
+
if (receipt.ack_outcome === "queued") {
|
|
40644
|
+
routeDecision ??= "main";
|
|
40645
|
+
routedAt ??= receipt.acked_at;
|
|
40646
|
+
pendingForMainCount = receipt.pending_for_main_count ?? null;
|
|
40647
|
+
}
|
|
40648
|
+
const state = deliveryReceiptState(receipt, now());
|
|
40649
|
+
if (state === "observed" || state === "replied") {
|
|
40650
|
+
observedAt = receipt.acked_at;
|
|
40651
|
+
}
|
|
40652
|
+
}
|
|
40653
|
+
} catch (error) {
|
|
40654
|
+
receiptReadErrorCode = error instanceof DeliveryReceiptReadError ? error.code : error instanceof SignalReadTimeoutError ? "timeout" : "transport";
|
|
40655
|
+
}
|
|
40656
|
+
const complete = claimedAt !== null && routeDecision !== null && (routeDecision === "worker" || surfacedAt !== null) && observedAt !== null;
|
|
40657
|
+
if (complete || now() >= deadlineMs) break;
|
|
40658
|
+
await sleep2(Math.min(options.pollMs ?? 250, deadlineMs - now()));
|
|
40659
|
+
}
|
|
40660
|
+
const stalledAt = claimedAt === null ? "claimed" : routeDecision === null ? "routed" : routeDecision === "main" && surfacedAt === null ? "surfaced" : observedAt === null ? "observed" : null;
|
|
40661
|
+
return {
|
|
40662
|
+
signalId,
|
|
40663
|
+
acceptedAt: new Date(startedAt).toISOString(),
|
|
40664
|
+
claimedAt,
|
|
40665
|
+
routeDecision,
|
|
40666
|
+
routedAt,
|
|
40667
|
+
pendingForMainCount,
|
|
40668
|
+
surfacedAt,
|
|
40669
|
+
observedAt,
|
|
40670
|
+
receiptReadErrorCode,
|
|
40671
|
+
stalledAt
|
|
40672
|
+
};
|
|
40673
|
+
}
|
|
40674
|
+
function renderListenerAttendanceCanary(result, workspaceId2, principalId) {
|
|
40675
|
+
const statusCommand = `cswarm listen status --workspace-id ${workspaceId2} --principal-id ${principalId}`;
|
|
40676
|
+
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";
|
|
40677
|
+
const lines = [
|
|
40678
|
+
`Canary note: ${result.signalId}.`,
|
|
40679
|
+
`ACCEPTED: yes at ${result.acceptedAt}.`,
|
|
40680
|
+
`CLAIMED: ${result.claimedAt === null ? "no" : `yes at ${result.claimedAt}`}.`,
|
|
40681
|
+
`QUEUED/WORKER: ${route}.`,
|
|
40682
|
+
`SURFACED: ${result.routeDecision === "worker" ? "not required for the worker route" : result.surfacedAt === null ? "no" : `yes at ${result.surfacedAt}`}.`,
|
|
40683
|
+
`OBSERVED: ${result.observedAt === null ? "no" : `yes at ${result.observedAt}`}.`
|
|
40684
|
+
];
|
|
40685
|
+
if (result.receiptReadErrorCode !== null) {
|
|
40686
|
+
lines.push(`RECEIPT READ: failed (${result.receiptReadErrorCode}).`);
|
|
40687
|
+
}
|
|
40688
|
+
if (result.stalledAt === null) {
|
|
40689
|
+
lines.push("Canary passed: every required hop was measured.");
|
|
40690
|
+
} else if (result.stalledAt === "surfaced") {
|
|
40691
|
+
lines.push(
|
|
40692
|
+
`STALLED: surfaced. Next: cswarm hook install claude --principal-id ${principalId} --write, then start a fresh session. Or restart the listener with --route worker.`
|
|
40693
|
+
);
|
|
40694
|
+
} else {
|
|
40695
|
+
lines.push(`STALLED: ${result.stalledAt}. Next: ${statusCommand}`);
|
|
40696
|
+
}
|
|
40697
|
+
return lines.join("\n");
|
|
40698
|
+
}
|
|
40699
|
+
|
|
40700
|
+
// src/listener/activity.ts
|
|
40701
|
+
var import_node_crypto20 = require("node:crypto");
|
|
40702
|
+
var ACTIVITY_FRAME_INTERVAL_MS = 750;
|
|
40703
|
+
var ACTIVITY_HEARTBEAT_MS = 15e3;
|
|
40704
|
+
var ACTIVITY_TOOL_TITLE_MAX = 160;
|
|
40705
|
+
var ACTIVITY_REQUEST_TIMEOUT_MS = 5e3;
|
|
40706
|
+
var SYSTEM_CLOCK = {
|
|
40707
|
+
now: Date.now,
|
|
40708
|
+
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
40709
|
+
clearTimer: (timer2) => clearTimeout(timer2)
|
|
40710
|
+
};
|
|
40711
|
+
var TERMINAL_TOOL_STATUSES = /* @__PURE__ */ new Set([
|
|
40712
|
+
"cancelled",
|
|
40713
|
+
"completed",
|
|
40714
|
+
"done",
|
|
40715
|
+
"failed",
|
|
40716
|
+
"rejected"
|
|
40717
|
+
]);
|
|
40718
|
+
var AgentActivityEndpointTransport = class {
|
|
40719
|
+
constructor(target2, credentialSession, fetcher) {
|
|
40720
|
+
this.target = target2;
|
|
40721
|
+
this.credentialSession = credentialSession;
|
|
40722
|
+
this.fetcher = fetcher ?? fetch;
|
|
40723
|
+
}
|
|
40724
|
+
target;
|
|
40725
|
+
credentialSession;
|
|
40726
|
+
fetcher;
|
|
40727
|
+
async publish(frame) {
|
|
40728
|
+
const credential = await this.credentialSession.bearer();
|
|
40729
|
+
const response = await this.fetcher(
|
|
40730
|
+
`${this.target.url}/functions/v1/activity`,
|
|
40731
|
+
{
|
|
40732
|
+
method: "POST",
|
|
40733
|
+
headers: {
|
|
40734
|
+
authorization: `Bearer ${credential}`,
|
|
40735
|
+
apikey: this.target.anonKey,
|
|
40736
|
+
"content-type": "application/json"
|
|
40737
|
+
},
|
|
40738
|
+
body: JSON.stringify({
|
|
40739
|
+
version: frame.version,
|
|
40740
|
+
workspace_id: frame.workspaceId,
|
|
40741
|
+
stream_id: frame.streamId,
|
|
40742
|
+
sequence: frame.sequence,
|
|
40743
|
+
phase: frame.phase,
|
|
40744
|
+
signal_id: frame.signalId,
|
|
40745
|
+
tool_title: frame.toolTitle,
|
|
40746
|
+
elapsed_ms: frame.elapsedMs
|
|
40747
|
+
}),
|
|
40748
|
+
signal: AbortSignal.timeout(ACTIVITY_REQUEST_TIMEOUT_MS)
|
|
40749
|
+
}
|
|
40750
|
+
);
|
|
40751
|
+
await response.body?.cancel();
|
|
40752
|
+
if (!response.ok) {
|
|
40753
|
+
throw new Error(`activity publish failed (${response.status})`);
|
|
40754
|
+
}
|
|
40755
|
+
}
|
|
40756
|
+
};
|
|
40757
|
+
var ListenerActivityController = class {
|
|
40758
|
+
constructor(options) {
|
|
40759
|
+
this.options = options;
|
|
40760
|
+
this.clock = options.clock ?? SYSTEM_CLOCK;
|
|
40761
|
+
this.streamId = options.streamId ?? (0, import_node_crypto20.randomUUID)();
|
|
40762
|
+
this.events = { update: (update) => this.onSessionUpdate(update) };
|
|
40763
|
+
}
|
|
40764
|
+
options;
|
|
40765
|
+
events;
|
|
40766
|
+
clock;
|
|
40767
|
+
streamId;
|
|
40768
|
+
sequence = 0;
|
|
40769
|
+
phase = "idle";
|
|
40770
|
+
signalId = null;
|
|
40771
|
+
signalStartedAt = null;
|
|
40772
|
+
runningTools = /* @__PURE__ */ new Map();
|
|
40773
|
+
latestToolId = null;
|
|
40774
|
+
dirty = false;
|
|
40775
|
+
sending = false;
|
|
40776
|
+
closed = false;
|
|
40777
|
+
lastSentAt = Number.NEGATIVE_INFINITY;
|
|
40778
|
+
timer = null;
|
|
40779
|
+
heartbeatTimer = null;
|
|
40780
|
+
/** Observe the listener state machine without changing its durable effect path. */
|
|
40781
|
+
onRuntimeEvent(event) {
|
|
40782
|
+
if (event.type === "ready") {
|
|
40783
|
+
this.setIdle();
|
|
40784
|
+
} else if (event.type === "delivery_claim" && event.signalId !== null) {
|
|
40785
|
+
this.beginSignal(event.signalId);
|
|
40786
|
+
} else if (event.type === "routing_decision") {
|
|
40787
|
+
this.beginSignal(event.signalId);
|
|
40788
|
+
} else if (event.type === "effect") {
|
|
40789
|
+
this.setIdle();
|
|
40790
|
+
}
|
|
40791
|
+
}
|
|
40792
|
+
/** Wrap one provider model so prompt and reply phases are visible. */
|
|
40793
|
+
instrumentModel(model) {
|
|
40794
|
+
return {
|
|
40795
|
+
start: async () => await model.start(),
|
|
40796
|
+
prompt: async (signal, mode3, prompt, attempt) => {
|
|
40797
|
+
this.beginSignal(signal.id);
|
|
40798
|
+
this.setPhase("prompting");
|
|
40799
|
+
const result = await model.prompt(signal, mode3, prompt, attempt);
|
|
40800
|
+
this.runningTools.clear();
|
|
40801
|
+
this.latestToolId = null;
|
|
40802
|
+
this.setPhase("replying");
|
|
40803
|
+
return result;
|
|
40804
|
+
},
|
|
40805
|
+
cancel: () => model.cancel(),
|
|
40806
|
+
close: async () => await model.close()
|
|
40807
|
+
};
|
|
40808
|
+
}
|
|
40809
|
+
/** Stop pending local timers; a missing next frame becomes stale in the panel. */
|
|
40810
|
+
close() {
|
|
40811
|
+
this.closed = true;
|
|
40812
|
+
if (this.timer !== null) this.clock.clearTimer(this.timer);
|
|
40813
|
+
if (this.heartbeatTimer !== null) this.clock.clearTimer(this.heartbeatTimer);
|
|
40814
|
+
this.timer = null;
|
|
40815
|
+
this.heartbeatTimer = null;
|
|
40816
|
+
}
|
|
40817
|
+
onSessionUpdate(update) {
|
|
40818
|
+
if (this.signalId === null) return;
|
|
40819
|
+
if (update.kind !== "tool_call" && update.kind !== "tool_call_update") return;
|
|
40820
|
+
const id = update.toolCallId;
|
|
40821
|
+
if (!id) return;
|
|
40822
|
+
const status = update.status?.toLowerCase();
|
|
40823
|
+
if (status && TERMINAL_TOOL_STATUSES.has(status)) {
|
|
40824
|
+
this.runningTools.delete(id);
|
|
40825
|
+
if (this.latestToolId === id) {
|
|
40826
|
+
this.latestToolId = [...this.runningTools.keys()].at(-1) ?? null;
|
|
40827
|
+
}
|
|
40828
|
+
this.setPhase(this.runningTools.size > 0 ? "tool-running" : "prompting");
|
|
40829
|
+
return;
|
|
40830
|
+
}
|
|
40831
|
+
const sanitizedTitle = update.title === void 0 ? this.runningTools.get(id) : sanitizeText(update.title).slice(0, ACTIVITY_TOOL_TITLE_MAX);
|
|
40832
|
+
const title = sanitizedTitle && sanitizedTitle.length > 0 ? sanitizedTitle : "Tool";
|
|
40833
|
+
this.runningTools.set(id, title);
|
|
40834
|
+
this.latestToolId = id;
|
|
40835
|
+
this.setPhase("tool-running");
|
|
40836
|
+
}
|
|
40837
|
+
beginSignal(signalId) {
|
|
40838
|
+
if (this.signalId === signalId) return;
|
|
40839
|
+
this.signalId = signalId;
|
|
40840
|
+
this.signalStartedAt = this.clock.now();
|
|
40841
|
+
this.runningTools.clear();
|
|
40842
|
+
this.latestToolId = null;
|
|
40843
|
+
this.setPhase("claimed");
|
|
40844
|
+
}
|
|
40845
|
+
setIdle() {
|
|
40846
|
+
this.signalId = null;
|
|
40847
|
+
this.signalStartedAt = null;
|
|
40848
|
+
this.runningTools.clear();
|
|
40849
|
+
this.latestToolId = null;
|
|
40850
|
+
this.setPhase("idle");
|
|
40851
|
+
}
|
|
40852
|
+
setPhase(phase) {
|
|
40853
|
+
this.phase = phase;
|
|
40854
|
+
if (this.heartbeatTimer !== null) {
|
|
40855
|
+
this.clock.clearTimer(this.heartbeatTimer);
|
|
40856
|
+
this.heartbeatTimer = null;
|
|
40857
|
+
}
|
|
40858
|
+
this.dirty = true;
|
|
40859
|
+
this.schedule();
|
|
40860
|
+
}
|
|
40861
|
+
armHeartbeat() {
|
|
40862
|
+
if (this.closed || this.heartbeatTimer !== null) return;
|
|
40863
|
+
this.heartbeatTimer = this.clock.setTimer(() => {
|
|
40864
|
+
this.heartbeatTimer = null;
|
|
40865
|
+
this.dirty = true;
|
|
40866
|
+
this.schedule();
|
|
40867
|
+
}, ACTIVITY_HEARTBEAT_MS);
|
|
40868
|
+
}
|
|
40869
|
+
schedule() {
|
|
40870
|
+
if (this.closed || this.timer !== null || this.sending) return;
|
|
40871
|
+
const delay2 = Math.max(
|
|
40872
|
+
0,
|
|
40873
|
+
this.lastSentAt + ACTIVITY_FRAME_INTERVAL_MS - this.clock.now()
|
|
40874
|
+
);
|
|
40875
|
+
if (delay2 === 0) {
|
|
40876
|
+
void this.flush();
|
|
40877
|
+
return;
|
|
40878
|
+
}
|
|
40879
|
+
this.timer = this.clock.setTimer(() => {
|
|
40880
|
+
this.timer = null;
|
|
40881
|
+
void this.flush();
|
|
40882
|
+
}, delay2);
|
|
40883
|
+
}
|
|
40884
|
+
async flush() {
|
|
40885
|
+
if (this.closed || this.sending || !this.dirty) return;
|
|
40886
|
+
this.dirty = false;
|
|
40887
|
+
this.sending = true;
|
|
40888
|
+
this.lastSentAt = this.clock.now();
|
|
40889
|
+
const toolTitle = this.latestToolId === null ? null : this.runningTools.get(this.latestToolId) ?? null;
|
|
40890
|
+
const frame = {
|
|
40891
|
+
version: 1,
|
|
40892
|
+
workspaceId: this.options.workspaceId,
|
|
40893
|
+
streamId: this.streamId,
|
|
40894
|
+
sequence: ++this.sequence,
|
|
40895
|
+
phase: this.phase,
|
|
40896
|
+
signalId: this.signalId,
|
|
40897
|
+
toolTitle,
|
|
40898
|
+
elapsedMs: this.signalStartedAt === null ? 0 : Math.max(0, Math.round(this.clock.now() - this.signalStartedAt))
|
|
40899
|
+
};
|
|
40900
|
+
try {
|
|
40901
|
+
await this.options.transport.publish(frame);
|
|
40902
|
+
} catch {
|
|
40903
|
+
} finally {
|
|
40904
|
+
this.sending = false;
|
|
40905
|
+
if (this.dirty) this.schedule();
|
|
40906
|
+
else this.armHeartbeat();
|
|
40907
|
+
}
|
|
40908
|
+
}
|
|
40909
|
+
};
|
|
40910
|
+
|
|
40911
|
+
// src/listener/http-client.ts
|
|
40912
|
+
var import_node_http2 = require("node:http");
|
|
40913
|
+
var import_node_https = require("node:https");
|
|
40914
|
+
var import_node_zlib = require("node:zlib");
|
|
40915
|
+
var LISTENER_HTTP_IDLE_TIMEOUT_MS = 6e4;
|
|
40916
|
+
function responseHeaders(message) {
|
|
40917
|
+
const headers = new Headers();
|
|
40918
|
+
for (let index = 0; index < message.rawHeaders.length; index += 2) {
|
|
40919
|
+
const name = message.rawHeaders[index];
|
|
40920
|
+
const value = message.rawHeaders[index + 1];
|
|
40921
|
+
if (name !== void 0 && value !== void 0) headers.append(name, value);
|
|
40922
|
+
}
|
|
40923
|
+
return headers;
|
|
40924
|
+
}
|
|
40925
|
+
function decodeResponseBody(bytes, headers) {
|
|
40926
|
+
const codings = (headers.get("content-encoding") ?? "").split(",").map((coding) => coding.trim().toLowerCase()).filter((coding) => coding !== "" && coding !== "identity");
|
|
40927
|
+
if (!codings.every((coding) => ["br", "deflate", "gzip", "x-gzip"].includes(coding))) {
|
|
40928
|
+
return Uint8Array.from(bytes).buffer;
|
|
40929
|
+
}
|
|
40930
|
+
let decoded = bytes;
|
|
40931
|
+
for (const coding of codings.reverse()) {
|
|
40932
|
+
if (coding === "br") decoded = (0, import_node_zlib.brotliDecompressSync)(decoded);
|
|
40933
|
+
else if (coding === "deflate") decoded = (0, import_node_zlib.inflateSync)(decoded);
|
|
40934
|
+
else decoded = (0, import_node_zlib.gunzipSync)(decoded);
|
|
40935
|
+
}
|
|
40936
|
+
return Uint8Array.from(decoded).buffer;
|
|
40937
|
+
}
|
|
40938
|
+
var ListenerHttpClient = class {
|
|
40939
|
+
fetch;
|
|
40940
|
+
httpAgent = new import_node_http2.Agent({
|
|
40941
|
+
keepAlive: true,
|
|
40942
|
+
maxSockets: 1,
|
|
40943
|
+
maxFreeSockets: 1,
|
|
40944
|
+
scheduling: "fifo"
|
|
40945
|
+
});
|
|
40946
|
+
httpsAgent = new import_node_https.Agent({
|
|
40947
|
+
keepAlive: true,
|
|
40948
|
+
maxSockets: 1,
|
|
40949
|
+
maxFreeSockets: 1,
|
|
40950
|
+
scheduling: "fifo"
|
|
40951
|
+
});
|
|
40952
|
+
idleTimeoutMs;
|
|
40953
|
+
sockets = /* @__PURE__ */ new WeakSet();
|
|
40954
|
+
requestCount = 0;
|
|
40955
|
+
openedCount = 0;
|
|
40956
|
+
activeRequests = 0;
|
|
40957
|
+
idleTimer = null;
|
|
40958
|
+
closed = false;
|
|
40959
|
+
constructor(options = {}) {
|
|
40960
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? LISTENER_HTTP_IDLE_TIMEOUT_MS;
|
|
40961
|
+
if (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs <= 0) {
|
|
40962
|
+
throw new Error("listener HTTP idle timeout must be a positive integer");
|
|
40963
|
+
}
|
|
40964
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
40965
|
+
this.fetch = this.request.bind(this);
|
|
40966
|
+
}
|
|
40967
|
+
/** Snapshot process-local counts without exposing the agents themselves. */
|
|
40968
|
+
metrics() {
|
|
40969
|
+
return {
|
|
40970
|
+
requests: this.requestCount,
|
|
40971
|
+
connectionsOpened: this.openedCount,
|
|
40972
|
+
connectionReuseRatio: this.openedCount === 0 ? 0 : this.requestCount / this.openedCount
|
|
40973
|
+
};
|
|
40974
|
+
}
|
|
40975
|
+
/** Close every idle or active socket when the listener process stops. */
|
|
40976
|
+
close() {
|
|
40977
|
+
if (this.closed) return;
|
|
40978
|
+
this.closed = true;
|
|
40979
|
+
this.clearIdleTimer();
|
|
40980
|
+
this.destroyAgents();
|
|
40981
|
+
}
|
|
40982
|
+
clearIdleTimer() {
|
|
40983
|
+
if (this.idleTimer === null) return;
|
|
40984
|
+
clearTimeout(this.idleTimer);
|
|
40985
|
+
this.idleTimer = null;
|
|
40986
|
+
}
|
|
40987
|
+
destroyAgents() {
|
|
40988
|
+
this.httpAgent.destroy();
|
|
40989
|
+
this.httpsAgent.destroy();
|
|
40990
|
+
}
|
|
40991
|
+
beginRequest() {
|
|
40992
|
+
if (this.closed) throw new Error("listener HTTP client is closed");
|
|
40993
|
+
this.clearIdleTimer();
|
|
40994
|
+
this.requestCount += 1;
|
|
40995
|
+
this.activeRequests += 1;
|
|
40996
|
+
}
|
|
40997
|
+
finishRequest() {
|
|
40998
|
+
this.activeRequests -= 1;
|
|
40999
|
+
if (this.activeRequests !== 0 || this.closed) return;
|
|
41000
|
+
this.idleTimer = setTimeout(() => {
|
|
41001
|
+
this.idleTimer = null;
|
|
41002
|
+
if (this.activeRequests === 0 && !this.closed) this.destroyAgents();
|
|
41003
|
+
}, this.idleTimeoutMs);
|
|
41004
|
+
this.idleTimer.unref?.();
|
|
41005
|
+
}
|
|
41006
|
+
trackSocket(request) {
|
|
41007
|
+
request.once("socket", (socket) => {
|
|
41008
|
+
if (this.sockets.has(socket)) return;
|
|
41009
|
+
this.sockets.add(socket);
|
|
41010
|
+
const opened = () => {
|
|
41011
|
+
this.openedCount += 1;
|
|
41012
|
+
};
|
|
41013
|
+
if (socket.connecting) socket.once("connect", opened);
|
|
41014
|
+
else opened();
|
|
41015
|
+
});
|
|
41016
|
+
}
|
|
41017
|
+
async request(input, init) {
|
|
41018
|
+
const webRequest = new Request(input, init);
|
|
41019
|
+
const body = webRequest.method === "GET" || webRequest.method === "HEAD" ? null : Buffer.from(await webRequest.arrayBuffer());
|
|
41020
|
+
let url = new URL(webRequest.url);
|
|
41021
|
+
let method = webRequest.method;
|
|
41022
|
+
let headers = new Headers(webRequest.headers);
|
|
41023
|
+
let redirected = false;
|
|
41024
|
+
for (let redirects = 0; ; redirects += 1) {
|
|
41025
|
+
const response = await this.sendOnce({
|
|
41026
|
+
url,
|
|
41027
|
+
method,
|
|
41028
|
+
headers,
|
|
41029
|
+
body: method === "GET" || method === "HEAD" ? null : body,
|
|
41030
|
+
signal: webRequest.signal,
|
|
41031
|
+
redirected
|
|
41032
|
+
});
|
|
41033
|
+
const location2 = response.headers.get("location");
|
|
41034
|
+
if (location2 === null || ![301, 302, 303, 307, 308].includes(response.status) || webRequest.redirect === "manual") {
|
|
41035
|
+
return response;
|
|
41036
|
+
}
|
|
41037
|
+
if (webRequest.redirect === "error" || redirects >= 20) {
|
|
41038
|
+
throw new TypeError("fetch failed while following a redirect");
|
|
41039
|
+
}
|
|
41040
|
+
const nextUrl = new URL(location2, url);
|
|
41041
|
+
if (nextUrl.protocol !== "http:" && nextUrl.protocol !== "https:") {
|
|
41042
|
+
throw new TypeError("listener HTTP client accepts only http and https URLs");
|
|
41043
|
+
}
|
|
41044
|
+
const rewriteToGet = response.status === 303 && method !== "HEAD" || (response.status === 301 || response.status === 302) && method === "POST";
|
|
41045
|
+
if (rewriteToGet) {
|
|
41046
|
+
method = "GET";
|
|
41047
|
+
for (const name of [
|
|
41048
|
+
"content-encoding",
|
|
41049
|
+
"content-language",
|
|
41050
|
+
"content-length",
|
|
41051
|
+
"content-location",
|
|
41052
|
+
"content-type",
|
|
41053
|
+
"transfer-encoding"
|
|
41054
|
+
]) {
|
|
41055
|
+
headers.delete(name);
|
|
41056
|
+
}
|
|
41057
|
+
}
|
|
41058
|
+
if (nextUrl.origin !== url.origin) {
|
|
41059
|
+
for (const name of [
|
|
41060
|
+
"apikey",
|
|
41061
|
+
"authorization",
|
|
41062
|
+
"cookie",
|
|
41063
|
+
"host",
|
|
41064
|
+
"proxy-authorization"
|
|
41065
|
+
]) {
|
|
41066
|
+
headers.delete(name);
|
|
41067
|
+
}
|
|
41068
|
+
}
|
|
41069
|
+
url = nextUrl;
|
|
41070
|
+
redirected = true;
|
|
41071
|
+
}
|
|
41072
|
+
}
|
|
41073
|
+
async sendOnce(options) {
|
|
41074
|
+
if (options.url.protocol !== "http:" && options.url.protocol !== "https:") {
|
|
41075
|
+
throw new TypeError("listener HTTP client accepts only http and https URLs");
|
|
41076
|
+
}
|
|
41077
|
+
const headers = Object.fromEntries(options.headers.entries());
|
|
41078
|
+
if (!("accept-encoding" in headers)) headers["accept-encoding"] = "gzip, deflate";
|
|
41079
|
+
if (options.body !== null && !("content-length" in headers) && !("transfer-encoding" in headers)) {
|
|
41080
|
+
headers["content-length"] = String(options.body.byteLength);
|
|
41081
|
+
}
|
|
41082
|
+
this.beginRequest();
|
|
41083
|
+
let finished = false;
|
|
41084
|
+
const finish = () => {
|
|
41085
|
+
if (finished) return;
|
|
41086
|
+
finished = true;
|
|
41087
|
+
this.finishRequest();
|
|
41088
|
+
};
|
|
41089
|
+
return await new Promise((resolve2, reject) => {
|
|
41090
|
+
const send = options.url.protocol === "https:" ? import_node_https.request : import_node_http2.request;
|
|
41091
|
+
const agent = options.url.protocol === "https:" ? this.httpsAgent : this.httpAgent;
|
|
41092
|
+
let request;
|
|
41093
|
+
try {
|
|
41094
|
+
request = send(options.url, {
|
|
41095
|
+
agent,
|
|
41096
|
+
method: options.method,
|
|
41097
|
+
headers,
|
|
41098
|
+
signal: options.signal
|
|
41099
|
+
});
|
|
41100
|
+
} catch (error) {
|
|
41101
|
+
finish();
|
|
41102
|
+
reject(error);
|
|
41103
|
+
return;
|
|
41104
|
+
}
|
|
41105
|
+
this.trackSocket(request);
|
|
41106
|
+
request.once("error", (error) => {
|
|
41107
|
+
finish();
|
|
41108
|
+
reject(error);
|
|
41109
|
+
});
|
|
41110
|
+
request.once("response", (message) => {
|
|
41111
|
+
const chunks = [];
|
|
41112
|
+
let settled = false;
|
|
41113
|
+
const rejectResponse = (error) => {
|
|
41114
|
+
if (settled) return;
|
|
41115
|
+
settled = true;
|
|
41116
|
+
finish();
|
|
41117
|
+
reject(error);
|
|
41118
|
+
};
|
|
41119
|
+
message.on("data", (chunk) => {
|
|
41120
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
41121
|
+
});
|
|
41122
|
+
message.once("aborted", () => {
|
|
41123
|
+
rejectResponse(new TypeError("response body was aborted"));
|
|
41124
|
+
});
|
|
41125
|
+
message.once("error", rejectResponse);
|
|
41126
|
+
message.once("end", () => {
|
|
41127
|
+
if (settled) return;
|
|
41128
|
+
settled = true;
|
|
41129
|
+
finish();
|
|
41130
|
+
const status = message.statusCode ?? 0;
|
|
41131
|
+
const bytes = Buffer.concat(chunks);
|
|
41132
|
+
const webHeaders = responseHeaders(message);
|
|
41133
|
+
const responseBody2 = status === 204 || status === 205 || status === 304 ? null : decodeResponseBody(bytes, webHeaders);
|
|
41134
|
+
try {
|
|
41135
|
+
const response = new Response(responseBody2, {
|
|
41136
|
+
status,
|
|
41137
|
+
statusText: message.statusMessage,
|
|
41138
|
+
headers: webHeaders
|
|
41139
|
+
});
|
|
41140
|
+
Object.defineProperties(response, {
|
|
41141
|
+
redirected: { value: options.redirected },
|
|
41142
|
+
url: { value: options.url.href }
|
|
41143
|
+
});
|
|
41144
|
+
resolve2(response);
|
|
41145
|
+
} catch (error) {
|
|
41146
|
+
reject(error);
|
|
41147
|
+
}
|
|
41148
|
+
});
|
|
41149
|
+
});
|
|
41150
|
+
try {
|
|
41151
|
+
if (options.body !== null && options.body.byteLength > 0) {
|
|
41152
|
+
request.write(options.body);
|
|
41153
|
+
}
|
|
41154
|
+
request.end();
|
|
41155
|
+
} catch (error) {
|
|
41156
|
+
request.destroy();
|
|
41157
|
+
finish();
|
|
41158
|
+
reject(error);
|
|
41159
|
+
}
|
|
41160
|
+
});
|
|
41161
|
+
}
|
|
41162
|
+
};
|
|
41163
|
+
|
|
41164
|
+
// src/resume.ts
|
|
41165
|
+
var import_node_child_process8 = require("node:child_process");
|
|
41166
|
+
function execFileText(file, args) {
|
|
41167
|
+
return new Promise((resolve2, reject) => {
|
|
41168
|
+
(0, import_node_child_process8.execFile)(file, [...args], {
|
|
41169
|
+
encoding: "utf8",
|
|
41170
|
+
maxBuffer: 4 * 1024 * 1024
|
|
41171
|
+
}, (error, stdout) => {
|
|
41172
|
+
if (error) reject(error);
|
|
41173
|
+
else resolve2(stdout);
|
|
41174
|
+
});
|
|
41175
|
+
});
|
|
41176
|
+
}
|
|
41177
|
+
function systemProcessTable() {
|
|
41178
|
+
return {
|
|
41179
|
+
async list() {
|
|
41180
|
+
const output = await execFileText("ps", ["-axo", "pid=,command="]);
|
|
41181
|
+
return output.split("\n").flatMap((line) => {
|
|
41182
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
41183
|
+
if (!match) return [];
|
|
41184
|
+
const pid = Number(match[1]);
|
|
41185
|
+
return Number.isSafeInteger(pid) && pid > 0 ? [{ pid, command: match[2] }] : [];
|
|
41186
|
+
});
|
|
41187
|
+
}
|
|
41188
|
+
};
|
|
41189
|
+
}
|
|
41190
|
+
function lsofStdoutConsumer() {
|
|
41191
|
+
return {
|
|
41192
|
+
async inspect(pid) {
|
|
41193
|
+
let output;
|
|
41194
|
+
try {
|
|
41195
|
+
output = await execFileText(
|
|
41196
|
+
process.platform === "darwin" ? "/usr/sbin/lsof" : "lsof",
|
|
41197
|
+
["-nP", "-a", "-p", String(pid), "-d", "1", "-F", "pftan"]
|
|
41198
|
+
);
|
|
41199
|
+
} catch {
|
|
41200
|
+
return "cannot_determine";
|
|
41201
|
+
}
|
|
41202
|
+
const lines = output.split("\n");
|
|
41203
|
+
const type = lines.find((line) => line.startsWith("t"))?.slice(1) ?? "";
|
|
41204
|
+
const names = lines.filter((line) => line.startsWith("n")).map((line) => line.slice(1));
|
|
41205
|
+
if (type === "unix") {
|
|
41206
|
+
if (names.some((name) => name === "->(none)")) return "orphaned";
|
|
41207
|
+
if (names.some((name) => name.startsWith("->") && name !== "->(none)")) {
|
|
41208
|
+
return "live_reader";
|
|
41209
|
+
}
|
|
41210
|
+
return "cannot_determine";
|
|
41211
|
+
}
|
|
41212
|
+
if (type === "PIPE" || type === "FIFO") return "cannot_determine";
|
|
41213
|
+
return type.length === 0 ? "cannot_determine" : "not_pipe";
|
|
41214
|
+
}
|
|
41215
|
+
};
|
|
41216
|
+
}
|
|
41217
|
+
function commandHasFlagValue(command2, flag, values2) {
|
|
41218
|
+
for (const value of values2) {
|
|
41219
|
+
const marker = `${flag} ${value}`;
|
|
41220
|
+
let start = command2.indexOf(marker);
|
|
41221
|
+
while (start !== -1) {
|
|
41222
|
+
const before = start === 0 ? " " : command2[start - 1];
|
|
41223
|
+
const afterIndex = start + marker.length;
|
|
41224
|
+
const after = afterIndex >= command2.length ? " " : command2[afterIndex];
|
|
41225
|
+
if (/\s/.test(before) && /\s/.test(after)) return true;
|
|
41226
|
+
start = command2.indexOf(marker, start + 1);
|
|
41227
|
+
}
|
|
41228
|
+
}
|
|
41229
|
+
return false;
|
|
41230
|
+
}
|
|
41231
|
+
function isNotifyCommand(command2) {
|
|
41232
|
+
return /(?:^|\s)inbox(?:\s|$)/.test(command2) && /(?:^|\s)--notify(?:\s|$)/.test(command2);
|
|
41233
|
+
}
|
|
41234
|
+
async function findNotifyWatchers(options) {
|
|
41235
|
+
const processTable = options.processTable ?? systemProcessTable();
|
|
41236
|
+
const stdoutConsumer = options.stdoutConsumer ?? lsofStdoutConsumer();
|
|
41237
|
+
const rows3 = await processTable.list();
|
|
41238
|
+
const matches = rows3.flatMap((row) => {
|
|
41239
|
+
if (!isNotifyCommand(row.command)) return [];
|
|
41240
|
+
const matchedBy = [];
|
|
41241
|
+
if (commandHasFlagValue(row.command, "--agent-token-file", options.credentialPaths)) {
|
|
41242
|
+
matchedBy.push("agent_token_file");
|
|
41243
|
+
}
|
|
41244
|
+
if (commandHasFlagValue(row.command, "--principal-id", [options.principalId])) {
|
|
41245
|
+
matchedBy.push("principal_id");
|
|
41246
|
+
}
|
|
41247
|
+
return matchedBy.length === 0 ? [] : [{ pid: row.pid, matchedBy }];
|
|
41248
|
+
});
|
|
41249
|
+
const unique = [...new Map(matches.map((row) => [row.pid, row])).values()].sort((left, right) => left.pid - right.pid);
|
|
41250
|
+
return await Promise.all(unique.map(async (row) => ({
|
|
41251
|
+
...row,
|
|
41252
|
+
stdout: await stdoutConsumer.inspect(row.pid)
|
|
41253
|
+
})));
|
|
41254
|
+
}
|
|
41255
|
+
async function readOnlyListenerInspection(paths, adapters = {}) {
|
|
41256
|
+
const query = adapters.queryStatus ?? queryListenerControl;
|
|
41257
|
+
const read = adapters.readStatus ?? readListenerStatusIfPresent;
|
|
41258
|
+
try {
|
|
41259
|
+
return {
|
|
41260
|
+
checkedDirectory: paths.instanceDirectory,
|
|
41261
|
+
status: await query(paths, "status"),
|
|
41262
|
+
source: "live_process"
|
|
41263
|
+
};
|
|
41264
|
+
} catch {
|
|
41265
|
+
const status = await read(paths);
|
|
41266
|
+
return {
|
|
41267
|
+
checkedDirectory: paths.instanceDirectory,
|
|
41268
|
+
status,
|
|
41269
|
+
source: status === null ? "not_found" : "recorded_file"
|
|
41270
|
+
};
|
|
41271
|
+
}
|
|
41272
|
+
}
|
|
41273
|
+
async function inspectResume(options, adapters) {
|
|
41274
|
+
const identity = await adapters.readIdentity();
|
|
41275
|
+
const principalId = identity.principalId.toLowerCase();
|
|
41276
|
+
const paths = listenerPaths({
|
|
41277
|
+
profileId: options.target.profileId,
|
|
41278
|
+
workspaceId: options.workspaceId,
|
|
41279
|
+
principalId,
|
|
41280
|
+
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
41281
|
+
});
|
|
41282
|
+
const listener = await readOnlyListenerInspection(paths, adapters);
|
|
41283
|
+
const watchers = await findNotifyWatchers({
|
|
41284
|
+
credentialPaths: options.credentialPathAliases ?? [options.credentialFile],
|
|
41285
|
+
principalId,
|
|
41286
|
+
...adapters.processTable ? { processTable: adapters.processTable } : {},
|
|
41287
|
+
...adapters.stdoutConsumer ? { stdoutConsumer: adapters.stdoutConsumer } : {}
|
|
41288
|
+
});
|
|
41289
|
+
const topics = await adapters.readBrainTopics();
|
|
41290
|
+
const digestStore = new FileBrainDigestStore(paths.instanceDirectory, principalId);
|
|
41291
|
+
const digest = await digestStore.preview(topics);
|
|
41292
|
+
const inbox = await adapters.readInboxCount(principalId, paths.instanceDirectory);
|
|
41293
|
+
return {
|
|
41294
|
+
identity: { ...identity, principalId },
|
|
41295
|
+
listener,
|
|
41296
|
+
watchers,
|
|
41297
|
+
brain: { digest, highWaterFile: digestStore.location },
|
|
41298
|
+
inbox,
|
|
41299
|
+
target: options.target,
|
|
41300
|
+
workspaceId: options.workspaceId,
|
|
41301
|
+
credentialFile: options.credentialFile,
|
|
41302
|
+
installedVersion: options.installedVersion
|
|
41303
|
+
};
|
|
41304
|
+
}
|
|
41305
|
+
function safeText(value) {
|
|
41306
|
+
return value.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ").slice(0, 2e3);
|
|
41307
|
+
}
|
|
41308
|
+
function shellArg(value) {
|
|
41309
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
41310
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
41311
|
+
}
|
|
41312
|
+
function commonCommandArgs(report) {
|
|
41313
|
+
return [
|
|
41314
|
+
"--agent-token-file",
|
|
41315
|
+
shellArg(report.credentialFile),
|
|
41316
|
+
"--url",
|
|
41317
|
+
shellArg(report.target.url),
|
|
41318
|
+
"--anon-key",
|
|
41319
|
+
shellArg(report.target.anonKey),
|
|
41320
|
+
"--workspace-id",
|
|
41321
|
+
report.workspaceId
|
|
41322
|
+
].join(" ");
|
|
41323
|
+
}
|
|
41324
|
+
function restartCommand(report, status) {
|
|
41325
|
+
const common = commonCommandArgs(report);
|
|
41326
|
+
const route = status.routeMode ?? "worker";
|
|
41327
|
+
const start = [
|
|
41328
|
+
"cswarm listen start",
|
|
41329
|
+
common,
|
|
41330
|
+
`--provider ${status.provider}`,
|
|
41331
|
+
`--permissions ${status.permissionMode ?? "allow"}`,
|
|
41332
|
+
`--route ${route}`,
|
|
41333
|
+
...route === "split" && status.deferOverChars !== null && status.deferOverChars !== void 0 ? [`--defer-over ${status.deferOverChars}`] : []
|
|
41334
|
+
].join(" ");
|
|
41335
|
+
return `cswarm listen stop ${common} && ${start}`;
|
|
41336
|
+
}
|
|
41337
|
+
function watcherStateLine(watcher) {
|
|
41338
|
+
const matched = watcher.matchedBy.map(
|
|
41339
|
+
(value) => value === "agent_token_file" ? "credential path" : "principal id"
|
|
41340
|
+
).join(" and ");
|
|
41341
|
+
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";
|
|
41342
|
+
return `- PID ${watcher.pid}: ${state}; matched ${matched}.`;
|
|
41343
|
+
}
|
|
41344
|
+
function renderResume(report) {
|
|
41345
|
+
const lines = [
|
|
41346
|
+
"Identity",
|
|
41347
|
+
`You are ${safeText(report.identity.displayName)} (${report.identity.principalId}).`,
|
|
41348
|
+
"Next: use this principal for every listener, watcher, brain, and inbox check below.",
|
|
41349
|
+
"",
|
|
41350
|
+
"Listener"
|
|
41351
|
+
];
|
|
41352
|
+
const listener = report.listener;
|
|
41353
|
+
const status = listener.status;
|
|
41354
|
+
if (status === null) {
|
|
41355
|
+
lines.push(
|
|
41356
|
+
`No listener found under ${safeText(listener.checkedDirectory)} for profile ${report.target.profileId}.`,
|
|
41357
|
+
`Next: start one with the original provider: cswarm listen start ${commonCommandArgs(report)} --provider <provider>`
|
|
41358
|
+
);
|
|
41359
|
+
} else {
|
|
41360
|
+
if (listener.source === "live_process") {
|
|
41361
|
+
lines.push(
|
|
41362
|
+
`Found under ${safeText(listener.checkedDirectory)}. State: ${status.state}, reported by running PID ${status.pid}.`
|
|
41363
|
+
);
|
|
41364
|
+
} else {
|
|
41365
|
+
lines.push(
|
|
41366
|
+
`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.`
|
|
41367
|
+
);
|
|
41368
|
+
}
|
|
41369
|
+
const runningVersion = status.cswarmVersion ?? null;
|
|
41370
|
+
if (listener.source !== "live_process") {
|
|
41371
|
+
lines.push(
|
|
41372
|
+
`Running listener cswarm version: cannot determine because the process did not answer; status file recorded ${runningVersion ?? "no version"}; installed CLI: ${report.installedVersion}.`,
|
|
41373
|
+
`Next: restart the listener because its process did not answer: ${restartCommand(report, status)}`
|
|
41374
|
+
);
|
|
41375
|
+
} else if (runningVersion === null) {
|
|
41376
|
+
lines.push(
|
|
41377
|
+
`Listener cswarm version: cannot determine from this listener; installed CLI: ${report.installedVersion}.`,
|
|
41378
|
+
`Next: restart it to make the running version reportable: ${restartCommand(report, status)}`
|
|
41379
|
+
);
|
|
41380
|
+
} else if (runningVersion !== report.installedVersion) {
|
|
41381
|
+
lines.push(
|
|
41382
|
+
`VERSION MISMATCH: listener runs ${runningVersion}; installed ${report.installedVersion} \u2014 restart it: ${restartCommand(report, status)}`
|
|
41383
|
+
);
|
|
41384
|
+
} else {
|
|
41385
|
+
lines.push(
|
|
41386
|
+
`Listener-reported cswarm: ${runningVersion}; installed CLI: ${report.installedVersion}.`,
|
|
41387
|
+
"Next: no listener restart is needed for a version change."
|
|
41388
|
+
);
|
|
41389
|
+
}
|
|
41390
|
+
}
|
|
41391
|
+
lines.push(
|
|
41392
|
+
"",
|
|
41393
|
+
"Notify watchers",
|
|
41394
|
+
`Checked process arguments for inbox --notify matching --agent-token-file ${safeText(report.credentialFile)} or --principal-id ${report.identity.principalId}.`
|
|
41395
|
+
);
|
|
41396
|
+
if (report.watchers.length === 0) {
|
|
41397
|
+
lines.push(
|
|
41398
|
+
"Found: 0.",
|
|
41399
|
+
`Next: start one watcher under a live Monitor: cswarm inbox --notify ${commonCommandArgs(report)}`
|
|
41400
|
+
);
|
|
41401
|
+
} else {
|
|
41402
|
+
lines.push(`Found: ${report.watchers.length}.`);
|
|
41403
|
+
lines.push(...report.watchers.map(watcherStateLine));
|
|
41404
|
+
const orphans = report.watchers.filter((watcher) => watcher.stdout === "orphaned");
|
|
41405
|
+
if (orphans.length > 0) {
|
|
41406
|
+
lines.push(
|
|
41407
|
+
`Next: stop only the orphan watcher${orphans.length === 1 ? "" : "s"}; CommonSwarm did not kill anything: kill ${orphans.map((watcher) => watcher.pid).join(" ")}`
|
|
41408
|
+
);
|
|
41409
|
+
} else if (report.watchers.some((watcher) => watcher.stdout === "cannot_determine")) {
|
|
41410
|
+
lines.push(
|
|
41411
|
+
"Next: verify each unknown stdout reader in the host Monitor before you start another watcher."
|
|
41412
|
+
);
|
|
41413
|
+
} else {
|
|
41414
|
+
lines.push("Next: keep one watcher with a live output surface; do not start a duplicate.");
|
|
41415
|
+
}
|
|
41416
|
+
}
|
|
41417
|
+
lines.push(
|
|
41418
|
+
"",
|
|
41419
|
+
"Brain digest",
|
|
41420
|
+
`Checked ${safeText(report.brain.highWaterFile)} without advancing it.`
|
|
41421
|
+
);
|
|
41422
|
+
if (report.brain.digest === null) {
|
|
41423
|
+
lines.push(
|
|
41424
|
+
"No brain topic is new or changed since this principal's digest high-water.",
|
|
41425
|
+
"Next: no brain read is needed now."
|
|
41426
|
+
);
|
|
41427
|
+
} else {
|
|
41428
|
+
lines.push(report.brain.digest, "Next: read any needed topic with the command above.");
|
|
41429
|
+
}
|
|
41430
|
+
const inboxCount = report.inbox.exact ? String(report.inbox.count) : `at least ${report.inbox.count}`;
|
|
41431
|
+
lines.push(
|
|
41432
|
+
"",
|
|
41433
|
+
"Unread inbox",
|
|
41434
|
+
`Unread directed asks and notes from the same read used by the hook: ${inboxCount}.`,
|
|
41435
|
+
report.inbox.count === 0 ? "Next: no inbox action is needed now." : `Next: read them without acknowledging them first: cswarm inbox ${commonCommandArgs(report)}`,
|
|
41436
|
+
"",
|
|
41437
|
+
"Read-only check complete. No cursor, brain high-water, listener status, receipt, acknowledgement, or process was changed."
|
|
41438
|
+
);
|
|
41439
|
+
return lines.join("\n");
|
|
41440
|
+
}
|
|
41441
|
+
function resumeJson(report) {
|
|
41442
|
+
return {
|
|
41443
|
+
identity: {
|
|
41444
|
+
display_name: report.identity.displayName,
|
|
41445
|
+
principal_id: report.identity.principalId
|
|
41446
|
+
},
|
|
41447
|
+
listener: {
|
|
41448
|
+
found: report.listener.status !== null,
|
|
41449
|
+
checked_directory: report.listener.checkedDirectory,
|
|
41450
|
+
source: report.listener.source,
|
|
41451
|
+
state: report.listener.status?.state ?? null,
|
|
41452
|
+
pid: report.listener.status?.pid ?? null,
|
|
41453
|
+
running_cswarm_version: report.listener.source === "live_process" ? report.listener.status?.cswarmVersion ?? null : null,
|
|
41454
|
+
installed_cswarm_version: report.installedVersion,
|
|
41455
|
+
version_mismatch: report.listener.source === "live_process" && report.listener.status?.cswarmVersion !== null && report.listener.status?.cswarmVersion !== void 0 && report.listener.status.cswarmVersion !== report.installedVersion
|
|
41456
|
+
},
|
|
41457
|
+
notify_watchers: report.watchers.map((watcher) => ({
|
|
41458
|
+
pid: watcher.pid,
|
|
41459
|
+
matched_by: watcher.matchedBy,
|
|
41460
|
+
stdout: watcher.stdout
|
|
41461
|
+
})),
|
|
41462
|
+
brain: {
|
|
41463
|
+
high_water_file: report.brain.highWaterFile,
|
|
41464
|
+
high_water_advanced: false,
|
|
41465
|
+
digest: report.brain.digest
|
|
41466
|
+
},
|
|
41467
|
+
unread_inbox: report.inbox,
|
|
41468
|
+
read_only: true
|
|
41469
|
+
};
|
|
41470
|
+
}
|
|
41471
|
+
|
|
39646
41472
|
// src/cli.ts
|
|
39647
41473
|
var import_meta = {};
|
|
39648
41474
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -39650,6 +41476,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39650
41476
|
"agent-token-file",
|
|
39651
41477
|
"agent-token-stdin",
|
|
39652
41478
|
"all-devices",
|
|
41479
|
+
"allow-unattended",
|
|
39653
41480
|
"anon-key",
|
|
39654
41481
|
"attach",
|
|
39655
41482
|
"branch",
|
|
@@ -39699,6 +41526,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39699
41526
|
"since",
|
|
39700
41527
|
"site",
|
|
39701
41528
|
"slug",
|
|
41529
|
+
"state-dir",
|
|
39702
41530
|
"renewal-horizon-days",
|
|
39703
41531
|
"standing",
|
|
39704
41532
|
"task-id",
|
|
@@ -39718,6 +41546,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
39718
41546
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
39719
41547
|
"agent-token-stdin",
|
|
39720
41548
|
"all-devices",
|
|
41549
|
+
"allow-unattended",
|
|
39721
41550
|
"confirm-standing",
|
|
39722
41551
|
"force-file-store",
|
|
39723
41552
|
"follow",
|
|
@@ -39747,8 +41576,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
39747
41576
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
39748
41577
|
];
|
|
39749
41578
|
function packageVersion() {
|
|
39750
|
-
if ("0.1.
|
|
39751
|
-
return "0.1.
|
|
41579
|
+
if ("0.1.46".length > 0) {
|
|
41580
|
+
return "0.1.46";
|
|
39752
41581
|
}
|
|
39753
41582
|
try {
|
|
39754
41583
|
const value = JSON.parse(
|
|
@@ -39865,6 +41694,7 @@ Usage:
|
|
|
39865
41694
|
cswarm target clear [--json]
|
|
39866
41695
|
cswarm status [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
39867
41696
|
cswarm whoami ${requiredAgentCredential} [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
41697
|
+
cswarm resume --agent-token-file <path> [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
|
|
39868
41698
|
cswarm members [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
39869
41699
|
cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--until <dur>] [--json]
|
|
39870
41700
|
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 +41714,8 @@ Usage:
|
|
|
39884
41714
|
cswarm brain get <topic> [--version <n>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
39885
41715
|
cswarm brain put <topic> [<markdown-path>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # without a path, reads Markdown from stdin
|
|
39886
41716
|
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]
|
|
41717
|
+
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]
|
|
41718
|
+
cswarm listen canary ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--state-dir <path>] [--wait <seconds>] [--json]
|
|
39888
41719
|
cswarm listen status ${agentCredential2} [--url <url> --anon-key <key>] --workspace-id <uuid> [--principal-id <uuid>] [--json]
|
|
39889
41720
|
cswarm listen stop ${agentCredential2} [--url <url> --anon-key <key>] --workspace-id <uuid> [--principal-id <uuid>] [--json]
|
|
39890
41721
|
cswarm hook check [--principal-id <uuid> ...] [--cooldown <seconds>]
|
|
@@ -39923,6 +41754,7 @@ Credential selection for command/dogfood:
|
|
|
39923
41754
|
One that persists or references the credential needs the complete
|
|
39924
41755
|
JSON artifact, because it needs a field a bare secret does not carry:
|
|
39925
41756
|
whoami reads server-proven identity -- complete or bare form
|
|
41757
|
+
resume reads reconnect state -- complete or bare file form
|
|
39926
41758
|
members reads only -- either form
|
|
39927
41759
|
working-on, note, ask, reply, feed, inbox
|
|
39928
41760
|
signal command/read only -- either form
|
|
@@ -39935,6 +41767,8 @@ Credential selection for command/dogfood:
|
|
|
39935
41767
|
command, dogfood
|
|
39936
41768
|
task protocol commands -- either form
|
|
39937
41769
|
listen start persists durable state, rotates -- needs expires_at
|
|
41770
|
+
listen canary posts one self-note and selects local state -- needs
|
|
41771
|
+
principal_id; it does not renew the credential
|
|
39938
41772
|
listen status
|
|
39939
41773
|
selects the listener profile -- complete JSON or
|
|
39940
41774
|
an explicit --principal-id without a credential
|
|
@@ -39972,15 +41806,20 @@ fresh credential.
|
|
|
39972
41806
|
listen start --route worker|main|split chooses where directed messages go. worker
|
|
39973
41807
|
is the unchanged default. main queues every ask or note for the interactive session.
|
|
39974
41808
|
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.
|
|
41809
|
+
1..10000 and an equal-length message stays on the worker path. main and split require
|
|
41810
|
+
a principal-scoped Claude hook or prior hook surface. --allow-unattended accepts the
|
|
41811
|
+
risk explicitly. Run cswarm hook check
|
|
39976
41812
|
--principal-id <uuid> to surface that agent's queued messages. A bare check works only
|
|
39977
41813
|
when the state directory holds one principal. hook check has its own 3s ceiling, exits 0
|
|
39978
41814
|
on every outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
41815
|
+
listen canary posts one self-addressed note, waits at most --wait seconds (default 10),
|
|
41816
|
+
and reports accepted, claimed, queued/worker, surfaced, and observed as separate hops.
|
|
39979
41817
|
hook install claude prints principal-scoped UserPromptSubmit JSON by default. --write changes
|
|
39980
41818
|
<project>/.claude/settings.local.json, which applies only to Claude Code sessions started in
|
|
39981
41819
|
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
|
|
41820
|
+
\${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json and warns that every Claude Code session reading
|
|
41821
|
+
that directory is affected. --repo keeps the repository-wide .claude/settings.json scope and
|
|
41822
|
+
also requires an ignored file. Uninstall also
|
|
39984
41823
|
requires --write and uses the same scope selection.
|
|
39985
41824
|
|
|
39986
41825
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
@@ -40237,7 +42076,7 @@ async function stdinInviteLink() {
|
|
|
40237
42076
|
return link;
|
|
40238
42077
|
}
|
|
40239
42078
|
async function confirmationLine(prompt) {
|
|
40240
|
-
const reader = (0,
|
|
42079
|
+
const reader = (0, import_promises13.createInterface)({
|
|
40241
42080
|
input: process.stdin,
|
|
40242
42081
|
output: process.stderr,
|
|
40243
42082
|
terminal: Boolean(process.stdin.isTTY)
|
|
@@ -40362,7 +42201,7 @@ async function runNew(args) {
|
|
|
40362
42201
|
assertWorkspaceName(name);
|
|
40363
42202
|
const cloud = await target(args);
|
|
40364
42203
|
const human = await humanCredential(args, cloud);
|
|
40365
|
-
const proposedId = (0,
|
|
42204
|
+
const proposedId = (0, import_node_crypto21.randomUUID)();
|
|
40366
42205
|
let result;
|
|
40367
42206
|
try {
|
|
40368
42207
|
result = await new ThinCommandClient(cloud).sendConnect({
|
|
@@ -41385,7 +43224,7 @@ function accepted(label, result) {
|
|
|
41385
43224
|
);
|
|
41386
43225
|
}
|
|
41387
43226
|
}
|
|
41388
|
-
async function agentSession(cloud, workspaceId2, agent) {
|
|
43227
|
+
async function agentSession(cloud, workspaceId2, agent, fetcher) {
|
|
41389
43228
|
let store2 = null;
|
|
41390
43229
|
try {
|
|
41391
43230
|
const candidate = await agentCredentialStore({
|
|
@@ -41411,7 +43250,8 @@ async function agentSession(cloud, workspaceId2, agent) {
|
|
|
41411
43250
|
runId: agent.runId,
|
|
41412
43251
|
expiresAt: agent.expiresAt
|
|
41413
43252
|
},
|
|
41414
|
-
store: store2
|
|
43253
|
+
store: store2,
|
|
43254
|
+
...fetcher ? { fetcher } : {}
|
|
41415
43255
|
});
|
|
41416
43256
|
}
|
|
41417
43257
|
async function commandWorkspaceAndCredential(args, cloud, options = {}) {
|
|
@@ -41693,8 +43533,8 @@ function prepareSignalAttachments(localPaths) {
|
|
|
41693
43533
|
name,
|
|
41694
43534
|
bytes,
|
|
41695
43535
|
contentType,
|
|
41696
|
-
fileId: (0,
|
|
41697
|
-
versionId: (0,
|
|
43536
|
+
fileId: (0, import_node_crypto21.randomUUID)(),
|
|
43537
|
+
versionId: (0, import_node_crypto21.randomUUID)(),
|
|
41698
43538
|
createCommandId: newCommandId(),
|
|
41699
43539
|
commitCommandId: newCommandId()
|
|
41700
43540
|
};
|
|
@@ -42168,6 +44008,100 @@ Owner: ${ownerName} (${identity.owner_user_id}).
|
|
|
42168
44008
|
`)
|
|
42169
44009
|
);
|
|
42170
44010
|
}
|
|
44011
|
+
async function runResume(args) {
|
|
44012
|
+
args.assertShape([
|
|
44013
|
+
...TARGET_FLAGS,
|
|
44014
|
+
"workspace-id",
|
|
44015
|
+
"agent-token-file",
|
|
44016
|
+
"state-dir",
|
|
44017
|
+
"json"
|
|
44018
|
+
], 1);
|
|
44019
|
+
const suppliedCredentialPath = args.optional("agent-token-file");
|
|
44020
|
+
if (suppliedCredentialPath === void 0) {
|
|
44021
|
+
throw new UsageError("cswarm resume needs --agent-token-file <path>");
|
|
44022
|
+
}
|
|
44023
|
+
if (/[\u0000-\u001f\u007f-\u009f]/.test(suppliedCredentialPath)) {
|
|
44024
|
+
throw new Error("--agent-token-file must not contain control characters");
|
|
44025
|
+
}
|
|
44026
|
+
const credentialFile = (0, import_node_path21.resolve)(suppliedCredentialPath);
|
|
44027
|
+
const cloud = await target(args);
|
|
44028
|
+
const workspaceId2 = listenerUuid(
|
|
44029
|
+
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
44030
|
+
"workspace-id"
|
|
44031
|
+
);
|
|
44032
|
+
const agent = await agentCredential(args);
|
|
44033
|
+
const report = await inspectResume({
|
|
44034
|
+
target: cloud,
|
|
44035
|
+
workspaceId: workspaceId2,
|
|
44036
|
+
credentialFile,
|
|
44037
|
+
credentialPathAliases: [.../* @__PURE__ */ new Set([suppliedCredentialPath, credentialFile])],
|
|
44038
|
+
installedVersion: CLI_BUILD_VERSION,
|
|
44039
|
+
...listenerStateDirectory(args) ? { stateDirectory: listenerStateDirectory(args) } : {}
|
|
44040
|
+
}, {
|
|
44041
|
+
readIdentity: async () => {
|
|
44042
|
+
const directory = await readAgentSignalDirectory(
|
|
44043
|
+
cloud,
|
|
44044
|
+
agent.token,
|
|
44045
|
+
workspaceId2
|
|
44046
|
+
);
|
|
44047
|
+
const identity = directory.identity;
|
|
44048
|
+
if (identity === void 0 || identity.workspace_id !== workspaceId2) {
|
|
44049
|
+
throw new Error(
|
|
44050
|
+
"the read service authenticated this credential but did not return its identity for this workspace; resume stopped without changing state"
|
|
44051
|
+
);
|
|
44052
|
+
}
|
|
44053
|
+
const principal = directory.agents.find(
|
|
44054
|
+
(candidate) => candidate.principal_id === identity.principal_id && candidate.owner_user_id === identity.owner_user_id
|
|
44055
|
+
);
|
|
44056
|
+
if (principal === void 0) {
|
|
44057
|
+
throw new Error(
|
|
44058
|
+
"the read service authenticated this credential but returned no matching live principal; resume stopped without changing state"
|
|
44059
|
+
);
|
|
44060
|
+
}
|
|
44061
|
+
if (agent.principalId !== null && agent.principalId !== identity.principal_id) {
|
|
44062
|
+
process.stderr.write(
|
|
44063
|
+
`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.
|
|
44064
|
+
`
|
|
44065
|
+
);
|
|
44066
|
+
}
|
|
44067
|
+
return {
|
|
44068
|
+
displayName: sanitizeDisplayLabel(principal.name, "Unnamed agent"),
|
|
44069
|
+
principalId: identity.principal_id
|
|
44070
|
+
};
|
|
44071
|
+
},
|
|
44072
|
+
readBrainTopics: async () => brainTopicSnapshots(await listBrainRowsAsAgent(
|
|
44073
|
+
cloud,
|
|
44074
|
+
agent.token,
|
|
44075
|
+
workspaceId2
|
|
44076
|
+
)),
|
|
44077
|
+
readInboxCount: async (principalId, instanceDirectory) => {
|
|
44078
|
+
const page = await readAgentSignalPage(
|
|
44079
|
+
cloud,
|
|
44080
|
+
{ kind: "agent", token: agent.token },
|
|
44081
|
+
{
|
|
44082
|
+
workspaceId: workspaceId2,
|
|
44083
|
+
inbox: true,
|
|
44084
|
+
ascending: false,
|
|
44085
|
+
limit: 100,
|
|
44086
|
+
includeStale: false
|
|
44087
|
+
},
|
|
44088
|
+
fetch,
|
|
44089
|
+
{ tolerateMalformedRows: true, maxMalformedRows: 3 }
|
|
44090
|
+
);
|
|
44091
|
+
const candidates = page.signals.filter(
|
|
44092
|
+
(signal) => (signal.kind === "ask" || signal.kind === "note") && signal.workspace_id === workspaceId2 && signal.to_agent === principalId
|
|
44093
|
+
).map((signal) => ({ signalId: signal.id }));
|
|
44094
|
+
const unseen = await new FileHookSurfaceStore(instanceDirectory).previewUnseen(candidates);
|
|
44095
|
+
return {
|
|
44096
|
+
count: unseen.length,
|
|
44097
|
+
exact: page.rawCount < 100 && page.malformedRows === 0
|
|
44098
|
+
};
|
|
44099
|
+
}
|
|
44100
|
+
});
|
|
44101
|
+
if (args.has("json")) printJson(resumeJson(report));
|
|
44102
|
+
else process.stdout.write(`${renderResume(report)}
|
|
44103
|
+
`);
|
|
44104
|
+
}
|
|
42171
44105
|
async function runSignalRead(args, inbox) {
|
|
42172
44106
|
const notify = inbox && args.has("notify");
|
|
42173
44107
|
args.assertShape(notify ? [
|
|
@@ -42268,15 +44202,6 @@ async function runSignalRead(args, inbox) {
|
|
|
42268
44202
|
})}
|
|
42269
44203
|
`);
|
|
42270
44204
|
}
|
|
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
44205
|
async function runInboxNotifyCommand(args) {
|
|
42281
44206
|
if (!hasAgentCredential(args)) {
|
|
42282
44207
|
throw new Error(
|
|
@@ -42294,6 +44219,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
42294
44219
|
);
|
|
42295
44220
|
}
|
|
42296
44221
|
const controller = new AbortController();
|
|
44222
|
+
const httpClient = new ListenerHttpClient();
|
|
42297
44223
|
const stop = () => controller.abort();
|
|
42298
44224
|
process.on("SIGINT", stop);
|
|
42299
44225
|
process.on("SIGTERM", stop);
|
|
@@ -42324,7 +44250,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
42324
44250
|
...after === null ? {} : { after }
|
|
42325
44251
|
}
|
|
42326
44252
|
},
|
|
42327
|
-
{ signal: controller.signal }
|
|
44253
|
+
{ signal: controller.signal, fetcher: httpClient.fetch }
|
|
42328
44254
|
);
|
|
42329
44255
|
},
|
|
42330
44256
|
emit: async (signal) => {
|
|
@@ -42333,7 +44259,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
42333
44259
|
selected.selectedWorkspace,
|
|
42334
44260
|
cloud
|
|
42335
44261
|
);
|
|
42336
|
-
await
|
|
44262
|
+
await writeArrivalMonitorLine(
|
|
42337
44263
|
args.has("json") ? JSON.stringify(notification) : formatArrivalNotification(notification)
|
|
42338
44264
|
);
|
|
42339
44265
|
},
|
|
@@ -42358,6 +44284,7 @@ async function runInboxNotifyCommand(args) {
|
|
|
42358
44284
|
} finally {
|
|
42359
44285
|
process.off("SIGINT", stop);
|
|
42360
44286
|
process.off("SIGTERM", stop);
|
|
44287
|
+
httpClient.close();
|
|
42361
44288
|
}
|
|
42362
44289
|
}
|
|
42363
44290
|
async function runReceipt(args) {
|
|
@@ -42430,6 +44357,7 @@ async function runInboxFollowCommand(args) {
|
|
|
42430
44357
|
includeStale: args.has("include-stale")
|
|
42431
44358
|
};
|
|
42432
44359
|
const controller = new AbortController();
|
|
44360
|
+
const httpClient = new ListenerHttpClient();
|
|
42433
44361
|
let legacyCursorWarned = false;
|
|
42434
44362
|
let malformedRowWarnings = 0;
|
|
42435
44363
|
const onAbortSignal = () => controller.abort();
|
|
@@ -42461,7 +44389,7 @@ async function runInboxFollowCommand(args) {
|
|
|
42461
44389
|
cloud,
|
|
42462
44390
|
credential,
|
|
42463
44391
|
query,
|
|
42464
|
-
{ signal: controller.signal },
|
|
44392
|
+
{ signal: controller.signal, fetcher: httpClient.fetch },
|
|
42465
44393
|
{
|
|
42466
44394
|
allowLegacyCursorFallback: true,
|
|
42467
44395
|
tolerateMalformedRows: true,
|
|
@@ -42493,7 +44421,7 @@ async function runInboxFollowCommand(args) {
|
|
|
42493
44421
|
cloud,
|
|
42494
44422
|
credential,
|
|
42495
44423
|
query,
|
|
42496
|
-
{ signal: controller.signal }
|
|
44424
|
+
{ signal: controller.signal, fetcher: httpClient.fetch }
|
|
42497
44425
|
);
|
|
42498
44426
|
},
|
|
42499
44427
|
emit: (frame) => {
|
|
@@ -42511,6 +44439,7 @@ async function runInboxFollowCommand(args) {
|
|
|
42511
44439
|
} finally {
|
|
42512
44440
|
process.off("SIGINT", onAbortSignal);
|
|
42513
44441
|
process.off("SIGTERM", onAbortSignal);
|
|
44442
|
+
httpClient.close();
|
|
42514
44443
|
}
|
|
42515
44444
|
}
|
|
42516
44445
|
function listenerUuid(value, flag) {
|
|
@@ -42677,10 +44606,148 @@ function listenerHostLimits(provider) {
|
|
|
42677
44606
|
}
|
|
42678
44607
|
};
|
|
42679
44608
|
}
|
|
42680
|
-
function
|
|
44609
|
+
function listenerAttendanceState(status, evidence) {
|
|
44610
|
+
const routeMode = status.routeMode ?? "worker";
|
|
44611
|
+
const pending = status.pendingForMainCount ?? 0;
|
|
44612
|
+
const connected = status.state === "ready";
|
|
44613
|
+
const attendanceState = routeMode === "worker" ? "not_required" : pending > 0 ? "unattended" : evidence.hookSurfaceAdvanced ? "attended" : "unproven";
|
|
44614
|
+
const attended = attendanceState === "attended" ? true : attendanceState === "unattended" ? false : null;
|
|
44615
|
+
const handled = pending > 0 ? false : routeMode === "worker" && status.lastAckAt !== null ? true : null;
|
|
44616
|
+
return {
|
|
44617
|
+
connected,
|
|
44618
|
+
attended,
|
|
44619
|
+
attendanceState,
|
|
44620
|
+
handled,
|
|
44621
|
+
handledState: handled === true ? "handled" : handled === false ? "not_handled" : "not_yet_measured"
|
|
44622
|
+
};
|
|
44623
|
+
}
|
|
44624
|
+
function listenerAttendanceRemedy(principalId) {
|
|
44625
|
+
return `cswarm hook install claude --principal-id ${principalId} --write, then start a fresh session. Or restart the listener with --route worker.`;
|
|
44626
|
+
}
|
|
44627
|
+
function listenerReadHealthSummary(status, nowMs) {
|
|
44628
|
+
return summarizeListenerReadHealth(
|
|
44629
|
+
status.readHealth ?? emptyListenerReadHealth(),
|
|
44630
|
+
status.readyAt,
|
|
44631
|
+
nowMs
|
|
44632
|
+
);
|
|
44633
|
+
}
|
|
44634
|
+
function listenerLapseNotices(status, summary) {
|
|
44635
|
+
const health = status.readHealth ?? emptyListenerReadHealth();
|
|
44636
|
+
const notices = [];
|
|
44637
|
+
if (health.currentReasonCode === "host_ports_exhausted") {
|
|
44638
|
+
notices.push({
|
|
44639
|
+
code: "listener_host_ports_exhausted",
|
|
44640
|
+
message: "This host has run out of outbound ports. The listener is probing only once per minute so it does not amplify the outage.",
|
|
44641
|
+
nextStep: "Find the consumer: lsof -nP -iTCP | awk '{print $1}' | sort | uniq -c | sort -rn"
|
|
44642
|
+
});
|
|
44643
|
+
} else if (
|
|
44644
|
+
// Reuse arrival-watch.ts's 60s loud-lapse transition. The listener keeps
|
|
44645
|
+
// the episode in durable status instead of the monitor's process-local machine.
|
|
44646
|
+
summary.currentEpisodeDurationMs !== null && summary.currentEpisodeDurationMs >= ARRIVAL_RETRY_NOTICE_THRESHOLD_MS
|
|
44647
|
+
) {
|
|
44648
|
+
notices.push({
|
|
44649
|
+
code: "listener_read_retry_persisting",
|
|
44650
|
+
message: `Listener reads have failed continuously for ${Math.floor(summary.currentEpisodeDurationMs / 1e3)}s. This is still in progress.`,
|
|
44651
|
+
nextStep: "Check cswarm status and the CommonSwarm service. If both are healthy, restart the listener."
|
|
44652
|
+
});
|
|
44653
|
+
}
|
|
44654
|
+
if (summary.throughputLapseHours.length > 0) {
|
|
44655
|
+
const latest = summary.throughputLapseHours.at(-1);
|
|
44656
|
+
notices.push({
|
|
44657
|
+
code: "listener_claim_throughput_lapse",
|
|
44658
|
+
message: `Claim throughput fell below 0.50 for the full hour at ${latest.hourStart}: ${latest.claims}/${Math.round(latest.expectedClaims)} expected (${latest.ratio.toFixed(3)}).`,
|
|
44659
|
+
nextStep: "This host is starving the listener \u2014 check load/memory pressure (sysctl kern.memorystatus_vm_pressure_level), or move the listener."
|
|
44660
|
+
});
|
|
44661
|
+
}
|
|
44662
|
+
return notices;
|
|
44663
|
+
}
|
|
44664
|
+
async function listenerProviderInstallEvidence(status) {
|
|
44665
|
+
if (status.provider !== "claude") return null;
|
|
44666
|
+
try {
|
|
44667
|
+
const notice = await inspectClaudeBridgeExecutable(
|
|
44668
|
+
status.providerExecutable ?? "claude-agent-acp",
|
|
44669
|
+
{ pathEnv: process.env.PATH, env: process.env }
|
|
44670
|
+
);
|
|
44671
|
+
return {
|
|
44672
|
+
executable: notice.executable,
|
|
44673
|
+
providerVersion: notice.providerVersion,
|
|
44674
|
+
bundledAgentSdkVersion: notice.bundledAgentSdkVersion,
|
|
44675
|
+
bundledClaudeCodeVersion: notice.bundledClaudeCodeVersion
|
|
44676
|
+
};
|
|
44677
|
+
} catch {
|
|
44678
|
+
return {
|
|
44679
|
+
executable: null,
|
|
44680
|
+
providerVersion: null,
|
|
44681
|
+
bundledAgentSdkVersion: null,
|
|
44682
|
+
bundledClaudeCodeVersion: null
|
|
44683
|
+
};
|
|
44684
|
+
}
|
|
44685
|
+
}
|
|
44686
|
+
function versionIsBelow(left, right) {
|
|
44687
|
+
if (!left || !right) return false;
|
|
44688
|
+
try {
|
|
44689
|
+
return compareSemVer(left, right) < 0;
|
|
44690
|
+
} catch {
|
|
44691
|
+
return false;
|
|
44692
|
+
}
|
|
44693
|
+
}
|
|
44694
|
+
function providerRestartRequired(status, installed) {
|
|
44695
|
+
if (!installed) return false;
|
|
44696
|
+
return status.providerVersion !== null && status.providerVersion !== void 0 && installed.providerVersion !== null && status.providerVersion !== installed.providerVersion || status.providerBundledClaudeCodeVersion !== null && status.providerBundledClaudeCodeVersion !== void 0 && installed.bundledClaudeCodeVersion !== null && status.providerBundledClaudeCodeVersion !== installed.bundledClaudeCodeVersion;
|
|
44697
|
+
}
|
|
44698
|
+
function listenerStatusJson(status, permissionMode, evidence = {
|
|
44699
|
+
pendingForMainOldestAt: null,
|
|
44700
|
+
hookSurfaceExists: false,
|
|
44701
|
+
hookSurfaceAdvanced: false
|
|
44702
|
+
}, nowMs = Date.now(), installed = null) {
|
|
42681
44703
|
const mode3 = permissionMode ?? status.permissionMode;
|
|
44704
|
+
const attendance = listenerAttendanceState(status, evidence);
|
|
44705
|
+
const pending = status.pendingForMainCount ?? 0;
|
|
44706
|
+
const readHealth = status.readHealth ?? emptyListenerReadHealth();
|
|
44707
|
+
const readSummary = listenerReadHealthSummary(status, nowMs);
|
|
44708
|
+
const lapseNotices = listenerLapseNotices(status, readSummary);
|
|
42682
44709
|
return {
|
|
42683
44710
|
...status,
|
|
44711
|
+
providerExecutable: status.providerExecutable ?? null,
|
|
44712
|
+
providerExecutableMeasured: typeof status.providerExecutable === "string",
|
|
44713
|
+
providerVersion: status.providerVersion ?? null,
|
|
44714
|
+
providerVersionMeasured: typeof status.providerVersion === "string",
|
|
44715
|
+
providerBundledAgentSdkVersion: status.providerBundledAgentSdkVersion ?? null,
|
|
44716
|
+
providerBundledClaudeCodeVersion: status.providerBundledClaudeCodeVersion ?? null,
|
|
44717
|
+
providerMinimumRequiredVersion: status.providerMinimumRequiredVersion ?? null,
|
|
44718
|
+
lastErrorReasonCode: status.lastErrorReasonCode ?? null,
|
|
44719
|
+
providerBelowDemandedMinimum: versionIsBelow(
|
|
44720
|
+
status.providerBundledClaudeCodeVersion,
|
|
44721
|
+
status.providerMinimumRequiredVersion
|
|
44722
|
+
),
|
|
44723
|
+
providerOnDiskExecutable: installed?.executable ?? null,
|
|
44724
|
+
providerOnDiskVersion: installed?.providerVersion ?? null,
|
|
44725
|
+
providerOnDiskBundledAgentSdkVersion: installed?.bundledAgentSdkVersion ?? null,
|
|
44726
|
+
providerOnDiskBundledClaudeCodeVersion: installed?.bundledClaudeCodeVersion ?? null,
|
|
44727
|
+
providerRestartRequired: providerRestartRequired(status, installed),
|
|
44728
|
+
...attendance,
|
|
44729
|
+
hookSurfaceExists: evidence.hookSurfaceExists,
|
|
44730
|
+
hookSurfaceAdvanced: evidence.hookSurfaceAdvanced,
|
|
44731
|
+
pendingForMainOldestAt: evidence.pendingForMainOldestAt,
|
|
44732
|
+
pendingForMainOldestAgeMs: evidence.pendingForMainOldestAt === null ? null : Math.max(0, nowMs - Date.parse(evidence.pendingForMainOldestAt)),
|
|
44733
|
+
attendanceWarningCode: pending > 0 ? "listener_unattended_main_queue" : null,
|
|
44734
|
+
attendanceNextStep: pending > 0 ? listenerAttendanceRemedy(status.principalId) : null,
|
|
44735
|
+
readRetryCurrentEpisodeStartedAt: readHealth.currentEpisodeStartedAt,
|
|
44736
|
+
readRetryCurrentEpisodeAttempts: readHealth.currentEpisodeAttempts,
|
|
44737
|
+
readRetryCurrentReasonCode: readHealth.currentReasonCode,
|
|
44738
|
+
readRetryCurrentHttpStatus: readHealth.currentHttpStatus,
|
|
44739
|
+
readRetryCurrentErrorConstructor: readHealth.currentErrorConstructor,
|
|
44740
|
+
readRetryCurrentEpisodeDurationMs: readSummary.currentEpisodeDurationMs,
|
|
44741
|
+
readRetryEpisodesLast24h: readSummary.episodesLast24h,
|
|
44742
|
+
readRetryLongestEpisodeAttemptsLast24h: readSummary.longestEpisodeAttemptsLast24h,
|
|
44743
|
+
readRetryLongestEpisodeDurationMsLast24h: readSummary.longestEpisodeDurationMsLast24h,
|
|
44744
|
+
readRetriesLastHour: readSummary.retriesLastHour,
|
|
44745
|
+
readRetryHours: readSummary.retryHours,
|
|
44746
|
+
claimCadenceMs: readHealth.claimCadenceMs,
|
|
44747
|
+
claimThroughputHours: readSummary.claimThroughputHours,
|
|
44748
|
+
listenerLapse: lapseNotices.length > 0,
|
|
44749
|
+
listenerLapseCodes: lapseNotices.map((notice) => notice.code),
|
|
44750
|
+
listenerLapseNextSteps: lapseNotices.map((notice) => notice.nextStep),
|
|
42684
44751
|
deliveryMode: status.deliveryMode ?? null,
|
|
42685
44752
|
pendingDeliveryCount: status.pendingDeliveryCount ?? null,
|
|
42686
44753
|
lastTerminalDeliveryFailureCount: status.lastTerminalDeliveryFailureCount ?? null,
|
|
@@ -42691,6 +44758,8 @@ function listenerStatusJson(status, permissionMode) {
|
|
|
42691
44758
|
deferOverChars: status.deferOverChars ?? null,
|
|
42692
44759
|
pendingForMainCount: status.pendingForMainCount ?? 0,
|
|
42693
44760
|
droppedForMainCount: status.droppedForMainCount ?? 0,
|
|
44761
|
+
connectionsOpened: status.connectionsOpened ?? null,
|
|
44762
|
+
connectionReuseRatio: status.connectionReuseRatio ?? null,
|
|
42694
44763
|
...mode3 ? {
|
|
42695
44764
|
permission_mode: mode3,
|
|
42696
44765
|
/* "allowed once" alone overstates it: allowOnceOrDeny selects allow_once only when the
|
|
@@ -42702,22 +44771,60 @@ function listenerStatusJson(status, permissionMode) {
|
|
|
42702
44771
|
host_limits: listenerHostLimits(status.provider)
|
|
42703
44772
|
};
|
|
42704
44773
|
}
|
|
42705
|
-
function renderListenerStatus(status
|
|
44774
|
+
function renderListenerStatus(status, evidence = {
|
|
44775
|
+
pendingForMainOldestAt: null,
|
|
44776
|
+
hookSurfaceExists: false,
|
|
44777
|
+
hookSurfaceAdvanced: false
|
|
44778
|
+
}, nowMs = Date.now(), installed = null) {
|
|
42706
44779
|
const routeMode = status.routeMode ?? "worker";
|
|
42707
44780
|
const pendingForMainCount = status.pendingForMainCount ?? 0;
|
|
42708
44781
|
const droppedForMainCount = status.droppedForMainCount ?? 0;
|
|
44782
|
+
const unattendedCount = `${pendingForMainCount} ${pendingForMainCount === 1 ? "message is" : "messages are"} unattended`;
|
|
44783
|
+
const attendance = listenerAttendanceState(status, evidence);
|
|
44784
|
+
const readHealth = status.readHealth ?? emptyListenerReadHealth();
|
|
44785
|
+
const readSummary = listenerReadHealthSummary(status, nowMs);
|
|
44786
|
+
const lapseNotices = listenerLapseNotices(status, readSummary);
|
|
42709
44787
|
const lines = [
|
|
42710
|
-
`Listener ${status.state} for agent ${status.principalId}.`,
|
|
44788
|
+
lapseNotices.length > 0 ? `Listener LAPSE for agent ${status.principalId}: ${lapseNotices.map((notice) => notice.code).join(", ")}.` : pendingForMainCount > 0 ? `Listener WARNING for agent ${status.principalId}: ${unattendedCount}.` : `Listener ${status.state} for agent ${status.principalId}.`,
|
|
44789
|
+
`CONNECTED: ${attendance.connected ? "yes" : "no"}. Transport state is ${status.state}.`,
|
|
44790
|
+
`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"}.`,
|
|
44791
|
+
`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
44792
|
`Provider: ${status.provider}; process: ${status.pid}; started: ${status.startedAt}.`,
|
|
44793
|
+
`Provider executable: ${status.providerExecutable ?? "not measured"}.`,
|
|
44794
|
+
`Connections opened: ${status.connectionsOpened ?? "not measured"}.`,
|
|
44795
|
+
`Connection reuse ratio: ${status.connectionReuseRatio ?? "not measured"}.`,
|
|
42712
44796
|
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."
|
|
44797
|
+
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.",
|
|
44798
|
+
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener process error is recorded.",
|
|
44799
|
+
readHealth.currentEpisodeStartedAt === null ? "Current read retry episode: none." : `Current read retry episode: ${readHealth.currentEpisodeAttempts} attempt${readHealth.currentEpisodeAttempts === 1 ? "" : "s"} since ${readHealth.currentEpisodeStartedAt}; reason ${readHealth.currentReasonCode}${readHealth.currentHttpStatus === null ? "" : ` (HTTP ${readHealth.currentHttpStatus})`}${readHealth.currentErrorConstructor === null ? "" : ` (${readHealth.currentErrorConstructor})`}.`,
|
|
44800
|
+
`Read retry episodes in the last 24h: ${readSummary.episodesLast24h}; retries in the rolling hour: ${readSummary.retriesLastHour}.`,
|
|
44801
|
+
readSummary.longestEpisodeAttemptsLast24h === 0 ? "Longest read retry episode in the last 24h: none recorded." : `Longest read retry episode in the last 24h: ${readSummary.longestEpisodeAttemptsLast24h} attempts over ${Math.floor(readSummary.longestEpisodeDurationMsLast24h / 1e3)}s.`,
|
|
44802
|
+
readSummary.retryHours.length === 0 ? "Read retries by hour in the last 24h: none." : `Read retries by hour in the last 24h: ${readSummary.retryHours.map((hour) => `${hour.hourStart}=${hour.retries}`).join("; ")}.`,
|
|
44803
|
+
readSummary.claimThroughputHours.length === 0 ? "Claim throughput by full hour: no complete listener hour is available yet." : `Claim throughput by full hour: ${readSummary.claimThroughputHours.map((hour) => `${hour.hourStart} ${hour.claims}/${Math.round(hour.expectedClaims)} (${hour.ratio.toFixed(3)})`).join("; ")}.`
|
|
42715
44804
|
];
|
|
44805
|
+
for (const notice of lapseNotices) {
|
|
44806
|
+
lines.push(`WARNING [${notice.code}]: ${notice.message}`);
|
|
44807
|
+
lines.push(`Next: ${notice.nextStep}`);
|
|
44808
|
+
}
|
|
42716
44809
|
if (status.lastErrorDetail) {
|
|
42717
44810
|
const [first, ...rest] = status.lastErrorDetail.split("\n");
|
|
42718
44811
|
lines.push(`Last error detail (local only): ${first}`);
|
|
42719
44812
|
for (const line of rest) lines.push(` ${line}`);
|
|
42720
44813
|
}
|
|
44814
|
+
if (status.lastErrorReasonCode) {
|
|
44815
|
+
lines.push(`Last provider reason code: ${status.lastErrorReasonCode}.`);
|
|
44816
|
+
}
|
|
44817
|
+
if (status.provider === "claude" && status.lastErrorCode === "permission_canary_failed") {
|
|
44818
|
+
lines.push(
|
|
44819
|
+
`Canary diagnosis: ${listenerFailureMessage(
|
|
44820
|
+
status.lastErrorCode,
|
|
44821
|
+
status.provider,
|
|
44822
|
+
status.lastErrorDetail,
|
|
44823
|
+
status.lastErrorReasonCode,
|
|
44824
|
+
status.providerMinimumRequiredVersion
|
|
44825
|
+
)}.`
|
|
44826
|
+
);
|
|
44827
|
+
}
|
|
42721
44828
|
if (status.lastWorkerStderrTail) {
|
|
42722
44829
|
const tailLines = status.lastWorkerStderrTail.split("\n").filter((line) => line.trim().length > 0);
|
|
42723
44830
|
lines.push("Worker stderr (local log only):");
|
|
@@ -42727,8 +44834,34 @@ function renderListenerStatus(status) {
|
|
|
42727
44834
|
}
|
|
42728
44835
|
if (status.providerVersion && status.providerLastMeasuredVersion) {
|
|
42729
44836
|
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.`
|
|
44837
|
+
status.providerVersion === status.providerLastMeasuredVersion ? `Provider version: ${status.providerVersion} (last measured: ${status.providerLastMeasuredVersion}).` : status.state === "ready" ? `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.` : `Provider version ${status.providerVersion} is newer than the last measured version ${status.providerLastMeasuredVersion}. It was measured before startup failed; compatibility was not established. Next: resolve the startup failure before verifying this provider release.`
|
|
44838
|
+
);
|
|
44839
|
+
} else {
|
|
44840
|
+
lines.push("Provider version: not measured.");
|
|
44841
|
+
}
|
|
44842
|
+
if (status.provider === "claude") {
|
|
44843
|
+
lines.push(
|
|
44844
|
+
`Bundled Claude Code version: ${status.providerBundledClaudeCodeVersion ?? "not measured"}.`,
|
|
44845
|
+
`Bundled Claude agent SDK version: ${status.providerBundledAgentSdkVersion ?? "not measured"}.`
|
|
42731
44846
|
);
|
|
44847
|
+
if (status.providerMinimumRequiredVersion) {
|
|
44848
|
+
lines.push(
|
|
44849
|
+
`Last API minimum demanded: Claude Code ${status.providerMinimumRequiredVersion}.`
|
|
44850
|
+
);
|
|
44851
|
+
}
|
|
44852
|
+
if (versionIsBelow(
|
|
44853
|
+
status.providerBundledClaudeCodeVersion,
|
|
44854
|
+
status.providerMinimumRequiredVersion
|
|
44855
|
+
)) {
|
|
44856
|
+
lines.push(
|
|
44857
|
+
`WARNING [claude_bridge_below_api_minimum]: this listener has bundled Claude Code ${status.providerBundledClaudeCodeVersion}, below the API minimum ${status.providerMinimumRequiredVersion}. Install the current bridge (npm i -g @agentclientprotocol/claude-agent-acp@latest), restart the listener, then run cswarm listen status and confirm that the bundled Claude Code version meets the API minimum ${status.providerMinimumRequiredVersion}.`
|
|
44858
|
+
);
|
|
44859
|
+
}
|
|
44860
|
+
if (providerRestartRequired(status, installed)) {
|
|
44861
|
+
lines.push(
|
|
44862
|
+
`A different Claude bridge is on disk: ${installed?.providerVersion ?? "version not measured"}${installed?.bundledClaudeCodeVersion ? ` (bundled Claude Code ${installed.bundledClaudeCodeVersion})` : ""}. Restart to pick up ${installed?.providerVersion ?? "the on-disk bridge"}.`
|
|
44863
|
+
);
|
|
44864
|
+
}
|
|
42732
44865
|
}
|
|
42733
44866
|
if (status.deliveryMode === "durable_claim") {
|
|
42734
44867
|
lines.push("Delivery mode: durable claim and acknowledgement.");
|
|
@@ -42755,8 +44888,14 @@ function renderListenerStatus(status) {
|
|
|
42755
44888
|
}
|
|
42756
44889
|
if (pendingForMainCount > 0) {
|
|
42757
44890
|
lines.push(
|
|
42758
|
-
|
|
44891
|
+
`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
44892
|
);
|
|
44893
|
+
lines.push(`Next: ${listenerAttendanceRemedy(status.principalId)}`);
|
|
44894
|
+
if (status.state === "stopped" || status.state === "failed") {
|
|
44895
|
+
lines.push(
|
|
44896
|
+
`${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)}`
|
|
44897
|
+
);
|
|
44898
|
+
}
|
|
42760
44899
|
}
|
|
42761
44900
|
}
|
|
42762
44901
|
if (status.lastTerminalDeliveryFailureCount !== null && status.lastTerminalDeliveryFailureCount > 0) {
|
|
@@ -42776,16 +44915,49 @@ async function unsurfacedPendingMainStats(instanceDirectory, fallback) {
|
|
|
42776
44915
|
const queue = new FilePendingMainQueue(instanceDirectory);
|
|
42777
44916
|
const pending = await queue.read();
|
|
42778
44917
|
const stats = await queue.stats();
|
|
42779
|
-
const
|
|
44918
|
+
const surface = new FileHookSurfaceStore(instanceDirectory);
|
|
44919
|
+
const staged = await surface.stage(
|
|
42780
44920
|
pending,
|
|
42781
44921
|
stats.droppedCount
|
|
42782
44922
|
);
|
|
42783
|
-
|
|
44923
|
+
const hook = await surface.evidence();
|
|
44924
|
+
const oldestAt = staged.unseen.reduce((oldest, item) => oldest === null || Date.parse(item.queuedAt) < Date.parse(oldest) ? item.queuedAt : oldest, null);
|
|
44925
|
+
return {
|
|
44926
|
+
count: staged.unseen.length,
|
|
44927
|
+
droppedCount: stats.droppedCount,
|
|
44928
|
+
oldestAt,
|
|
44929
|
+
hookSurfaceExists: hook.exists,
|
|
44930
|
+
hookSurfaceAdvanced: hook.surfacedSignalIds.length > 0
|
|
44931
|
+
};
|
|
42784
44932
|
} catch {
|
|
42785
|
-
return
|
|
44933
|
+
return {
|
|
44934
|
+
...fallback,
|
|
44935
|
+
oldestAt: null,
|
|
44936
|
+
hookSurfaceExists: false,
|
|
44937
|
+
hookSurfaceAdvanced: false
|
|
44938
|
+
};
|
|
44939
|
+
}
|
|
44940
|
+
}
|
|
44941
|
+
function quotedListenerFailureDetail(detail) {
|
|
44942
|
+
const recorded = detail?.trim();
|
|
44943
|
+
if (!recorded) return "not recorded";
|
|
44944
|
+
const bounded = recorded.length > 600 ? `${recorded.slice(0, 599)}\u2026` : recorded;
|
|
44945
|
+
return JSON.stringify(bounded);
|
|
44946
|
+
}
|
|
44947
|
+
function listenerProviderIdentitySummary(status) {
|
|
44948
|
+
const parts = [
|
|
44949
|
+
`Provider executable: ${status.providerExecutable ?? "not measured"}`,
|
|
44950
|
+
`provider version: ${status.providerVersion ?? "not measured"}`
|
|
44951
|
+
];
|
|
44952
|
+
if (status.provider === "claude") {
|
|
44953
|
+
parts.push(
|
|
44954
|
+
`bundled Claude Code: ${status.providerBundledClaudeCodeVersion ?? "not measured"}`,
|
|
44955
|
+
`bundled Claude agent SDK: ${status.providerBundledAgentSdkVersion ?? "not measured"}`
|
|
44956
|
+
);
|
|
42786
44957
|
}
|
|
44958
|
+
return parts.join("; ");
|
|
42787
44959
|
}
|
|
42788
|
-
function listenerFailureMessage(code, provider) {
|
|
44960
|
+
function listenerFailureMessage(code, provider, detail, reasonCode, minimumRequiredVersion) {
|
|
42789
44961
|
if (code === "version_below_floor") {
|
|
42790
44962
|
if (provider === "codex") {
|
|
42791
44963
|
return "the Codex listener requires codex-acp 1.1.9 or newer; update the bridge, then retry";
|
|
@@ -42804,6 +44976,9 @@ function listenerFailureMessage(code, provider) {
|
|
|
42804
44976
|
if (code === "version_refused") {
|
|
42805
44977
|
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
44978
|
}
|
|
44979
|
+
if (code === "executable_not_bridge" && provider === "codex") {
|
|
44980
|
+
return "this is the Codex CLI; --codex-executable takes the codex-acp bridge (npm i -g @agentclientprotocol/codex-acp)";
|
|
44981
|
+
}
|
|
42807
44982
|
if (code === "executable_missing" && provider === "claude") {
|
|
42808
44983
|
return "claude-agent-acp is not installed; run npm install -g @agentclientprotocol/claude-agent-acp@latest (minimum 0.64.2), then retry";
|
|
42809
44984
|
}
|
|
@@ -42842,10 +45017,25 @@ function listenerFailureMessage(code, provider) {
|
|
|
42842
45017
|
}
|
|
42843
45018
|
if (code === "permission_canary_failed") {
|
|
42844
45019
|
if (provider === "claude") {
|
|
42845
|
-
|
|
45020
|
+
const shape = classifyClaudeCanaryFailure(detail, reasonCode);
|
|
45021
|
+
const ran = "the Claude ACP permission canary ran, but no workspace signal prompt was delivered";
|
|
45022
|
+
const response = `bridge response [${shape.code}]: ${quotedListenerFailureDetail(detail)}`;
|
|
45023
|
+
if (shape.code === "claude_bridge_version_required") {
|
|
45024
|
+
const minimum = minimumRequiredVersion ?? shape.minimumRequiredVersion;
|
|
45025
|
+
return `${ran}. ${response}. Next: install the current bridge (npm i -g @agentclientprotocol/claude-agent-acp@latest), restart the listener, then run cswarm listen status and confirm that the bundled Claude Code version meets the API minimum ${minimum ?? "reported there"}`;
|
|
45026
|
+
}
|
|
45027
|
+
if (shape.code === "claude_canary_timeout") {
|
|
45028
|
+
return `${ran}. ${response}. Next: run claude -p and check for session-limit text, check host load, then retry`;
|
|
45029
|
+
}
|
|
45030
|
+
if (shape.code === "claude_canary_auth_failed") {
|
|
45031
|
+
return `${ran}. ${response}. Next: confirm Claude Code keychain/OAuth sign-in, then retry`;
|
|
45032
|
+
}
|
|
45033
|
+
return `${ran}. ${response}. The cause was not determined. Next: inspect the quoted bridge response and local worker stderr, then retry only after the cause is known or the failure appears transient`;
|
|
42846
45034
|
}
|
|
42847
45035
|
if (provider === "codex") {
|
|
42848
|
-
|
|
45036
|
+
const recorded = detail?.trim();
|
|
45037
|
+
const gate = "the Codex listener did not pass the read-only ACP permission safety gate; no workspace signal prompt was delivered";
|
|
45038
|
+
return recorded ? `${gate}. Recorded reason: ${JSON.stringify(recorded)}` : `${gate}. The recorded reason was unavailable. Next: run cswarm listen status, then retry`;
|
|
42849
45039
|
}
|
|
42850
45040
|
if (provider === "grok") {
|
|
42851
45041
|
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";
|
|
@@ -42928,11 +45118,19 @@ async function runConfiguredListener(options) {
|
|
|
42928
45118
|
principalId: options.principalId,
|
|
42929
45119
|
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
42930
45120
|
});
|
|
42931
|
-
const
|
|
42932
|
-
|
|
42933
|
-
|
|
42934
|
-
|
|
42935
|
-
|
|
45121
|
+
const httpClient = new ListenerHttpClient();
|
|
45122
|
+
let liveCredentialSession;
|
|
45123
|
+
try {
|
|
45124
|
+
liveCredentialSession = await agentSession(
|
|
45125
|
+
options.cloud,
|
|
45126
|
+
options.workspaceId,
|
|
45127
|
+
options.agent,
|
|
45128
|
+
httpClient.fetch
|
|
45129
|
+
);
|
|
45130
|
+
} catch (error) {
|
|
45131
|
+
httpClient.close();
|
|
45132
|
+
throw error;
|
|
45133
|
+
}
|
|
42936
45134
|
let storedCredential = null;
|
|
42937
45135
|
const credentialSession = {
|
|
42938
45136
|
bearer: async () => {
|
|
@@ -42960,7 +45158,7 @@ async function runConfiguredListener(options) {
|
|
|
42960
45158
|
options.cloud,
|
|
42961
45159
|
credential,
|
|
42962
45160
|
options.workspaceId,
|
|
42963
|
-
context
|
|
45161
|
+
{ ...context, fetcher: httpClient.fetch }
|
|
42964
45162
|
);
|
|
42965
45163
|
const provenance = listenerSenderProvenance(signal, senderDirectory);
|
|
42966
45164
|
if (context.includeBrainDigest !== true) return provenance;
|
|
@@ -42971,7 +45169,8 @@ async function runConfiguredListener(options) {
|
|
|
42971
45169
|
options.workspaceId,
|
|
42972
45170
|
{
|
|
42973
45171
|
...context.signal ? { signal: context.signal } : {},
|
|
42974
|
-
deadlineMs: context.deadlineMs
|
|
45172
|
+
deadlineMs: context.deadlineMs,
|
|
45173
|
+
fetcher: httpClient.fetch
|
|
42975
45174
|
}
|
|
42976
45175
|
);
|
|
42977
45176
|
const brainDigest = await new FileBrainDigestStore(
|
|
@@ -43011,7 +45210,19 @@ async function runConfiguredListener(options) {
|
|
|
43011
45210
|
let workerStderrGeneration = 0;
|
|
43012
45211
|
let providerVersionNotice = null;
|
|
43013
45212
|
const onVersionNotice = (notice) => {
|
|
43014
|
-
providerVersionNotice =
|
|
45213
|
+
providerVersionNotice = {
|
|
45214
|
+
runningVersion: notice.runningVersion,
|
|
45215
|
+
lastMeasuredVersion: notice.lastMeasuredVersion
|
|
45216
|
+
};
|
|
45217
|
+
};
|
|
45218
|
+
const onClaudeRuntimeNotice = (notice) => {
|
|
45219
|
+
providerVersionNotice = {
|
|
45220
|
+
runningVersion: notice.providerVersion,
|
|
45221
|
+
lastMeasuredVersion: notice.lastMeasuredVersion,
|
|
45222
|
+
executable: notice.executable,
|
|
45223
|
+
bundledAgentSdkVersion: notice.bundledAgentSdkVersion,
|
|
45224
|
+
bundledClaudeCodeVersion: notice.bundledClaudeCodeVersion
|
|
45225
|
+
};
|
|
43015
45226
|
};
|
|
43016
45227
|
const newWorkerStderrTailSink = () => {
|
|
43017
45228
|
const generation = ++workerStderrGeneration;
|
|
@@ -43021,7 +45232,7 @@ async function runConfiguredListener(options) {
|
|
|
43021
45232
|
lastWorkerStderrTail = tail.length > 0 ? tail : null;
|
|
43022
45233
|
};
|
|
43023
45234
|
};
|
|
43024
|
-
const newModel = (onCanaryAttempt) => {
|
|
45235
|
+
const newModel = (onCanaryAttempt, events) => {
|
|
43025
45236
|
providerVersionNotice = null;
|
|
43026
45237
|
return options.provider === "opencode" ? new OpenCodeListenerModel({
|
|
43027
45238
|
cwd: options.cwd,
|
|
@@ -43030,6 +45241,7 @@ async function runConfiguredListener(options) {
|
|
|
43030
45241
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
43031
45242
|
onCanaryAttempt,
|
|
43032
45243
|
onVersionNotice,
|
|
45244
|
+
events,
|
|
43033
45245
|
...options.model ? { model: options.model } : {},
|
|
43034
45246
|
...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
43035
45247
|
}) : options.provider === "claude" ? new ClaudeListenerModel({
|
|
@@ -43038,7 +45250,9 @@ async function runConfiguredListener(options) {
|
|
|
43038
45250
|
promptTimeoutMs: resolveTurnBudgetMs,
|
|
43039
45251
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
43040
45252
|
onCanaryAttempt,
|
|
45253
|
+
onRuntimeNotice: onClaudeRuntimeNotice,
|
|
43041
45254
|
onVersionNotice,
|
|
45255
|
+
events,
|
|
43042
45256
|
...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
43043
45257
|
}) : options.provider === "codex" ? new CodexListenerModel({
|
|
43044
45258
|
cwd: options.cwd,
|
|
@@ -43047,6 +45261,7 @@ async function runConfiguredListener(options) {
|
|
|
43047
45261
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
43048
45262
|
onCanaryAttempt,
|
|
43049
45263
|
onVersionNotice,
|
|
45264
|
+
events,
|
|
43050
45265
|
...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
|
|
43051
45266
|
}) : new GrokListenerModel({
|
|
43052
45267
|
cwd: options.cwd,
|
|
@@ -43055,6 +45270,7 @@ async function runConfiguredListener(options) {
|
|
|
43055
45270
|
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
43056
45271
|
onCanaryAttempt,
|
|
43057
45272
|
onVersionNotice,
|
|
45273
|
+
events,
|
|
43058
45274
|
...options.model ? { model: options.model } : {},
|
|
43059
45275
|
...options.effort ? { effort: options.effort } : {},
|
|
43060
45276
|
...options.executable ? { executable: options.executable } : {}
|
|
@@ -43077,6 +45293,7 @@ async function runConfiguredListener(options) {
|
|
|
43077
45293
|
workspaceId: options.workspaceId,
|
|
43078
45294
|
principalId: options.principalId,
|
|
43079
45295
|
provider: options.provider,
|
|
45296
|
+
cswarmVersion: CLI_BUILD_VERSION,
|
|
43080
45297
|
permissionMode: options.permissionMode,
|
|
43081
45298
|
routeMode,
|
|
43082
45299
|
deferOverChars,
|
|
@@ -43084,6 +45301,7 @@ async function runConfiguredListener(options) {
|
|
|
43084
45301
|
// one has run, else the configured cap.
|
|
43085
45302
|
getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
|
|
43086
45303
|
getProviderVersionNotice: () => providerVersionNotice,
|
|
45304
|
+
getConnectionMetrics: () => httpClient.metrics(),
|
|
43087
45305
|
takeWorkerStderrTail: () => {
|
|
43088
45306
|
const tail = lastWorkerStderrTail;
|
|
43089
45307
|
lastWorkerStderrTail = null;
|
|
@@ -43117,28 +45335,48 @@ async function runConfiguredListener(options) {
|
|
|
43117
45335
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
43118
45336
|
});
|
|
43119
45337
|
};
|
|
43120
|
-
|
|
43121
|
-
target: options.cloud,
|
|
45338
|
+
const activity = new ListenerActivityController({
|
|
43122
45339
|
workspaceId: options.workspaceId,
|
|
43123
|
-
|
|
43124
|
-
|
|
43125
|
-
|
|
43126
|
-
|
|
43127
|
-
|
|
43128
|
-
onEvent,
|
|
43129
|
-
declareModel: listenerModelLabel(options.provider),
|
|
43130
|
-
listenerInstanceId,
|
|
43131
|
-
deliveryJournal: selectedJournal,
|
|
43132
|
-
resolveSenderProvenance,
|
|
43133
|
-
routeMode,
|
|
43134
|
-
deferOverChars,
|
|
43135
|
-
pendingMainQueue
|
|
45340
|
+
transport: new AgentActivityEndpointTransport(
|
|
45341
|
+
options.cloud,
|
|
45342
|
+
credentialSession,
|
|
45343
|
+
httpClient.fetch
|
|
45344
|
+
)
|
|
43136
45345
|
});
|
|
45346
|
+
const instrumentedModel = activity.instrumentModel(
|
|
45347
|
+
newModel(onCanaryAttempt, activity.events)
|
|
45348
|
+
);
|
|
45349
|
+
try {
|
|
45350
|
+
return await runListenerRuntime({
|
|
45351
|
+
target: options.cloud,
|
|
45352
|
+
workspaceId: options.workspaceId,
|
|
45353
|
+
principalId: options.principalId,
|
|
45354
|
+
credentialSession,
|
|
45355
|
+
store: effectStore,
|
|
45356
|
+
model: instrumentedModel,
|
|
45357
|
+
signal,
|
|
45358
|
+
onEvent: (event) => {
|
|
45359
|
+
activity.onRuntimeEvent(event);
|
|
45360
|
+
onEvent(event);
|
|
45361
|
+
},
|
|
45362
|
+
declareModel: listenerModelLabel(options.provider),
|
|
45363
|
+
listenerInstanceId,
|
|
45364
|
+
deliveryJournal: selectedJournal,
|
|
45365
|
+
resolveSenderProvenance,
|
|
45366
|
+
routeMode,
|
|
45367
|
+
deferOverChars,
|
|
45368
|
+
pendingMainQueue,
|
|
45369
|
+
fetcher: httpClient.fetch
|
|
45370
|
+
});
|
|
45371
|
+
} finally {
|
|
45372
|
+
activity.close();
|
|
45373
|
+
}
|
|
43137
45374
|
}
|
|
43138
45375
|
});
|
|
43139
45376
|
} finally {
|
|
43140
45377
|
process.off("SIGINT", onProcessSignal);
|
|
43141
45378
|
process.off("SIGTERM", onProcessSignal);
|
|
45379
|
+
httpClient.close();
|
|
43142
45380
|
}
|
|
43143
45381
|
}
|
|
43144
45382
|
async function runListenStart(args) {
|
|
@@ -43159,6 +45397,7 @@ async function runListenStart(args) {
|
|
|
43159
45397
|
"turn-budget",
|
|
43160
45398
|
"route",
|
|
43161
45399
|
"defer-over",
|
|
45400
|
+
"allow-unattended",
|
|
43162
45401
|
"foreground",
|
|
43163
45402
|
"json"
|
|
43164
45403
|
], 2);
|
|
@@ -43198,6 +45437,9 @@ async function runListenStart(args) {
|
|
|
43198
45437
|
`a listener is already ${existing.state} for agent ${principalId}`
|
|
43199
45438
|
);
|
|
43200
45439
|
}
|
|
45440
|
+
if (routing.routeMode !== "worker" && !args.has("allow-unattended") && !await listenerHasAttendanceSurface(paths.instanceDirectory, cwd, principalId)) {
|
|
45441
|
+
throw new ListenerUnattendedRefusedError(principalId);
|
|
45442
|
+
}
|
|
43201
45443
|
let status;
|
|
43202
45444
|
if (args.has("foreground")) {
|
|
43203
45445
|
status = await runConfiguredListener({
|
|
@@ -43282,16 +45524,39 @@ async function runListenStart(args) {
|
|
|
43282
45524
|
});
|
|
43283
45525
|
} catch (error) {
|
|
43284
45526
|
if (error instanceof ListenerStartupError) {
|
|
43285
|
-
|
|
45527
|
+
const failedStatus = await effectiveListenerStatus(paths).catch(() => null);
|
|
45528
|
+
const detail = failedStatus?.lastErrorCode === error.code ? failedStatus.lastErrorDetail : null;
|
|
45529
|
+
const reasonCode = failedStatus?.lastErrorCode === error.code ? failedStatus.lastErrorReasonCode : null;
|
|
45530
|
+
const message = listenerFailureMessage(
|
|
45531
|
+
error.code,
|
|
45532
|
+
provider,
|
|
45533
|
+
detail,
|
|
45534
|
+
reasonCode,
|
|
45535
|
+
failedStatus?.providerMinimumRequiredVersion
|
|
45536
|
+
);
|
|
45537
|
+
throw new Error(
|
|
45538
|
+
failedStatus === null ? message : `${message}. ${listenerProviderIdentitySummary(failedStatus)}`
|
|
45539
|
+
);
|
|
43286
45540
|
}
|
|
43287
45541
|
throw error;
|
|
43288
45542
|
}
|
|
43289
45543
|
}
|
|
43290
45544
|
if (status.state === "failed") {
|
|
43291
45545
|
throw new Error(
|
|
43292
|
-
listenerFailureMessage(
|
|
45546
|
+
`${listenerFailureMessage(
|
|
45547
|
+
status.lastErrorCode ?? "unknown_error",
|
|
45548
|
+
provider,
|
|
45549
|
+
status.lastErrorDetail,
|
|
45550
|
+
status.lastErrorReasonCode,
|
|
45551
|
+
status.providerMinimumRequiredVersion
|
|
45552
|
+
)}. ${listenerProviderIdentitySummary(status)}`
|
|
43293
45553
|
);
|
|
43294
45554
|
}
|
|
45555
|
+
let attendanceEvidence = {
|
|
45556
|
+
pendingForMainOldestAt: null,
|
|
45557
|
+
hookSurfaceExists: false,
|
|
45558
|
+
hookSurfaceAdvanced: false
|
|
45559
|
+
};
|
|
43295
45560
|
if ((status.routeMode ?? "worker") !== "worker") {
|
|
43296
45561
|
const recordedPending = status.pendingForMainCount ?? 0;
|
|
43297
45562
|
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
@@ -43304,9 +45569,14 @@ async function runListenStart(args) {
|
|
|
43304
45569
|
pendingForMainCount: queueStats.count,
|
|
43305
45570
|
droppedForMainCount: queueStats.droppedCount
|
|
43306
45571
|
};
|
|
45572
|
+
attendanceEvidence = {
|
|
45573
|
+
pendingForMainOldestAt: queueStats.oldestAt,
|
|
45574
|
+
hookSurfaceExists: queueStats.hookSurfaceExists,
|
|
45575
|
+
hookSurfaceAdvanced: queueStats.hookSurfaceAdvanced
|
|
45576
|
+
};
|
|
43307
45577
|
}
|
|
43308
45578
|
if (args.has("json")) {
|
|
43309
|
-
printJson(listenerStatusJson(status, permissionMode));
|
|
45579
|
+
printJson(listenerStatusJson(status, permissionMode, attendanceEvidence));
|
|
43310
45580
|
return;
|
|
43311
45581
|
}
|
|
43312
45582
|
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 +45588,8 @@ async function runListenStart(args) {
|
|
|
43318
45588
|
` : `The Grok worker uses your selected cwd and local Grok configuration, including user and cmux hooks. ${workerAudience}
|
|
43319
45589
|
`;
|
|
43320
45590
|
process.stdout.write(
|
|
43321
|
-
`${args.has("foreground") ? "Listener stopped." : "Listener is ready and will keep receiving after this command exits."}
|
|
43322
|
-
${renderListenerStatus(status)}
|
|
45591
|
+
`${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."}
|
|
45592
|
+
${renderListenerStatus(status, attendanceEvidence)}
|
|
43323
45593
|
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
45594
|
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
45595
|
` + 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 +45708,11 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
43438
45708
|
}
|
|
43439
45709
|
return;
|
|
43440
45710
|
}
|
|
45711
|
+
let attendanceEvidence = {
|
|
45712
|
+
pendingForMainOldestAt: null,
|
|
45713
|
+
hookSurfaceExists: false,
|
|
45714
|
+
hookSurfaceAdvanced: false
|
|
45715
|
+
};
|
|
43441
45716
|
if ((status.routeMode ?? "worker") !== "worker") {
|
|
43442
45717
|
const recordedPending = status.pendingForMainCount ?? 0;
|
|
43443
45718
|
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
@@ -43450,13 +45725,93 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
43450
45725
|
pendingForMainCount: queueStats.count,
|
|
43451
45726
|
droppedForMainCount: queueStats.droppedCount
|
|
43452
45727
|
};
|
|
45728
|
+
attendanceEvidence = {
|
|
45729
|
+
pendingForMainOldestAt: queueStats.oldestAt,
|
|
45730
|
+
hookSurfaceExists: queueStats.hookSurfaceExists,
|
|
45731
|
+
hookSurfaceAdvanced: queueStats.hookSurfaceAdvanced
|
|
45732
|
+
};
|
|
43453
45733
|
}
|
|
45734
|
+
const installed = command2 === "status" ? await listenerProviderInstallEvidence(status) : null;
|
|
43454
45735
|
if (args.has("json")) {
|
|
43455
|
-
printJson(
|
|
45736
|
+
printJson(
|
|
45737
|
+
listenerStatusJson(
|
|
45738
|
+
status,
|
|
45739
|
+
void 0,
|
|
45740
|
+
attendanceEvidence,
|
|
45741
|
+
Date.now(),
|
|
45742
|
+
installed
|
|
45743
|
+
)
|
|
45744
|
+
);
|
|
43456
45745
|
} else {
|
|
43457
|
-
process.stdout.write(
|
|
43458
|
-
|
|
45746
|
+
process.stdout.write(
|
|
45747
|
+
`${renderListenerStatus(status, attendanceEvidence, Date.now(), installed)}
|
|
45748
|
+
`
|
|
45749
|
+
);
|
|
45750
|
+
}
|
|
45751
|
+
}
|
|
45752
|
+
async function runListenCanary(args) {
|
|
45753
|
+
args.assertShape([
|
|
45754
|
+
...TARGET_FLAGS,
|
|
45755
|
+
...CREDENTIAL_FLAGS,
|
|
45756
|
+
"workspace-id",
|
|
45757
|
+
"state-dir",
|
|
45758
|
+
"wait",
|
|
45759
|
+
"json"
|
|
45760
|
+
], 2);
|
|
45761
|
+
if (!hasAgentCredential(args)) {
|
|
45762
|
+
throw new Error(
|
|
45763
|
+
"listen canary requires --agent-token-file or --agent-token-stdin; credentials are never accepted on argv"
|
|
45764
|
+
);
|
|
45765
|
+
}
|
|
45766
|
+
const cloud = await target(args);
|
|
45767
|
+
const workspaceId2 = listenerUuid(
|
|
45768
|
+
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
45769
|
+
"workspace-id"
|
|
45770
|
+
);
|
|
45771
|
+
const agent = await agentCredential(args);
|
|
45772
|
+
if (agent.principalId === null) {
|
|
45773
|
+
throw new Error(
|
|
45774
|
+
"listen canary needs the complete JSON agent credential so it can address the agent and select its listener state"
|
|
45775
|
+
);
|
|
45776
|
+
}
|
|
45777
|
+
const principalId = listenerUuid(agent.principalId, "principal-id");
|
|
45778
|
+
const stateDirectory2 = listenerStateDirectory(args);
|
|
45779
|
+
const paths = listenerPaths({
|
|
45780
|
+
profileId: cloud.profileId,
|
|
45781
|
+
workspaceId: workspaceId2,
|
|
45782
|
+
principalId,
|
|
45783
|
+
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {}
|
|
45784
|
+
});
|
|
45785
|
+
const waitMs = parseWaitSeconds(args.optional("wait") ?? "10") * 1e3;
|
|
45786
|
+
const httpClient = new ListenerHttpClient();
|
|
45787
|
+
let result;
|
|
45788
|
+
try {
|
|
45789
|
+
result = await runListenerAttendanceCanary({
|
|
45790
|
+
target: cloud,
|
|
45791
|
+
workspaceId: workspaceId2,
|
|
45792
|
+
principalId,
|
|
45793
|
+
paths,
|
|
45794
|
+
waitMs,
|
|
45795
|
+
fetcher: httpClient.fetch,
|
|
45796
|
+
// Canary must remain read-only apart from its one self-note. It therefore
|
|
45797
|
+
// uses the presented token and never enters the renewal/mint path.
|
|
45798
|
+
credential: async () => agent.token
|
|
45799
|
+
});
|
|
45800
|
+
} finally {
|
|
45801
|
+
httpClient.close();
|
|
43459
45802
|
}
|
|
45803
|
+
if (args.has("json")) {
|
|
45804
|
+
printJson({
|
|
45805
|
+
workspaceId: workspaceId2,
|
|
45806
|
+
principalId,
|
|
45807
|
+
...result
|
|
45808
|
+
});
|
|
45809
|
+
return;
|
|
45810
|
+
}
|
|
45811
|
+
process.stdout.write(
|
|
45812
|
+
`${renderListenerAttendanceCanary(result, workspaceId2, principalId)}
|
|
45813
|
+
`
|
|
45814
|
+
);
|
|
43460
45815
|
}
|
|
43461
45816
|
async function runListen(args) {
|
|
43462
45817
|
const command2 = args.positionals[1];
|
|
@@ -43468,7 +45823,11 @@ async function runListen(args) {
|
|
|
43468
45823
|
await runListenStatusOrStop(args, command2);
|
|
43469
45824
|
return;
|
|
43470
45825
|
}
|
|
43471
|
-
|
|
45826
|
+
if (command2 === "canary") {
|
|
45827
|
+
await runListenCanary(args);
|
|
45828
|
+
return;
|
|
45829
|
+
}
|
|
45830
|
+
throw new UsageError("listen requires start, status, stop, or canary");
|
|
43472
45831
|
}
|
|
43473
45832
|
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
43474
45833
|
function scopedClaudeHookCommand(principalId) {
|
|
@@ -43477,6 +45836,46 @@ function scopedClaudeHookCommand(principalId) {
|
|
|
43477
45836
|
function isCommonSwarmClaudeHook(value) {
|
|
43478
45837
|
return typeof value === "string" && (value === CLAUDE_HOOK_COMMAND || /^cswarm hook check --principal-id [0-9a-f-]{36}$/.test(value));
|
|
43479
45838
|
}
|
|
45839
|
+
var ListenerUnattendedRefusedError = class extends Error {
|
|
45840
|
+
code = "listen_unattended_refused";
|
|
45841
|
+
constructor(principalId) {
|
|
45842
|
+
super(
|
|
45843
|
+
`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.`
|
|
45844
|
+
);
|
|
45845
|
+
this.name = "ListenerUnattendedRefusedError";
|
|
45846
|
+
}
|
|
45847
|
+
};
|
|
45848
|
+
function settingsHaveScopedClaudeHook(settings, principalId) {
|
|
45849
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
|
|
45850
|
+
return false;
|
|
45851
|
+
}
|
|
45852
|
+
const promptHooks = settings.hooks.UserPromptSubmit;
|
|
45853
|
+
if (!Array.isArray(promptHooks)) return false;
|
|
45854
|
+
const expected = scopedClaudeHookCommand(principalId);
|
|
45855
|
+
return promptHooks.some((group) => {
|
|
45856
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) return false;
|
|
45857
|
+
const hooks = group.hooks;
|
|
45858
|
+
return Array.isArray(hooks) && hooks.some(
|
|
45859
|
+
(hook) => hook !== null && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === expected
|
|
45860
|
+
);
|
|
45861
|
+
});
|
|
45862
|
+
}
|
|
45863
|
+
async function listenerHasAttendanceSurface(instanceDirectory, cwd, principalId) {
|
|
45864
|
+
const surface = await new FileHookSurfaceStore(instanceDirectory).evidence();
|
|
45865
|
+
if (surface.exists) return true;
|
|
45866
|
+
const repositoryRoot = gitRepositoryRoot(cwd) ?? cwd;
|
|
45867
|
+
const settingsPaths = /* @__PURE__ */ new Set([
|
|
45868
|
+
(0, import_node_path21.join)(repositoryRoot, CLAUDE_PROJECT_SETTINGS_IGNORE_LINE),
|
|
45869
|
+
(0, import_node_path21.join)(repositoryRoot, CLAUDE_REPO_SETTINGS_IGNORE_LINE),
|
|
45870
|
+
userClaudeSettingsTarget().path
|
|
45871
|
+
]);
|
|
45872
|
+
for (const path of settingsPaths) {
|
|
45873
|
+
if (settingsHaveScopedClaudeHook(readClaudeSettings(path), principalId)) {
|
|
45874
|
+
return true;
|
|
45875
|
+
}
|
|
45876
|
+
}
|
|
45877
|
+
return false;
|
|
45878
|
+
}
|
|
43480
45879
|
function claudeUserPromptHookSnippet(principalId) {
|
|
43481
45880
|
return {
|
|
43482
45881
|
hooks: {
|
|
@@ -43495,7 +45894,9 @@ function claudeUserPromptHookSnippet(principalId) {
|
|
|
43495
45894
|
}
|
|
43496
45895
|
var CLAUDE_PROJECT_SETTINGS_IGNORE_LINE = ".claude/settings.local.json";
|
|
43497
45896
|
var CLAUDE_REPO_SETTINGS_IGNORE_LINE = ".claude/settings.json";
|
|
43498
|
-
|
|
45897
|
+
function claudeUserScopeWarning(settingsPath) {
|
|
45898
|
+
return `Warning: --user scope writes settings to ${(0, import_node_path21.dirname)(settingsPath)} and applies to every Claude Code session that reads that directory.`;
|
|
45899
|
+
}
|
|
43499
45900
|
function userClaudeSettingsTarget() {
|
|
43500
45901
|
const configured = process.env.CLAUDE_CONFIG_DIR;
|
|
43501
45902
|
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 +45907,7 @@ function userClaudeSettingsTarget() {
|
|
|
43506
45907
|
};
|
|
43507
45908
|
}
|
|
43508
45909
|
function gitRepositoryRoot(cwd) {
|
|
43509
|
-
const result = (0,
|
|
45910
|
+
const result = (0, import_node_child_process9.spawnSync)(
|
|
43510
45911
|
"git",
|
|
43511
45912
|
["-C", cwd, "rev-parse", "--show-toplevel"],
|
|
43512
45913
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -43526,12 +45927,12 @@ function projectClaudeSettingsTarget(scope, ignoreLine) {
|
|
|
43526
45927
|
const base = root ?? process.cwd();
|
|
43527
45928
|
const path = (0, import_node_path21.join)(base, ignoreLine);
|
|
43528
45929
|
if (root === null) return { path, scope, projectRoot: base };
|
|
43529
|
-
const tracked = (0,
|
|
45930
|
+
const tracked = (0, import_node_child_process9.spawnSync)(
|
|
43530
45931
|
"git",
|
|
43531
45932
|
["-C", root, "ls-files", "--error-unmatch", "--", ignoreLine],
|
|
43532
45933
|
{ encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
|
|
43533
45934
|
);
|
|
43534
|
-
const ignored = (0,
|
|
45935
|
+
const ignored = (0, import_node_child_process9.spawnSync)(
|
|
43535
45936
|
"git",
|
|
43536
45937
|
["-C", root, "check-ignore", "--quiet", "--", ignoreLine],
|
|
43537
45938
|
{ encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }
|
|
@@ -43664,19 +46065,25 @@ async function runHook(args) {
|
|
|
43664
46065
|
process.exit(0);
|
|
43665
46066
|
}, 3e3);
|
|
43666
46067
|
hardExit.unref();
|
|
43667
|
-
|
|
43668
|
-
|
|
43669
|
-
|
|
43670
|
-
|
|
43671
|
-
|
|
43672
|
-
|
|
46068
|
+
const httpClient = new ListenerHttpClient();
|
|
46069
|
+
try {
|
|
46070
|
+
await runListenerHookCheck({
|
|
46071
|
+
...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
|
|
46072
|
+
...principalIds.length === 0 ? {} : { principalIds },
|
|
46073
|
+
fetcher: httpClient.fetch,
|
|
46074
|
+
write: async (output) => {
|
|
46075
|
+
await new Promise((resolve2, reject) => {
|
|
46076
|
+
process.stdout.write(`${output}
|
|
43673
46077
|
`, (error) => {
|
|
43674
|
-
|
|
43675
|
-
|
|
46078
|
+
if (error) reject(error);
|
|
46079
|
+
else resolve2();
|
|
46080
|
+
});
|
|
43676
46081
|
});
|
|
43677
|
-
}
|
|
43678
|
-
}
|
|
43679
|
-
}
|
|
46082
|
+
}
|
|
46083
|
+
});
|
|
46084
|
+
} finally {
|
|
46085
|
+
httpClient.close();
|
|
46086
|
+
}
|
|
43680
46087
|
return;
|
|
43681
46088
|
}
|
|
43682
46089
|
if (command2 !== "install" && command2 !== "uninstall") {
|
|
@@ -43713,7 +46120,7 @@ async function runHook(args) {
|
|
|
43713
46120
|
const settings = readClaudeSettings(path);
|
|
43714
46121
|
const updated = command2 === "install" ? installClaudeHook(settings, principalId) : uninstallClaudeHook(settings);
|
|
43715
46122
|
if (target2.scope === "user") {
|
|
43716
|
-
process.stdout.write(`${
|
|
46123
|
+
process.stdout.write(`${claudeUserScopeWarning(path)}
|
|
43717
46124
|
`);
|
|
43718
46125
|
}
|
|
43719
46126
|
(0, import_node_fs7.mkdirSync)((0, import_node_path21.dirname)(path), { recursive: true });
|
|
@@ -43802,8 +46209,8 @@ async function uploadNamedFile(context, name, bytes) {
|
|
|
43802
46209
|
workspaceId: context.selected.selectedWorkspace,
|
|
43803
46210
|
credential: context.selected.bearer
|
|
43804
46211
|
};
|
|
43805
|
-
const fileId = (0,
|
|
43806
|
-
const versionId = (0,
|
|
46212
|
+
const fileId = (0, import_node_crypto21.randomUUID)();
|
|
46213
|
+
const versionId = (0, import_node_crypto21.randomUUID)();
|
|
43807
46214
|
const createCommandId = newCommandId();
|
|
43808
46215
|
const commitCommandId = newCommandId();
|
|
43809
46216
|
const created = await onceRetried(
|
|
@@ -44214,7 +46621,7 @@ async function runDogfood(args) {
|
|
|
44214
46621
|
const { selectedWorkspace, bearer } = await commandWorkspaceAndCredential(args, cloud);
|
|
44215
46622
|
const client = new ThinCommandClient(cloud);
|
|
44216
46623
|
const route = stream(args);
|
|
44217
|
-
const taskId = args.optional("task-id") ?? (0,
|
|
46624
|
+
const taskId = args.optional("task-id") ?? (0, import_node_crypto21.randomUUID)();
|
|
44218
46625
|
const ttl = Number(args.optional("ttl-ms") ?? "3600000");
|
|
44219
46626
|
if (!Number.isSafeInteger(ttl) || ttl <= 0 || ttl > 144e5) {
|
|
44220
46627
|
throw new Error("--ttl-ms must be an integer in 1..14400000");
|
|
@@ -44280,7 +46687,7 @@ async function runSeed(args) {
|
|
|
44280
46687
|
if (!tokenOut || !(0, import_node_path21.isAbsolute)(tokenOut)) {
|
|
44281
46688
|
throw new Error("SEED_TOKEN_OUT must be an absolute path");
|
|
44282
46689
|
}
|
|
44283
|
-
const tokenFile = await (0,
|
|
46690
|
+
const tokenFile = await (0, import_promises12.open)(tokenOut, "wx", 384).catch((error) => {
|
|
44284
46691
|
if (error.code === "EEXIST") {
|
|
44285
46692
|
throw new Error("SEED_TOKEN_OUT already exists; refusing to overwrite it");
|
|
44286
46693
|
}
|
|
@@ -44319,7 +46726,7 @@ async function runSeed(args) {
|
|
|
44319
46726
|
tokenWritten = true;
|
|
44320
46727
|
}
|
|
44321
46728
|
await tokenFile.close();
|
|
44322
|
-
if (!tokenWritten) await (0,
|
|
46729
|
+
if (!tokenWritten) await (0, import_promises12.unlink)(tokenOut);
|
|
44323
46730
|
process.stdout.write(`${JSON.stringify({
|
|
44324
46731
|
userId: result.userId,
|
|
44325
46732
|
membershipRole: result.membershipRole,
|
|
@@ -44332,7 +46739,7 @@ async function runSeed(args) {
|
|
|
44332
46739
|
`);
|
|
44333
46740
|
} catch (error) {
|
|
44334
46741
|
await tokenFile.close().catch(() => void 0);
|
|
44335
|
-
if (!tokenWritten) await (0,
|
|
46742
|
+
if (!tokenWritten) await (0, import_promises12.unlink)(tokenOut).catch(() => void 0);
|
|
44336
46743
|
throw error;
|
|
44337
46744
|
}
|
|
44338
46745
|
}
|
|
@@ -44433,6 +46840,10 @@ async function main() {
|
|
|
44433
46840
|
await runWhoami(args);
|
|
44434
46841
|
return;
|
|
44435
46842
|
}
|
|
46843
|
+
if (verb === "resume") {
|
|
46844
|
+
await runResume(args);
|
|
46845
|
+
return;
|
|
46846
|
+
}
|
|
44436
46847
|
if (verb === "feedback") {
|
|
44437
46848
|
await runFeedback(args);
|
|
44438
46849
|
return;
|
|
@@ -44518,6 +46929,7 @@ function markRestartable(error) {
|
|
|
44518
46929
|
return error;
|
|
44519
46930
|
}
|
|
44520
46931
|
function exitCodeFor(error) {
|
|
46932
|
+
if (error instanceof NotifyStdoutClosedError) return EXIT_NOTIFY_ORPHANED;
|
|
44521
46933
|
return error instanceof Error ? restartableExit.get(error) ?? 1 : 1;
|
|
44522
46934
|
}
|
|
44523
46935
|
function safeParagraph(message) {
|
|
@@ -44574,6 +46986,7 @@ ${usage()}
|
|
|
44574
46986
|
// Annotate the CommonJS export names for ESM import in node:
|
|
44575
46987
|
0 && (module.exports = {
|
|
44576
46988
|
EXIT_RESTARTABLE,
|
|
46989
|
+
ListenerUnattendedRefusedError,
|
|
44577
46990
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
44578
46991
|
clampTurnBudgetToCredential,
|
|
44579
46992
|
claudeUserPromptHookSnippet,
|
|
@@ -44581,6 +46994,7 @@ ${usage()}
|
|
|
44581
46994
|
listenerFailureMessage,
|
|
44582
46995
|
listenerHostLimits,
|
|
44583
46996
|
listenerPermissionMode,
|
|
46997
|
+
listenerProviderInstallEvidence,
|
|
44584
46998
|
listenerRouteConfiguration,
|
|
44585
46999
|
listenerStatusJson,
|
|
44586
47000
|
renderListenerStatus,
|