commonswarm 0.1.22 → 0.1.25
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 +1447 -171
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -4381,8 +4381,8 @@ var require_RealtimeChannel = __commonJS({
|
|
|
4381
4381
|
}
|
|
4382
4382
|
/** @internal */
|
|
4383
4383
|
_notThisChannelEvent(event, ref) {
|
|
4384
|
-
const { close, error, leave, join:
|
|
4385
|
-
const events = [close, error, leave,
|
|
4384
|
+
const { close, error, leave, join: join17 } = constants_1.CHANNEL_EVENTS;
|
|
4385
|
+
const events = [close, error, leave, join17];
|
|
4386
4386
|
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
|
4387
4387
|
}
|
|
4388
4388
|
/** @internal */
|
|
@@ -13505,12 +13505,16 @@ __export(cli_exports, {
|
|
|
13505
13505
|
EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
|
|
13506
13506
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
13507
13507
|
clampTurnBudgetToCredential: () => clampTurnBudgetToCredential,
|
|
13508
|
+
claudeUserPromptHookSnippet: () => claudeUserPromptHookSnippet,
|
|
13508
13509
|
describeAudience: () => describeAudience,
|
|
13509
13510
|
listenerFailureMessage: () => listenerFailureMessage,
|
|
13510
13511
|
listenerHostLimits: () => listenerHostLimits,
|
|
13511
13512
|
listenerPermissionMode: () => listenerPermissionMode,
|
|
13513
|
+
listenerRouteConfiguration: () => listenerRouteConfiguration,
|
|
13512
13514
|
listenerStatusJson: () => listenerStatusJson,
|
|
13515
|
+
renderListenerStatus: () => renderListenerStatus,
|
|
13513
13516
|
renderRoster: () => renderRoster,
|
|
13517
|
+
replyRefusalHint: () => replyRefusalHint,
|
|
13514
13518
|
resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
|
|
13515
13519
|
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable,
|
|
13516
13520
|
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer
|
|
@@ -13518,9 +13522,9 @@ __export(cli_exports, {
|
|
|
13518
13522
|
module.exports = __toCommonJS(cli_exports);
|
|
13519
13523
|
var import_node_crypto19 = require("node:crypto");
|
|
13520
13524
|
var import_node_fs7 = require("node:fs");
|
|
13521
|
-
var
|
|
13522
|
-
var
|
|
13523
|
-
var
|
|
13525
|
+
var import_promises11 = require("node:fs/promises");
|
|
13526
|
+
var import_node_path19 = require("node:path");
|
|
13527
|
+
var import_promises12 = require("node:readline/promises");
|
|
13524
13528
|
|
|
13525
13529
|
// src/cloud/auth.ts
|
|
13526
13530
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -22162,15 +22166,8 @@ function createWorkspaceError(status, body) {
|
|
|
22162
22166
|
return new CreateWorkspaceError(
|
|
22163
22167
|
status,
|
|
22164
22168
|
code,
|
|
22165
|
-
/*
|
|
22166
|
-
|
|
22167
|
-
* is unreachable from every surface — `archived_at` exists and nothing writes it — and on a
|
|
22168
|
-
* self-serve deployment the reader IS the operator, so it named a person who does not exist
|
|
22169
|
-
* to perform an action that does not exist.
|
|
22170
|
-
*
|
|
22171
|
-
* It now states the limit, does not offer a remedy that is unimplemented, and names the one
|
|
22172
|
-
* route that does work: someone else's invitation, which is not capped. */
|
|
22173
|
-
`${limit === null ? "You have already created as many workspaces as this account allows." : `You have already created ${limit} workspaces, which is the limit for one account.`} Workspaces cannot be removed yet, so this limit is a ceiling rather than a queue. Workspaces you were invited to do not count against it \u2014 a collaborator can still add you to theirs.`
|
|
22169
|
+
/* Closing one live workspace now frees a slot; name the exact confirmed command. */
|
|
22170
|
+
`${limit === null ? "You have already created as many workspaces as this account allows." : `You have already created ${limit} workspaces, which is the limit for one account.`} Close one with cswarm workspace close <full-id|exact-name> --confirm <same-selector>, then try again. Workspaces you were invited to do not count against it.`
|
|
22174
22171
|
);
|
|
22175
22172
|
}
|
|
22176
22173
|
if (status === 403) {
|
|
@@ -22797,6 +22794,12 @@ var CONTENT_TYPES = /* @__PURE__ */ new Map([
|
|
|
22797
22794
|
[".md", "text/markdown"],
|
|
22798
22795
|
[".txt", "text/plain"],
|
|
22799
22796
|
[".csv", "text/csv"],
|
|
22797
|
+
// .html/.htm: a web/marketing team's deliverables (Fastio feedback 2026-08-19). Every
|
|
22798
|
+
// download is served Content-Disposition: attachment (§5), never rendered inline, so HTML
|
|
22799
|
+
// is no more dangerous than the .svg already permitted — the spec treats all downloads as
|
|
22800
|
+
// untrusted attachments the consumer must not execute.
|
|
22801
|
+
[".html", "text/html"],
|
|
22802
|
+
[".htm", "text/html"],
|
|
22800
22803
|
[".json", "application/json"],
|
|
22801
22804
|
[".yaml", "application/yaml"],
|
|
22802
22805
|
[".yml", "application/yaml"],
|
|
@@ -23273,10 +23276,14 @@ function parseProfile(raw) {
|
|
|
23273
23276
|
}
|
|
23274
23277
|
return value;
|
|
23275
23278
|
}
|
|
23276
|
-
async function withFileLock(stateDirectory2, lockName, work) {
|
|
23279
|
+
async function withFileLock(stateDirectory2, lockName, work, options = {}) {
|
|
23277
23280
|
await secureDirectory(stateDirectory2);
|
|
23278
23281
|
const lockPath = (0, import_node_path.join)(stateDirectory2, `${lockName}.lock`);
|
|
23279
|
-
const
|
|
23282
|
+
const timeoutMs = options.timeoutMs ?? LOCK_TIMEOUT_MS;
|
|
23283
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > LOCK_TIMEOUT_MS) {
|
|
23284
|
+
throw new Error("credential refresh lock timeout is invalid");
|
|
23285
|
+
}
|
|
23286
|
+
const deadline = Date.now() + timeoutMs;
|
|
23280
23287
|
let handle = null;
|
|
23281
23288
|
while (handle === null) {
|
|
23282
23289
|
try {
|
|
@@ -27730,6 +27737,7 @@ var DEFAULT_MEMBERSHIP_REVOKED = {
|
|
|
27730
27737
|
message: "Your previously selected workspace is no longer available to this account. CommonSwarm cleared that saved selection."
|
|
27731
27738
|
};
|
|
27732
27739
|
var PROJECT_NOT_AVAILABLE = "That workspace is not available to this account. Run cswarm workspaces to see workspaces you can select.";
|
|
27740
|
+
var ARCHIVED_PROJECT_NOT_AVAILABLE = "That workspace is closed and cannot be selected. Run cswarm workspaces to see live workspaces you can select.";
|
|
27733
27741
|
function compareText(left, right) {
|
|
27734
27742
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
27735
27743
|
}
|
|
@@ -27767,8 +27775,8 @@ var WorkspaceResolutionError = class extends WorkspaceCliError {
|
|
|
27767
27775
|
};
|
|
27768
27776
|
var WorkspaceUnavailableError = class extends WorkspaceCliError {
|
|
27769
27777
|
code = "project_not_available";
|
|
27770
|
-
constructor() {
|
|
27771
|
-
super(
|
|
27778
|
+
constructor(message = PROJECT_NOT_AVAILABLE) {
|
|
27779
|
+
super(message);
|
|
27772
27780
|
this.name = "WorkspaceUnavailableError";
|
|
27773
27781
|
}
|
|
27774
27782
|
structured() {
|
|
@@ -27872,41 +27880,41 @@ function cloudWorkspaceDirectory(target2, fetcher = fetch) {
|
|
|
27872
27880
|
"workspaces",
|
|
27873
27881
|
{
|
|
27874
27882
|
select: "workspace_id,name,archived_at",
|
|
27883
|
+
archived_at: "is.null",
|
|
27875
27884
|
order: "workspace_id.asc"
|
|
27876
27885
|
},
|
|
27877
27886
|
fetcher
|
|
27878
27887
|
)
|
|
27879
27888
|
]);
|
|
27880
|
-
const
|
|
27889
|
+
const roles = /* @__PURE__ */ new Map();
|
|
27890
|
+
for (const row of membershipRows) {
|
|
27891
|
+
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27892
|
+
roles.set(workspaceId2, checkedRole(row.role));
|
|
27893
|
+
}
|
|
27894
|
+
const result = [];
|
|
27881
27895
|
for (const row of workspaceRows) {
|
|
27882
27896
|
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27883
27897
|
const archivedAt = checkedNullableTimestamp(
|
|
27884
27898
|
row.archived_at,
|
|
27885
27899
|
"archived_at"
|
|
27886
27900
|
);
|
|
27887
|
-
|
|
27901
|
+
if (archivedAt !== null) continue;
|
|
27902
|
+
const role = roles.get(workspaceId2);
|
|
27903
|
+
if (!role) {
|
|
27904
|
+
throw new Error(
|
|
27905
|
+
"workspace read omitted the current user's live membership"
|
|
27906
|
+
);
|
|
27907
|
+
}
|
|
27908
|
+
result.push({
|
|
27909
|
+
workspace_id: workspaceId2,
|
|
27888
27910
|
name: sanitizeDisplayLabel(
|
|
27889
27911
|
checkedString(row.name, "workspace name"),
|
|
27890
27912
|
"Unnamed workspace"
|
|
27891
27913
|
),
|
|
27892
|
-
|
|
27914
|
+
role,
|
|
27915
|
+
archived: false
|
|
27893
27916
|
});
|
|
27894
27917
|
}
|
|
27895
|
-
const result = membershipRows.map((row) => {
|
|
27896
|
-
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27897
|
-
const project = names.get(workspaceId2);
|
|
27898
|
-
if (!project) {
|
|
27899
|
-
throw new Error(
|
|
27900
|
-
"workspace read omitted a workspace for a live membership"
|
|
27901
|
-
);
|
|
27902
|
-
}
|
|
27903
|
-
return {
|
|
27904
|
-
workspace_id: workspaceId2,
|
|
27905
|
-
name: project.name,
|
|
27906
|
-
role: checkedRole(row.role),
|
|
27907
|
-
archived: project.archived
|
|
27908
|
-
};
|
|
27909
|
-
});
|
|
27910
27918
|
return sortWorkspaces(result);
|
|
27911
27919
|
},
|
|
27912
27920
|
async status(session, workspaceId2) {
|
|
@@ -27961,6 +27969,9 @@ function cloudWorkspaceDirectory(target2, fetcher = fetch) {
|
|
|
27961
27969
|
you: userId === session.userId
|
|
27962
27970
|
};
|
|
27963
27971
|
});
|
|
27972
|
+
if (!members.some((member) => member.you)) {
|
|
27973
|
+
throw new WorkspaceUnavailableError();
|
|
27974
|
+
}
|
|
27964
27975
|
const memberNames = new Map(
|
|
27965
27976
|
members.map((member) => [member.user_id, member.name])
|
|
27966
27977
|
);
|
|
@@ -28086,6 +28097,32 @@ async function clearWorkspaceDefault(store2, userId, expectedWorkspaceId) {
|
|
|
28086
28097
|
return true;
|
|
28087
28098
|
});
|
|
28088
28099
|
}
|
|
28100
|
+
async function updateWorkspaceDefaultAfterClose(store2, userId, closedWorkspaceId, workspaces) {
|
|
28101
|
+
return await store2.withLock(async () => {
|
|
28102
|
+
const current = await store2.readProfile();
|
|
28103
|
+
if (current.userId !== userId || current.workspaceId !== closedWorkspaceId) {
|
|
28104
|
+
return {
|
|
28105
|
+
closedWasSelected: false,
|
|
28106
|
+
nextWorkspace: null,
|
|
28107
|
+
selectedWorkspaceId: current.userId === userId ? current.workspaceId : null
|
|
28108
|
+
};
|
|
28109
|
+
}
|
|
28110
|
+
const nextWorkspace = sortWorkspaces(workspaces).find(
|
|
28111
|
+
(workspace) => workspace.workspace_id !== closedWorkspaceId && !workspace.archived
|
|
28112
|
+
) ?? null;
|
|
28113
|
+
await store2.writeProfile({
|
|
28114
|
+
...current,
|
|
28115
|
+
workspaceId: nextWorkspace?.workspace_id ?? null,
|
|
28116
|
+
principalId: null,
|
|
28117
|
+
principalName: null
|
|
28118
|
+
});
|
|
28119
|
+
return {
|
|
28120
|
+
closedWasSelected: true,
|
|
28121
|
+
nextWorkspace,
|
|
28122
|
+
selectedWorkspaceId: nextWorkspace?.workspace_id ?? null
|
|
28123
|
+
};
|
|
28124
|
+
});
|
|
28125
|
+
}
|
|
28089
28126
|
function workspaceOverride(explicit, environmental) {
|
|
28090
28127
|
if (explicit !== void 0) {
|
|
28091
28128
|
if (!UUID_RE6.test(explicit)) {
|
|
@@ -28144,6 +28181,14 @@ async function resolveWorkspace(options) {
|
|
|
28144
28181
|
throw new WorkspaceResolutionError(workspaces);
|
|
28145
28182
|
}
|
|
28146
28183
|
async function selectWorkspace(selector, workspaces, store2, userId) {
|
|
28184
|
+
const selected = resolveWorkspaceSelector(selector, workspaces);
|
|
28185
|
+
if (selected.archived) {
|
|
28186
|
+
throw new WorkspaceUnavailableError(ARCHIVED_PROJECT_NOT_AVAILABLE);
|
|
28187
|
+
}
|
|
28188
|
+
await writeWorkspaceDefault(store2, userId, selected.workspace_id);
|
|
28189
|
+
return selected;
|
|
28190
|
+
}
|
|
28191
|
+
function resolveWorkspaceSelector(selector, workspaces) {
|
|
28147
28192
|
const sorted = sortWorkspaces(workspaces);
|
|
28148
28193
|
let selected;
|
|
28149
28194
|
if (UUID_RE6.test(selector)) {
|
|
@@ -28162,7 +28207,6 @@ async function selectWorkspace(selector, workspaces, store2, userId) {
|
|
|
28162
28207
|
selected = matches[0];
|
|
28163
28208
|
}
|
|
28164
28209
|
if (!selected) throw new WorkspaceUnavailableError();
|
|
28165
|
-
await writeWorkspaceDefault(store2, userId, selected.workspace_id);
|
|
28166
28210
|
return selected;
|
|
28167
28211
|
}
|
|
28168
28212
|
function holderLabel(holder) {
|
|
@@ -28181,13 +28225,8 @@ function relativeExpiry(expiry, now = Date.now()) {
|
|
|
28181
28225
|
const amount = relativeMagnitude(remaining);
|
|
28182
28226
|
return remaining >= 0 ? `expires in ${amount}` : `expired ${amount} ago`;
|
|
28183
28227
|
}
|
|
28184
|
-
var ARCHIVE_NOT_ENFORCED_CODE = "workspace_archive_not_enforced";
|
|
28185
|
-
var ARCHIVE_NOT_ENFORCED_MESSAGE = "Archiving a workspace does not restrict what members or their agents can do in it: an archived workspace stays selectable, and commands against it still succeed while your membership is live. Removing a workspace from this list means ending your membership, which this CLI cannot do \u2014 ask whoever runs the workspace.";
|
|
28186
28228
|
function archiveKnownGaps() {
|
|
28187
|
-
return [
|
|
28188
|
-
code: ARCHIVE_NOT_ENFORCED_CODE,
|
|
28189
|
-
message: ARCHIVE_NOT_ENFORCED_MESSAGE
|
|
28190
|
-
}];
|
|
28229
|
+
return [];
|
|
28191
28230
|
}
|
|
28192
28231
|
function renderWorkspaces(workspaces, currentWorkspaceId) {
|
|
28193
28232
|
if (workspaces.length === 0) {
|
|
@@ -28211,9 +28250,6 @@ function renderWorkspaces(workspaces, currentWorkspaceId) {
|
|
|
28211
28250
|
"No workspace is selected. Run cswarm use <full-id|exact-name>."
|
|
28212
28251
|
);
|
|
28213
28252
|
}
|
|
28214
|
-
if (workspaces.some((workspace) => workspace.archived)) {
|
|
28215
|
-
lines.push(ARCHIVE_NOT_ENFORCED_MESSAGE);
|
|
28216
|
-
}
|
|
28217
28253
|
return lines.join("\n");
|
|
28218
28254
|
}
|
|
28219
28255
|
function renderStatus(options) {
|
|
@@ -32281,7 +32317,8 @@ var STATES = /* @__PURE__ */ new Set([
|
|
|
32281
32317
|
"done",
|
|
32282
32318
|
"expired",
|
|
32283
32319
|
"failed",
|
|
32284
|
-
"observed"
|
|
32320
|
+
"observed",
|
|
32321
|
+
"routed_main"
|
|
32285
32322
|
]);
|
|
32286
32323
|
var RELATIONS = /* @__PURE__ */ new Set(["same_owner", "cross_owner", "unknown"]);
|
|
32287
32324
|
var SIGNAL_KINDS2 = /* @__PURE__ */ new Set(["ask", "note"]);
|
|
@@ -32344,7 +32381,7 @@ function parseListenerEffectRecord(raw, expectedId) {
|
|
|
32344
32381
|
}
|
|
32345
32382
|
if (row.version === 1) {
|
|
32346
32383
|
rejectUnknownKeys(row, V1_EFFECT_KEYS);
|
|
32347
|
-
if ("signalKind" in row || row.state === "observed") {
|
|
32384
|
+
if ("signalKind" in row || row.state === "observed" || row.state === "routed_main") {
|
|
32348
32385
|
throw new Error("stored listener effect is malformed");
|
|
32349
32386
|
}
|
|
32350
32387
|
return upcastV1Ask(row);
|
|
@@ -32387,6 +32424,10 @@ function parseV2Record(row) {
|
|
|
32387
32424
|
if (typeof row.commandId !== "string" || row.commandId !== "" || row.state !== "observed" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
|
|
32388
32425
|
throw new Error("stored listener effect is malformed");
|
|
32389
32426
|
}
|
|
32427
|
+
} else if (row.state === "routed_main") {
|
|
32428
|
+
if (row.commandId !== "" || row.promptAttempts !== 0 || row.postAttempts !== 0 || row.replyBody !== null || row.replyTruncated !== false || row.replySignalId !== null || row.failureCode !== null) {
|
|
32429
|
+
throw new Error("stored listener effect is malformed");
|
|
32430
|
+
}
|
|
32390
32431
|
} else {
|
|
32391
32432
|
if (typeof row.commandId !== "string" || !COMMAND_ID_RE2.test(row.commandId) || row.state === "observed") {
|
|
32392
32433
|
throw new Error("stored listener effect is malformed");
|
|
@@ -32449,6 +32490,14 @@ function newObservedNoteRecord(input) {
|
|
|
32449
32490
|
updatedAt: input.updatedAt
|
|
32450
32491
|
};
|
|
32451
32492
|
}
|
|
32493
|
+
function newRoutedMainAskRecord(input) {
|
|
32494
|
+
const base = newObservedNoteRecord(input);
|
|
32495
|
+
return {
|
|
32496
|
+
...base,
|
|
32497
|
+
signalKind: "ask",
|
|
32498
|
+
state: "routed_main"
|
|
32499
|
+
};
|
|
32500
|
+
}
|
|
32452
32501
|
function rejectWrite() {
|
|
32453
32502
|
throw new Error("listener effect write rejected");
|
|
32454
32503
|
}
|
|
@@ -34178,6 +34227,186 @@ var DeliveryCommandClient = class {
|
|
|
34178
34227
|
}
|
|
34179
34228
|
};
|
|
34180
34229
|
|
|
34230
|
+
// src/listener/main-routing.ts
|
|
34231
|
+
var import_node_path14 = require("node:path");
|
|
34232
|
+
var UUID_RE11 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
34233
|
+
var MAX_QUEUE_BYTES = 1024 * 1024;
|
|
34234
|
+
var QUEUE_FILE = "pending-for-main.json";
|
|
34235
|
+
var QUEUE_LOCK = "pending-for-main";
|
|
34236
|
+
var LISTENER_MAIN_QUEUE_MAX = 200;
|
|
34237
|
+
var LISTENER_DEFER_OVER_MIN = 1;
|
|
34238
|
+
var LISTENER_DEFER_OVER_MAX = 1e4;
|
|
34239
|
+
function decideListenerRoute(route, threshold, bodyLength) {
|
|
34240
|
+
if (!Number.isSafeInteger(bodyLength) || bodyLength < 0) {
|
|
34241
|
+
throw new Error("listener route body length must be a non-negative integer");
|
|
34242
|
+
}
|
|
34243
|
+
if (route === "worker") {
|
|
34244
|
+
if (threshold !== null) {
|
|
34245
|
+
throw new Error("worker route cannot have a split threshold");
|
|
34246
|
+
}
|
|
34247
|
+
return "worker";
|
|
34248
|
+
}
|
|
34249
|
+
if (route === "main") {
|
|
34250
|
+
if (threshold !== null) {
|
|
34251
|
+
throw new Error("main route cannot have a split threshold");
|
|
34252
|
+
}
|
|
34253
|
+
return "main";
|
|
34254
|
+
}
|
|
34255
|
+
if (route !== "split") {
|
|
34256
|
+
throw new Error("listener route mode is invalid");
|
|
34257
|
+
}
|
|
34258
|
+
if (threshold === null || !Number.isSafeInteger(threshold) || threshold < LISTENER_DEFER_OVER_MIN || threshold > LISTENER_DEFER_OVER_MAX) {
|
|
34259
|
+
throw new Error("split route threshold is invalid");
|
|
34260
|
+
}
|
|
34261
|
+
return bodyLength > threshold ? "main" : "worker";
|
|
34262
|
+
}
|
|
34263
|
+
function checkedTimestamp2(value) {
|
|
34264
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
34265
|
+
}
|
|
34266
|
+
function parseEntry(value) {
|
|
34267
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34268
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34269
|
+
}
|
|
34270
|
+
const row = value;
|
|
34271
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
34272
|
+
"signalId",
|
|
34273
|
+
"workspaceId",
|
|
34274
|
+
"principalId",
|
|
34275
|
+
"fromId",
|
|
34276
|
+
"fromKind",
|
|
34277
|
+
"senderName",
|
|
34278
|
+
"body",
|
|
34279
|
+
"createdAt",
|
|
34280
|
+
"queuedAt"
|
|
34281
|
+
]);
|
|
34282
|
+
if (Object.keys(row).some((key2) => !allowed.has(key2))) {
|
|
34283
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34284
|
+
}
|
|
34285
|
+
if (typeof row.signalId !== "string" || !UUID_RE11.test(row.signalId) || typeof row.workspaceId !== "string" || !UUID_RE11.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE11.test(row.principalId) || typeof row.fromId !== "string" || !UUID_RE11.test(row.fromId) || row.fromKind !== "user" && row.fromKind !== "agent" || !(row.senderName === null || typeof row.senderName === "string" && row.senderName.length <= 200) || typeof row.body !== "string" || row.body.length < 1 || row.body.length > 2e3 || !checkedTimestamp2(row.createdAt) || !checkedTimestamp2(row.queuedAt)) {
|
|
34286
|
+
throw new Error("stored pending-for-main entry is malformed");
|
|
34287
|
+
}
|
|
34288
|
+
return {
|
|
34289
|
+
signalId: row.signalId.toLowerCase(),
|
|
34290
|
+
workspaceId: row.workspaceId.toLowerCase(),
|
|
34291
|
+
principalId: row.principalId.toLowerCase(),
|
|
34292
|
+
fromId: row.fromId.toLowerCase(),
|
|
34293
|
+
fromKind: row.fromKind,
|
|
34294
|
+
senderName: row.senderName,
|
|
34295
|
+
body: row.body,
|
|
34296
|
+
createdAt: row.createdAt,
|
|
34297
|
+
queuedAt: row.queuedAt
|
|
34298
|
+
};
|
|
34299
|
+
}
|
|
34300
|
+
function parseFile(raw) {
|
|
34301
|
+
let value;
|
|
34302
|
+
try {
|
|
34303
|
+
value = JSON.parse(raw);
|
|
34304
|
+
} catch {
|
|
34305
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34306
|
+
}
|
|
34307
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34308
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34309
|
+
}
|
|
34310
|
+
const row = value;
|
|
34311
|
+
if (Object.keys(row).some(
|
|
34312
|
+
(key2) => key2 !== "version" && key2 !== "entries" && key2 !== "droppedCount"
|
|
34313
|
+
) || 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)) {
|
|
34314
|
+
throw new Error("stored pending-for-main queue is malformed");
|
|
34315
|
+
}
|
|
34316
|
+
const entries = row.entries.map(parseEntry);
|
|
34317
|
+
if (new Set(entries.map((entry) => entry.signalId)).size !== entries.length) {
|
|
34318
|
+
throw new Error("stored pending-for-main queue repeats a signal");
|
|
34319
|
+
}
|
|
34320
|
+
return { version: 1, entries, droppedCount: row.droppedCount ?? 0 };
|
|
34321
|
+
}
|
|
34322
|
+
var FilePendingMainQueue = class {
|
|
34323
|
+
path;
|
|
34324
|
+
directory;
|
|
34325
|
+
constructor(instanceDirectory) {
|
|
34326
|
+
if (!(0, import_node_path14.isAbsolute)(instanceDirectory)) {
|
|
34327
|
+
throw new Error("pending-for-main directory must be absolute");
|
|
34328
|
+
}
|
|
34329
|
+
this.directory = instanceDirectory;
|
|
34330
|
+
this.path = (0, import_node_path14.join)(instanceDirectory, QUEUE_FILE);
|
|
34331
|
+
}
|
|
34332
|
+
async readUnlocked() {
|
|
34333
|
+
const raw = await readSecureJsonFile(this.path, MAX_QUEUE_BYTES);
|
|
34334
|
+
return raw === null ? { version: 1, entries: [], droppedCount: 0 } : parseFile(raw);
|
|
34335
|
+
}
|
|
34336
|
+
async writeUnlocked(file) {
|
|
34337
|
+
const canonical = parseFile(JSON.stringify(file));
|
|
34338
|
+
await writeSecureJsonFile(this.path, JSON.stringify(canonical));
|
|
34339
|
+
}
|
|
34340
|
+
async read() {
|
|
34341
|
+
return [...(await this.readUnlocked()).entries];
|
|
34342
|
+
}
|
|
34343
|
+
async count() {
|
|
34344
|
+
return (await this.readUnlocked()).entries.length;
|
|
34345
|
+
}
|
|
34346
|
+
async stats() {
|
|
34347
|
+
const file = await this.readUnlocked();
|
|
34348
|
+
return { count: file.entries.length, droppedCount: file.droppedCount };
|
|
34349
|
+
}
|
|
34350
|
+
async enqueue(entry) {
|
|
34351
|
+
const checked = parseEntry(entry);
|
|
34352
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
34353
|
+
const file = await this.readUnlocked();
|
|
34354
|
+
if (file.entries.some((item) => item.signalId === checked.signalId)) {
|
|
34355
|
+
return {
|
|
34356
|
+
count: file.entries.length,
|
|
34357
|
+
added: false,
|
|
34358
|
+
droppedOldest: false,
|
|
34359
|
+
droppedCount: file.droppedCount
|
|
34360
|
+
};
|
|
34361
|
+
}
|
|
34362
|
+
file.entries.push(checked);
|
|
34363
|
+
const droppedOldest = file.entries.length > LISTENER_MAIN_QUEUE_MAX;
|
|
34364
|
+
if (droppedOldest) {
|
|
34365
|
+
file.entries.shift();
|
|
34366
|
+
file.droppedCount += 1;
|
|
34367
|
+
}
|
|
34368
|
+
await this.writeUnlocked(file);
|
|
34369
|
+
return {
|
|
34370
|
+
count: file.entries.length,
|
|
34371
|
+
added: true,
|
|
34372
|
+
droppedOldest,
|
|
34373
|
+
droppedCount: file.droppedCount
|
|
34374
|
+
};
|
|
34375
|
+
});
|
|
34376
|
+
}
|
|
34377
|
+
async remove(signalIds, lockTimeoutMs) {
|
|
34378
|
+
if (signalIds.size === 0) return await this.count();
|
|
34379
|
+
return await withFileLock(this.directory, QUEUE_LOCK, async () => {
|
|
34380
|
+
const file = await this.readUnlocked();
|
|
34381
|
+
const entries = file.entries.filter((entry) => !signalIds.has(entry.signalId));
|
|
34382
|
+
if (entries.length !== file.entries.length) {
|
|
34383
|
+
await this.writeUnlocked({
|
|
34384
|
+
version: 1,
|
|
34385
|
+
entries,
|
|
34386
|
+
droppedCount: file.droppedCount
|
|
34387
|
+
});
|
|
34388
|
+
}
|
|
34389
|
+
return entries.length;
|
|
34390
|
+
}, lockTimeoutMs === void 0 ? {} : { timeoutMs: lockTimeoutMs });
|
|
34391
|
+
}
|
|
34392
|
+
};
|
|
34393
|
+
function pendingMainEntry(signal, principalId, provenance, now) {
|
|
34394
|
+
if (signal.kind !== "ask") {
|
|
34395
|
+
throw new Error("only directed asks can enter the pending-for-main queue");
|
|
34396
|
+
}
|
|
34397
|
+
return parseEntry({
|
|
34398
|
+
signalId: signal.id,
|
|
34399
|
+
workspaceId: signal.workspace_id,
|
|
34400
|
+
principalId,
|
|
34401
|
+
fromId: signal.from,
|
|
34402
|
+
fromKind: signal.from_kind,
|
|
34403
|
+
senderName: provenance.senderName,
|
|
34404
|
+
body: signal.body,
|
|
34405
|
+
createdAt: signal.created_at,
|
|
34406
|
+
queuedAt: new Date(now).toISOString()
|
|
34407
|
+
});
|
|
34408
|
+
}
|
|
34409
|
+
|
|
34181
34410
|
// src/listener/runtime.ts
|
|
34182
34411
|
var LISTENER_PAGE_LIMIT = 100;
|
|
34183
34412
|
var LISTENER_IDLE_POLL_MS = 2e3;
|
|
@@ -34188,7 +34417,7 @@ var LISTENER_REPLY_ONLY_MINIMUM_MS = SIGNAL_REQUEST_TIMEOUT_MS + LISTENER_ACK_ON
|
|
|
34188
34417
|
var LISTENER_PROMPT_START_MINIMUM_MS = SIGNAL_READ_TIMEOUT_MS + ACP_DEFAULT_REQUEST_TIMEOUT_MS + LISTENER_REPLY_ONLY_MINIMUM_MS;
|
|
34189
34418
|
var LISTENER_DELIVERY_RETRY_INITIAL_MS = 500;
|
|
34190
34419
|
var LISTENER_DELIVERY_RETRY_MAX_MS = 3e4;
|
|
34191
|
-
var
|
|
34420
|
+
var UUID_RE12 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
34192
34421
|
var ListenerCapabilityError = class extends Error {
|
|
34193
34422
|
code;
|
|
34194
34423
|
constructor(code, message) {
|
|
@@ -34267,6 +34496,9 @@ function ackForTerminalEffect(record, now) {
|
|
|
34267
34496
|
if (record.state === "observed" && record.signalKind === "note") {
|
|
34268
34497
|
return { outcome: "observed", lastErrorCode: null };
|
|
34269
34498
|
}
|
|
34499
|
+
if (record.state === "routed_main" && record.signalKind === "ask") {
|
|
34500
|
+
return { outcome: "observed", lastErrorCode: null };
|
|
34501
|
+
}
|
|
34270
34502
|
if (record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now()) {
|
|
34271
34503
|
return { outcome: "expired", lastErrorCode: null };
|
|
34272
34504
|
}
|
|
@@ -34286,7 +34518,7 @@ function ackForTerminalEffect(record, now) {
|
|
|
34286
34518
|
return { outcome: "failed_terminal", lastErrorCode: "local_effect_failed" };
|
|
34287
34519
|
}
|
|
34288
34520
|
function isAckableTerminalEffect(record, now) {
|
|
34289
|
-
return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
|
|
34521
|
+
return record.state === "done" && record.signalKind === "ask" && !!record.replySignalId || record.state === "observed" && record.signalKind === "note" || record.state === "routed_main" && record.signalKind === "ask" || record.state === "expired" && record.signalKind === "ask" && Date.parse(record.askUntil) <= now() || record.state === "failed" && record.signalKind === "ask";
|
|
34290
34522
|
}
|
|
34291
34523
|
function effectPhaseBudget(record) {
|
|
34292
34524
|
if (record === null || record.state === "received" || record.state === "prompting") {
|
|
@@ -34449,6 +34681,8 @@ async function runListenerRuntime(options) {
|
|
|
34449
34681
|
const random = options.random ?? Math.random;
|
|
34450
34682
|
const pageLimit = options.pageLimit ?? LISTENER_PAGE_LIMIT;
|
|
34451
34683
|
const pollMs = options.pollMs ?? LISTENER_IDLE_POLL_MS;
|
|
34684
|
+
const routeMode = options.routeMode ?? "worker";
|
|
34685
|
+
const deferOverChars = options.deferOverChars ?? null;
|
|
34452
34686
|
const abort = options.signal;
|
|
34453
34687
|
const hasInstanceId = options.listenerInstanceId !== void 0;
|
|
34454
34688
|
const hasJournal = options.deliveryJournal !== void 0;
|
|
@@ -34458,7 +34692,7 @@ async function runListenerRuntime(options) {
|
|
|
34458
34692
|
new Error("listener instance id and delivery journal must be configured together")
|
|
34459
34693
|
);
|
|
34460
34694
|
}
|
|
34461
|
-
if (hasInstanceId && !
|
|
34695
|
+
if (hasInstanceId && !UUID_RE12.test(options.listenerInstanceId)) {
|
|
34462
34696
|
return await closeBeforeStart(
|
|
34463
34697
|
options.model,
|
|
34464
34698
|
new Error("listener instance id must be a UUID")
|
|
@@ -34470,6 +34704,14 @@ async function runListenerRuntime(options) {
|
|
|
34470
34704
|
new Error("an injected delivery client requires durable delivery configuration")
|
|
34471
34705
|
);
|
|
34472
34706
|
}
|
|
34707
|
+
try {
|
|
34708
|
+
decideListenerRoute(routeMode, deferOverChars, 0);
|
|
34709
|
+
if (routeMode !== "worker" && options.pendingMainQueue === void 0) {
|
|
34710
|
+
throw new Error("main listener routing requires a pending queue");
|
|
34711
|
+
}
|
|
34712
|
+
} catch (error) {
|
|
34713
|
+
return await closeBeforeStart(options.model, asError2(error));
|
|
34714
|
+
}
|
|
34473
34715
|
let initialJournal = null;
|
|
34474
34716
|
if (hasJournal) {
|
|
34475
34717
|
try {
|
|
@@ -34522,6 +34764,52 @@ async function runListenerRuntime(options) {
|
|
|
34522
34764
|
...options.resolveSenderProvenance === void 0 ? {} : { resolveSenderProvenance: options.resolveSenderProvenance },
|
|
34523
34765
|
isCredentialFailure: isCredentialLoss
|
|
34524
34766
|
});
|
|
34767
|
+
const routeAskToMain = async (signal) => {
|
|
34768
|
+
let provenance = {
|
|
34769
|
+
senderName: null,
|
|
34770
|
+
operatorId: null,
|
|
34771
|
+
operatorName: null
|
|
34772
|
+
};
|
|
34773
|
+
if (options.resolveSenderProvenance) {
|
|
34774
|
+
try {
|
|
34775
|
+
provenance = await options.resolveSenderProvenance(signal, {
|
|
34776
|
+
...abort ? { signal: abort } : {},
|
|
34777
|
+
deadlineMs: now() + SIGNAL_READ_TIMEOUT_MS
|
|
34778
|
+
});
|
|
34779
|
+
} catch {
|
|
34780
|
+
}
|
|
34781
|
+
}
|
|
34782
|
+
const queued = await options.pendingMainQueue.enqueue(
|
|
34783
|
+
pendingMainEntry(signal, options.principalId, provenance, now())
|
|
34784
|
+
);
|
|
34785
|
+
options.onEvent?.({
|
|
34786
|
+
type: "main_queue",
|
|
34787
|
+
signalId: signal.id,
|
|
34788
|
+
pendingCount: queued.count,
|
|
34789
|
+
droppedOldest: queued.droppedOldest,
|
|
34790
|
+
droppedCount: queued.droppedCount,
|
|
34791
|
+
ts: eventTime(now)
|
|
34792
|
+
});
|
|
34793
|
+
const existing = await options.store.read(signal.id);
|
|
34794
|
+
if (existing !== null) {
|
|
34795
|
+
if (!sameEffectSignal(existing, signal) || existing.state !== "routed_main") {
|
|
34796
|
+
throw new Error("stored listener effect does not match the main-routed ask");
|
|
34797
|
+
}
|
|
34798
|
+
return existing;
|
|
34799
|
+
}
|
|
34800
|
+
await options.store.write(newRoutedMainAskRecord({
|
|
34801
|
+
signalId: signal.id,
|
|
34802
|
+
body: signal.body,
|
|
34803
|
+
until: signal.until,
|
|
34804
|
+
senderOwnerRelation: signal.sender_owner_relation ?? "unknown",
|
|
34805
|
+
updatedAt: eventTime(now)
|
|
34806
|
+
}));
|
|
34807
|
+
const persisted = await options.store.read(signal.id);
|
|
34808
|
+
if (persisted === null || !sameEffectSignal(persisted, signal) || persisted.state !== "routed_main") {
|
|
34809
|
+
throw new Error("main-routed listener effect could not be verified");
|
|
34810
|
+
}
|
|
34811
|
+
return persisted;
|
|
34812
|
+
};
|
|
34525
34813
|
let malformedWarnings = 0;
|
|
34526
34814
|
const readPage = options.readPage ?? (async (input) => await readAgentSignalPage(
|
|
34527
34815
|
options.target,
|
|
@@ -34687,11 +34975,13 @@ async function runListenerRuntime(options) {
|
|
|
34687
34975
|
break;
|
|
34688
34976
|
}
|
|
34689
34977
|
if (!ready) {
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34978
|
+
if (routeMode !== "main") {
|
|
34979
|
+
try {
|
|
34980
|
+
await options.model.start();
|
|
34981
|
+
} catch (error) {
|
|
34982
|
+
stop = { reason: "fatal", error: asError2(error) };
|
|
34983
|
+
break;
|
|
34984
|
+
}
|
|
34695
34985
|
}
|
|
34696
34986
|
ready = true;
|
|
34697
34987
|
options.onEvent?.({
|
|
@@ -34993,56 +35283,81 @@ async function runListenerRuntime(options) {
|
|
|
34993
35283
|
ts: eventTime(now)
|
|
34994
35284
|
});
|
|
34995
35285
|
} else if (signal.kind === "ask") {
|
|
34996
|
-
|
|
34997
|
-
|
|
34998
|
-
|
|
34999
|
-
|
|
35000
|
-
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35006
|
-
|
|
35007
|
-
|
|
35008
|
-
|
|
35009
|
-
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
stop = { reason: "cancelled" };
|
|
35013
|
-
break;
|
|
35014
|
-
}
|
|
35015
|
-
if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
|
|
35016
|
-
await journal.clearActive(eventTime(now));
|
|
35017
|
-
after = null;
|
|
35018
|
-
}
|
|
35019
|
-
break;
|
|
35020
|
-
}
|
|
35021
|
-
const processed = await engine.process(signal);
|
|
35022
|
-
const effect = "record" in processed ? processed.record : null;
|
|
35286
|
+
const decision = decideListenerRoute(
|
|
35287
|
+
routeMode,
|
|
35288
|
+
deferOverChars,
|
|
35289
|
+
signal.body.length
|
|
35290
|
+
);
|
|
35291
|
+
options.onEvent?.({
|
|
35292
|
+
type: "routing_decision",
|
|
35293
|
+
signalId: signal.id,
|
|
35294
|
+
routeMode,
|
|
35295
|
+
decision,
|
|
35296
|
+
threshold: deferOverChars,
|
|
35297
|
+
bodyLength: signal.body.length,
|
|
35298
|
+
ts: eventTime(now)
|
|
35299
|
+
});
|
|
35300
|
+
if (decision === "main") {
|
|
35301
|
+
terminal = await routeAskToMain(signal);
|
|
35023
35302
|
options.onEvent?.({
|
|
35024
35303
|
type: "effect",
|
|
35025
35304
|
signalId: signal.id,
|
|
35026
|
-
status:
|
|
35027
|
-
failureCode:
|
|
35305
|
+
status: "routed_main",
|
|
35306
|
+
failureCode: null,
|
|
35028
35307
|
ts: eventTime(now)
|
|
35029
35308
|
});
|
|
35030
|
-
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
35038
|
-
)
|
|
35039
|
-
|
|
35040
|
-
|
|
35309
|
+
} else {
|
|
35310
|
+
let processAttempt = 0;
|
|
35311
|
+
while (terminal === null) {
|
|
35312
|
+
const before = await options.store.read(signal.id);
|
|
35313
|
+
if (before !== null && !sameEffectSignal(before, signal)) {
|
|
35314
|
+
throw new Error("stored listener effect does not match the authoritative delivery");
|
|
35315
|
+
}
|
|
35316
|
+
const requiredBudget = effectPhaseBudget(before);
|
|
35317
|
+
if (leasedUntilMs <= now() + requiredBudget) {
|
|
35318
|
+
await sleep2(
|
|
35319
|
+
Math.max(
|
|
35320
|
+
0,
|
|
35321
|
+
leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS - now()
|
|
35322
|
+
),
|
|
35323
|
+
abort
|
|
35324
|
+
);
|
|
35325
|
+
if (abort?.aborted) {
|
|
35326
|
+
stop = { reason: "cancelled" };
|
|
35327
|
+
break;
|
|
35328
|
+
}
|
|
35329
|
+
if (now() >= leasedUntilMs + LISTENER_DELIVERY_SAFETY_MARGIN_MS) {
|
|
35330
|
+
await journal.clearActive(eventTime(now));
|
|
35331
|
+
after = null;
|
|
35332
|
+
}
|
|
35041
35333
|
break;
|
|
35042
35334
|
}
|
|
35043
|
-
|
|
35335
|
+
const processed = await engine.process(signal);
|
|
35336
|
+
const effect = "record" in processed ? processed.record : null;
|
|
35337
|
+
options.onEvent?.({
|
|
35338
|
+
type: "effect",
|
|
35339
|
+
signalId: signal.id,
|
|
35340
|
+
status: processed.status,
|
|
35341
|
+
failureCode: effect?.failureCode ?? null,
|
|
35342
|
+
ts: eventTime(now)
|
|
35343
|
+
});
|
|
35344
|
+
if (processed.status === "ignored") {
|
|
35345
|
+
throw new Error("claimed delivery was ignored by the listener engine");
|
|
35346
|
+
}
|
|
35347
|
+
if (processed.status === "retry_pending") {
|
|
35348
|
+
processAttempt += 1;
|
|
35349
|
+
await sleep2(
|
|
35350
|
+
deliveryRetryDelay(processAttempt, null, random),
|
|
35351
|
+
abort
|
|
35352
|
+
);
|
|
35353
|
+
if (abort?.aborted) {
|
|
35354
|
+
stop = { reason: "cancelled" };
|
|
35355
|
+
break;
|
|
35356
|
+
}
|
|
35357
|
+
continue;
|
|
35358
|
+
}
|
|
35359
|
+
terminal = processed.record;
|
|
35044
35360
|
}
|
|
35045
|
-
terminal = processed.record;
|
|
35046
35361
|
}
|
|
35047
35362
|
} else {
|
|
35048
35363
|
throw new Error("claimed delivery has an unsupported signal kind");
|
|
@@ -35115,6 +35430,31 @@ async function runListenerRuntime(options) {
|
|
|
35115
35430
|
let result;
|
|
35116
35431
|
try {
|
|
35117
35432
|
await readOrReplaceUnreadableEffect(options.store, signal, now);
|
|
35433
|
+
const decision = decideListenerRoute(
|
|
35434
|
+
routeMode,
|
|
35435
|
+
deferOverChars,
|
|
35436
|
+
signal.body.length
|
|
35437
|
+
);
|
|
35438
|
+
options.onEvent?.({
|
|
35439
|
+
type: "routing_decision",
|
|
35440
|
+
signalId: signal.id,
|
|
35441
|
+
routeMode,
|
|
35442
|
+
decision,
|
|
35443
|
+
threshold: deferOverChars,
|
|
35444
|
+
bodyLength: signal.body.length,
|
|
35445
|
+
ts: eventTime(now)
|
|
35446
|
+
});
|
|
35447
|
+
if (decision === "main") {
|
|
35448
|
+
const record2 = await routeAskToMain(signal);
|
|
35449
|
+
options.onEvent?.({
|
|
35450
|
+
type: "effect",
|
|
35451
|
+
signalId: signal.id,
|
|
35452
|
+
status: "routed_main",
|
|
35453
|
+
failureCode: null,
|
|
35454
|
+
ts: eventTime(now)
|
|
35455
|
+
});
|
|
35456
|
+
continue;
|
|
35457
|
+
}
|
|
35118
35458
|
result = await engine.process(signal);
|
|
35119
35459
|
} catch (error) {
|
|
35120
35460
|
if (abort?.aborted) {
|
|
@@ -35174,8 +35514,8 @@ async function runListenerRuntime(options) {
|
|
|
35174
35514
|
// src/listener/control.ts
|
|
35175
35515
|
var import_node_net = require("node:net");
|
|
35176
35516
|
var import_promises9 = require("node:fs/promises");
|
|
35177
|
-
var
|
|
35178
|
-
var
|
|
35517
|
+
var import_node_path15 = require("node:path");
|
|
35518
|
+
var UUID_RE13 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35179
35519
|
var MAX_STATUS_BYTES = 16 * 1024;
|
|
35180
35520
|
var MAX_CONTROL_BYTES = 8 * 1024;
|
|
35181
35521
|
var CONTROL_TIMEOUT_MS = 2e3;
|
|
@@ -35189,19 +35529,19 @@ var ListenerAlreadyRunningError = class extends Error {
|
|
|
35189
35529
|
};
|
|
35190
35530
|
function listenerPaths(options) {
|
|
35191
35531
|
const root = options.stateDirectory ?? defaultListenerStateDirectory();
|
|
35192
|
-
if (!(0,
|
|
35532
|
+
if (!(0, import_node_path15.isAbsolute)(root)) {
|
|
35193
35533
|
throw new Error("listener state directory must be absolute");
|
|
35194
35534
|
}
|
|
35195
35535
|
const key2 = listenerInstanceKey(options);
|
|
35196
|
-
const instanceDirectory = (0,
|
|
35536
|
+
const instanceDirectory = (0, import_node_path15.join)(root, key2);
|
|
35197
35537
|
const uid2 = typeof process.getuid === "function" ? process.getuid() : process.pid;
|
|
35198
|
-
const controlDirectory = process.platform === "win32" ? "" : (0,
|
|
35199
|
-
const socketPath = process.platform === "win32" ? `\\\\.\\pipe\\cswarm-${key2}` : (0,
|
|
35538
|
+
const controlDirectory = process.platform === "win32" ? "" : (0, import_node_path15.join)("/tmp", `cswarm-control-${uid2}`);
|
|
35539
|
+
const socketPath = process.platform === "win32" ? `\\\\.\\pipe\\cswarm-${key2}` : (0, import_node_path15.join)(controlDirectory, `${key2.slice(0, 32)}.sock`);
|
|
35200
35540
|
return {
|
|
35201
35541
|
key: key2,
|
|
35202
35542
|
instanceDirectory,
|
|
35203
|
-
statusPath: (0,
|
|
35204
|
-
logPath: (0,
|
|
35543
|
+
statusPath: (0, import_node_path15.join)(instanceDirectory, "status.json"),
|
|
35544
|
+
logPath: (0, import_node_path15.join)(instanceDirectory, "events.ndjson"),
|
|
35205
35545
|
socketPath
|
|
35206
35546
|
};
|
|
35207
35547
|
}
|
|
@@ -35229,7 +35569,11 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
35229
35569
|
"lastTerminalDeliveryFailureCount",
|
|
35230
35570
|
"lastTerminalDeliveryFailureAt",
|
|
35231
35571
|
"lastClaimAt",
|
|
35232
|
-
"lastAckAt"
|
|
35572
|
+
"lastAckAt",
|
|
35573
|
+
"routeMode",
|
|
35574
|
+
"deferOverChars",
|
|
35575
|
+
"pendingForMainCount",
|
|
35576
|
+
"droppedForMainCount"
|
|
35233
35577
|
]);
|
|
35234
35578
|
var STATUS_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
35235
35579
|
"leaseId",
|
|
@@ -35274,12 +35618,17 @@ function parseStatus(raw) {
|
|
|
35274
35618
|
throw new Error("stored listener status is malformed");
|
|
35275
35619
|
}
|
|
35276
35620
|
}
|
|
35277
|
-
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" &&
|
|
35621
|
+
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE13.test(candidate);
|
|
35278
35622
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
35279
35623
|
const nullableTimestamp = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
35280
|
-
if (row.version !== 1 || typeof row.instanceId !== "string" || !
|
|
35624
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE13.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE13.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE13.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid2(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !/swm_(?:agt|inv|cap)_/i.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path15.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp(row.lastAckAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0)) {
|
|
35281
35625
|
throw new Error("stored listener status is malformed");
|
|
35282
35626
|
}
|
|
35627
|
+
const routeMode = row.routeMode ?? "worker";
|
|
35628
|
+
const deferOverChars = row.deferOverChars ?? null;
|
|
35629
|
+
if (routeMode === "split" && deferOverChars === null || routeMode !== "split" && deferOverChars !== null) {
|
|
35630
|
+
throw new Error("stored listener status routing fields are malformed");
|
|
35631
|
+
}
|
|
35283
35632
|
return {
|
|
35284
35633
|
...row,
|
|
35285
35634
|
deliveryMode: row.deliveryMode ?? null,
|
|
@@ -35288,7 +35637,11 @@ function parseStatus(raw) {
|
|
|
35288
35637
|
lastTerminalDeliveryFailureAt: row.lastTerminalDeliveryFailureAt ?? null,
|
|
35289
35638
|
lastClaimAt: row.lastClaimAt ?? null,
|
|
35290
35639
|
lastAckAt: row.lastAckAt ?? null,
|
|
35291
|
-
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null
|
|
35640
|
+
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null,
|
|
35641
|
+
routeMode,
|
|
35642
|
+
deferOverChars,
|
|
35643
|
+
pendingForMainCount: row.pendingForMainCount ?? 0,
|
|
35644
|
+
droppedForMainCount: row.droppedForMainCount ?? 0
|
|
35292
35645
|
};
|
|
35293
35646
|
}
|
|
35294
35647
|
async function writeListenerStatus(paths, status) {
|
|
@@ -35330,7 +35683,13 @@ async function appendListenerEvent(paths, event) {
|
|
|
35330
35683
|
// bounded by the supervisor, and the prompt-turn budget behind a timeout.
|
|
35331
35684
|
// Local log only — this file never feeds a server payload.
|
|
35332
35685
|
"worker_stderr_tail",
|
|
35333
|
-
"turn_budget_ms"
|
|
35686
|
+
"turn_budget_ms",
|
|
35687
|
+
"route_mode",
|
|
35688
|
+
"route_decision",
|
|
35689
|
+
"defer_over_chars",
|
|
35690
|
+
"body_length",
|
|
35691
|
+
"pending_main_count",
|
|
35692
|
+
"dropped_count"
|
|
35334
35693
|
]);
|
|
35335
35694
|
const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
|
|
35336
35695
|
const deliveryOutcomes = /* @__PURE__ */ new Set([
|
|
@@ -35339,6 +35698,8 @@ async function appendListenerEvent(paths, event) {
|
|
|
35339
35698
|
"expired",
|
|
35340
35699
|
"failed_terminal"
|
|
35341
35700
|
]);
|
|
35701
|
+
const routeModes = /* @__PURE__ */ new Set(["worker", "main", "split"]);
|
|
35702
|
+
const routeDecisions = /* @__PURE__ */ new Set(["worker", "main"]);
|
|
35342
35703
|
for (const [key2, value] of Object.entries(event)) {
|
|
35343
35704
|
if (!allowed.has(key2)) {
|
|
35344
35705
|
throw new Error(`listener event field is not allowed: ${key2}`);
|
|
@@ -35352,6 +35713,18 @@ async function appendListenerEvent(paths, event) {
|
|
|
35352
35713
|
if (key2 === "outcome" && !(value === null || typeof value === "string" && deliveryOutcomes.has(value))) {
|
|
35353
35714
|
throw new Error("listener event outcome is not allowed");
|
|
35354
35715
|
}
|
|
35716
|
+
if (key2 === "route_mode" && !(typeof value === "string" && routeModes.has(value))) {
|
|
35717
|
+
throw new Error("listener event route mode is not allowed");
|
|
35718
|
+
}
|
|
35719
|
+
if (key2 === "route_decision" && !(typeof value === "string" && routeDecisions.has(value))) {
|
|
35720
|
+
throw new Error("listener event route decision is not allowed");
|
|
35721
|
+
}
|
|
35722
|
+
if (key2 === "defer_over_chars" && !(value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e4)) {
|
|
35723
|
+
throw new Error("listener event split threshold is not allowed");
|
|
35724
|
+
}
|
|
35725
|
+
if ((key2 === "body_length" || key2 === "pending_main_count" || key2 === "dropped_count") && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
|
|
35726
|
+
throw new Error("listener event main-route count is not allowed");
|
|
35727
|
+
}
|
|
35355
35728
|
if (key2 === "worker_stderr_tail" && !(typeof value === "string" && value.length > 0 && value.length <= 2048)) {
|
|
35356
35729
|
throw new Error("listener event stderr tail is not allowed");
|
|
35357
35730
|
}
|
|
@@ -35419,7 +35792,7 @@ function writeResponse(socket, response) {
|
|
|
35419
35792
|
}
|
|
35420
35793
|
async function startupLock(paths) {
|
|
35421
35794
|
await ensureSecureStateDirectory(paths.instanceDirectory);
|
|
35422
|
-
const lockPath = (0,
|
|
35795
|
+
const lockPath = (0, import_node_path15.join)(paths.instanceDirectory, "starting.lock");
|
|
35423
35796
|
const deadline = Date.now() + START_LOCK_WAIT_MS;
|
|
35424
35797
|
while (Date.now() < deadline) {
|
|
35425
35798
|
let handle;
|
|
@@ -35460,7 +35833,7 @@ async function startupLock(paths) {
|
|
|
35460
35833
|
async function prepareSocket(paths) {
|
|
35461
35834
|
if (process.platform !== "win32") {
|
|
35462
35835
|
const uid2 = typeof process.getuid === "function" ? process.getuid() : process.pid;
|
|
35463
|
-
const directory = (0,
|
|
35836
|
+
const directory = (0, import_node_path15.join)("/tmp", `cswarm-control-${uid2}`);
|
|
35464
35837
|
await ensureSecureStateDirectory(directory);
|
|
35465
35838
|
}
|
|
35466
35839
|
try {
|
|
@@ -35595,7 +35968,7 @@ async function queryListenerControl(paths, command2, timeoutMs = CONTROL_TIMEOUT
|
|
|
35595
35968
|
|
|
35596
35969
|
// src/listener/supervisor.ts
|
|
35597
35970
|
var import_node_crypto18 = require("node:crypto");
|
|
35598
|
-
var
|
|
35971
|
+
var UUID_RE14 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
35599
35972
|
var LISTENER_RESTART_MAX_ATTEMPTS = 5;
|
|
35600
35973
|
var LISTENER_RESTART_INITIAL_MS = 1e3;
|
|
35601
35974
|
var LISTENER_RESTART_MAX_MS = 6e4;
|
|
@@ -35683,6 +36056,10 @@ async function runListenerSupervisor(options) {
|
|
|
35683
36056
|
lastTerminalDeliveryFailureAt: null,
|
|
35684
36057
|
lastClaimAt: null,
|
|
35685
36058
|
lastAckAt: null,
|
|
36059
|
+
routeMode: options.routeMode ?? "worker",
|
|
36060
|
+
deferOverChars: options.deferOverChars ?? null,
|
|
36061
|
+
pendingForMainCount: 0,
|
|
36062
|
+
droppedForMainCount: 0,
|
|
35686
36063
|
logPath: options.paths.logPath
|
|
35687
36064
|
};
|
|
35688
36065
|
let writes = Promise.resolve();
|
|
@@ -35718,7 +36095,7 @@ async function runListenerSupervisor(options) {
|
|
|
35718
36095
|
// before the socket can answer, before any status/event persistence.
|
|
35719
36096
|
initialize: prepare ? async () => {
|
|
35720
36097
|
const selected = await prepare(proposedInstanceId);
|
|
35721
|
-
if (!selected || typeof selected !== "object" || typeof selected.instanceId !== "string" || !
|
|
36098
|
+
if (!selected || typeof selected !== "object" || typeof selected.instanceId !== "string" || !UUID_RE14.test(selected.instanceId)) {
|
|
35722
36099
|
throw new Error("listener prepare returned an invalid instance id");
|
|
35723
36100
|
}
|
|
35724
36101
|
status = { ...status, instanceId: selected.instanceId };
|
|
@@ -35868,6 +36245,36 @@ async function runListenerSupervisor(options) {
|
|
|
35868
36245
|
});
|
|
35869
36246
|
return;
|
|
35870
36247
|
}
|
|
36248
|
+
if (event.type === "routing_decision") {
|
|
36249
|
+
log({
|
|
36250
|
+
ts: event.ts,
|
|
36251
|
+
event: "listener_routing_decision",
|
|
36252
|
+
signal_id: event.signalId,
|
|
36253
|
+
route_mode: event.routeMode,
|
|
36254
|
+
route_decision: event.decision,
|
|
36255
|
+
defer_over_chars: event.threshold,
|
|
36256
|
+
body_length: event.bodyLength
|
|
36257
|
+
});
|
|
36258
|
+
return;
|
|
36259
|
+
}
|
|
36260
|
+
if (event.type === "main_queue") {
|
|
36261
|
+
status = {
|
|
36262
|
+
...status,
|
|
36263
|
+
pendingForMainCount: event.pendingCount,
|
|
36264
|
+
droppedForMainCount: event.droppedCount,
|
|
36265
|
+
lastSignalId: event.signalId,
|
|
36266
|
+
updatedAt: event.ts
|
|
36267
|
+
};
|
|
36268
|
+
persist();
|
|
36269
|
+
log({
|
|
36270
|
+
ts: event.ts,
|
|
36271
|
+
event: event.droppedOldest ? "listener_main_queue_oldest_dropped" : "listener_main_queue",
|
|
36272
|
+
signal_id: event.signalId,
|
|
36273
|
+
pending_main_count: event.pendingCount,
|
|
36274
|
+
dropped_count: event.droppedOldest ? 1 : 0
|
|
36275
|
+
});
|
|
36276
|
+
return;
|
|
36277
|
+
}
|
|
35871
36278
|
const unknown = event;
|
|
35872
36279
|
log({
|
|
35873
36280
|
ts: typeof unknown.ts === "string" && Number.isFinite(Date.parse(unknown.ts)) ? unknown.ts : iso2(now),
|
|
@@ -36039,9 +36446,9 @@ async function waitForListenerReady(paths, options = {}) {
|
|
|
36039
36446
|
}
|
|
36040
36447
|
|
|
36041
36448
|
// src/listener/delivery-journal.ts
|
|
36042
|
-
var
|
|
36449
|
+
var import_node_path16 = require("node:path");
|
|
36043
36450
|
var import_node_util2 = require("node:util");
|
|
36044
|
-
var
|
|
36451
|
+
var UUID_RE15 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
36045
36452
|
var COMMAND_ID_RE3 = /^[A-Za-z0-9_-]{8,72}$/;
|
|
36046
36453
|
var SIGNAL_FINGERPRINT_RE = /^[0-9a-f]{64}$/;
|
|
36047
36454
|
var MAX_JOURNAL_BYTES = 8192;
|
|
@@ -36135,7 +36542,7 @@ var ALLOWED_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
36135
36542
|
"credential_unavailable"
|
|
36136
36543
|
]);
|
|
36137
36544
|
function claimCommandId(listenerInstanceId, claimOrdinal) {
|
|
36138
|
-
if (!
|
|
36545
|
+
if (!UUID_RE15.test(listenerInstanceId)) {
|
|
36139
36546
|
throw new Error("stored delivery journal is malformed");
|
|
36140
36547
|
}
|
|
36141
36548
|
if (!Number.isSafeInteger(claimOrdinal) || claimOrdinal < 0) {
|
|
@@ -36150,7 +36557,7 @@ function claimCommandId(listenerInstanceId, claimOrdinal) {
|
|
|
36150
36557
|
return id;
|
|
36151
36558
|
}
|
|
36152
36559
|
function ackCommandId(leaseId) {
|
|
36153
|
-
if (!
|
|
36560
|
+
if (!UUID_RE15.test(leaseId)) {
|
|
36154
36561
|
throw new Error("stored delivery journal is malformed");
|
|
36155
36562
|
}
|
|
36156
36563
|
const cleanLease = leaseId.toLowerCase().replace(/-/g, "");
|
|
@@ -36233,19 +36640,19 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
36233
36640
|
if (row.version !== 1) {
|
|
36234
36641
|
throw new Error("stored delivery journal is malformed");
|
|
36235
36642
|
}
|
|
36236
|
-
if (typeof row.workspaceId !== "string" || !
|
|
36643
|
+
if (typeof row.workspaceId !== "string" || !UUID_RE15.test(row.workspaceId) || row.workspaceId !== row.workspaceId.toLowerCase()) {
|
|
36237
36644
|
throw new Error("stored delivery journal is malformed");
|
|
36238
36645
|
}
|
|
36239
36646
|
if (expectedWorkspaceId && row.workspaceId !== expectedWorkspaceId.toLowerCase()) {
|
|
36240
36647
|
throw new Error("stored delivery journal is malformed");
|
|
36241
36648
|
}
|
|
36242
|
-
if (typeof row.principalId !== "string" || !
|
|
36649
|
+
if (typeof row.principalId !== "string" || !UUID_RE15.test(row.principalId) || row.principalId !== row.principalId.toLowerCase()) {
|
|
36243
36650
|
throw new Error("stored delivery journal is malformed");
|
|
36244
36651
|
}
|
|
36245
36652
|
if (expectedPrincipalId && row.principalId !== expectedPrincipalId.toLowerCase()) {
|
|
36246
36653
|
throw new Error("stored delivery journal is malformed");
|
|
36247
36654
|
}
|
|
36248
|
-
if (typeof row.listenerInstanceId !== "string" || !
|
|
36655
|
+
if (typeof row.listenerInstanceId !== "string" || !UUID_RE15.test(row.listenerInstanceId) || row.listenerInstanceId !== row.listenerInstanceId.toLowerCase()) {
|
|
36249
36656
|
throw new Error("stored delivery journal is malformed");
|
|
36250
36657
|
}
|
|
36251
36658
|
if (!Number.isSafeInteger(row.nextClaimOrdinal) || row.nextClaimOrdinal < 0) {
|
|
@@ -36309,10 +36716,10 @@ function parseJournalRecord(raw, expectedWorkspaceId, expectedPrincipalId) {
|
|
|
36309
36716
|
if (active.claimLastAttemptAt === null) {
|
|
36310
36717
|
throw new Error("stored delivery journal is malformed");
|
|
36311
36718
|
}
|
|
36312
|
-
if (typeof active.signalId !== "string" || !
|
|
36719
|
+
if (typeof active.signalId !== "string" || !UUID_RE15.test(active.signalId) || active.signalId !== active.signalId.toLowerCase()) {
|
|
36313
36720
|
throw new Error("stored delivery journal is malformed");
|
|
36314
36721
|
}
|
|
36315
|
-
if (typeof active.leaseId !== "string" || !
|
|
36722
|
+
if (typeof active.leaseId !== "string" || !UUID_RE15.test(active.leaseId) || active.leaseId !== active.leaseId.toLowerCase()) {
|
|
36316
36723
|
throw new Error("stored delivery journal is malformed");
|
|
36317
36724
|
}
|
|
36318
36725
|
if (!isValidIsoTimestamp(active.leasedUntil) || Date.parse(active.leasedUntil) <= Date.parse(active.claimCreatedAt)) {
|
|
@@ -36384,7 +36791,7 @@ var FileListenerDeliveryJournal = class {
|
|
|
36384
36791
|
["profileId", "workspaceId", "principalId"],
|
|
36385
36792
|
"delivery journal configuration rejected"
|
|
36386
36793
|
);
|
|
36387
|
-
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !
|
|
36794
|
+
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !UUID_RE15.test(options.workspaceId) || typeof options.principalId !== "string" || !UUID_RE15.test(options.principalId)) {
|
|
36388
36795
|
throw new Error("delivery journal configuration rejected");
|
|
36389
36796
|
}
|
|
36390
36797
|
if (options.stateDirectory !== void 0) {
|
|
@@ -36399,11 +36806,11 @@ var FileListenerDeliveryJournal = class {
|
|
|
36399
36806
|
stateDirectory: options.stateDirectory
|
|
36400
36807
|
});
|
|
36401
36808
|
const root = this.options.stateDirectory ?? defaultListenerStateDirectory();
|
|
36402
|
-
if (!(0,
|
|
36809
|
+
if (!(0, import_node_path16.isAbsolute)(root)) {
|
|
36403
36810
|
throw new Error("delivery journal configuration rejected");
|
|
36404
36811
|
}
|
|
36405
|
-
this.instanceDirectory = (0,
|
|
36406
|
-
this.journalPath = (0,
|
|
36812
|
+
this.instanceDirectory = (0, import_node_path16.join)(root, listenerInstanceKey(this.options));
|
|
36813
|
+
this.journalPath = (0, import_node_path16.join)(this.instanceDirectory, "delivery-journal.json");
|
|
36407
36814
|
}
|
|
36408
36815
|
async readRecordUnlocked() {
|
|
36409
36816
|
let raw;
|
|
@@ -36505,7 +36912,7 @@ var FileListenerDeliveryJournal = class {
|
|
|
36505
36912
|
["signalId", "leaseId", "leasedUntil"],
|
|
36506
36913
|
"delivery journal mutation rejected"
|
|
36507
36914
|
);
|
|
36508
|
-
if (typeof input.signalId !== "string" || !
|
|
36915
|
+
if (typeof input.signalId !== "string" || !UUID_RE15.test(input.signalId) || typeof input.leaseId !== "string" || !UUID_RE15.test(input.leaseId) || !isValidIsoTimestamp(input.leasedUntil) || input.signalFingerprint !== void 0 && (typeof input.signalFingerprint !== "string" || !SIGNAL_FINGERPRINT_RE.test(input.signalFingerprint))) {
|
|
36509
36916
|
throw new Error("delivery journal mutation rejected");
|
|
36510
36917
|
}
|
|
36511
36918
|
const canonicalSignalId = input.signalId.toLowerCase();
|
|
@@ -36637,7 +37044,7 @@ async function openListenerDeliveryJournal(options) {
|
|
|
36637
37044
|
["profileId", "workspaceId", "principalId", "proposedListenerInstanceId"],
|
|
36638
37045
|
"delivery journal configuration rejected"
|
|
36639
37046
|
);
|
|
36640
|
-
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !
|
|
37047
|
+
if (typeof options.profileId !== "string" || !options.profileId || options.profileId.includes("\0") || typeof options.workspaceId !== "string" || !UUID_RE15.test(options.workspaceId) || typeof options.principalId !== "string" || !UUID_RE15.test(options.principalId) || typeof options.proposedListenerInstanceId !== "string" || !UUID_RE15.test(options.proposedListenerInstanceId)) {
|
|
36641
37048
|
throw new Error("delivery journal configuration rejected");
|
|
36642
37049
|
}
|
|
36643
37050
|
if (options.stateDirectory !== void 0) {
|
|
@@ -36732,9 +37139,9 @@ async function openListenerDeliveryJournal(options) {
|
|
|
36732
37139
|
|
|
36733
37140
|
// src/listener/detach.ts
|
|
36734
37141
|
var import_node_child_process7 = require("node:child_process");
|
|
36735
|
-
var
|
|
37142
|
+
var import_node_path17 = require("node:path");
|
|
36736
37143
|
function isNativeAbsolutePath(value, platform = process.platform) {
|
|
36737
|
-
return platform === "win32" ?
|
|
37144
|
+
return platform === "win32" ? import_node_path17.win32.isAbsolute(value) : import_node_path17.posix.isAbsolute(value);
|
|
36738
37145
|
}
|
|
36739
37146
|
function listenerNodeExecArgv(values2) {
|
|
36740
37147
|
const safe = [];
|
|
@@ -36814,7 +37221,9 @@ function buildListenerChildArgs(spec) {
|
|
|
36814
37221
|
...provider === "codex" && codexExe ? ["--codex-executable", codexExe] : [],
|
|
36815
37222
|
...spec.model ? ["--model", spec.model] : [],
|
|
36816
37223
|
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : [],
|
|
36817
|
-
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : []
|
|
37224
|
+
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : [],
|
|
37225
|
+
...spec.route && spec.route !== "worker" ? ["--route", spec.route] : [],
|
|
37226
|
+
...spec.deferOver !== void 0 ? ["--defer-over", String(spec.deferOver)] : []
|
|
36818
37227
|
];
|
|
36819
37228
|
}
|
|
36820
37229
|
async function spawnDetachedListener(options) {
|
|
@@ -36847,6 +37256,503 @@ async function spawnDetachedListener(options) {
|
|
|
36847
37256
|
return child;
|
|
36848
37257
|
}
|
|
36849
37258
|
|
|
37259
|
+
// src/listener/hook.ts
|
|
37260
|
+
var import_promises10 = require("node:fs/promises");
|
|
37261
|
+
var import_node_path18 = require("node:path");
|
|
37262
|
+
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;
|
|
37263
|
+
var TOKEN_RE = /^swm_agt_[A-Za-z0-9_-]{43}$/;
|
|
37264
|
+
var INSTANCE_KEY_RE = /^[0-9a-f]{64}$/;
|
|
37265
|
+
var MAX_HOOK_CREDENTIAL_BYTES = 8 * 1024;
|
|
37266
|
+
var MAX_HOOK_SURFACE_BYTES = 128 * 1024;
|
|
37267
|
+
var MAX_GLOBAL_STATE_BYTES = 4 * 1024;
|
|
37268
|
+
var LISTENER_CREDENTIAL_FILE = "listener-credential.json";
|
|
37269
|
+
var RETIRED_HOOK_CREDENTIAL_FILE = "hook-credential.json";
|
|
37270
|
+
var HOOK_SURFACE_FILE = "hook-surface.json";
|
|
37271
|
+
var GLOBAL_STATE_FILE = "hook-check.json";
|
|
37272
|
+
var HOOK_SURFACE_LOCK = "hook-surface";
|
|
37273
|
+
var GLOBAL_STATE_LOCK = "hook-check";
|
|
37274
|
+
var HOOK_LOCK_TIMEOUT_MS = 250;
|
|
37275
|
+
var HOOK_CHECK_TIMEOUT_MS = 3e3;
|
|
37276
|
+
var HOOK_DEFAULT_COOLDOWN_SECONDS = 30;
|
|
37277
|
+
var HOOK_SURFACED_IDS_MAX = 1024;
|
|
37278
|
+
var HOOK_BODY_PREVIEW_CHARS = 240;
|
|
37279
|
+
function exactKeys2(row, keys) {
|
|
37280
|
+
const expected = new Set(keys);
|
|
37281
|
+
return Object.keys(row).length === expected.size && Object.keys(row).every((key2) => expected.has(key2));
|
|
37282
|
+
}
|
|
37283
|
+
function parseListenerCredential(raw) {
|
|
37284
|
+
let value;
|
|
37285
|
+
try {
|
|
37286
|
+
value = JSON.parse(raw);
|
|
37287
|
+
} catch {
|
|
37288
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37289
|
+
}
|
|
37290
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37291
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37292
|
+
}
|
|
37293
|
+
const row = value;
|
|
37294
|
+
if (!exactKeys2(row, [
|
|
37295
|
+
"version",
|
|
37296
|
+
"profileId",
|
|
37297
|
+
"targetUrl",
|
|
37298
|
+
"anonKey",
|
|
37299
|
+
"workspaceId",
|
|
37300
|
+
"principalId",
|
|
37301
|
+
"credential",
|
|
37302
|
+
"updatedAt"
|
|
37303
|
+
]) || row.version !== 1 || typeof row.profileId !== "string" || !/^[0-9a-f]{24}$/.test(row.profileId) || typeof row.targetUrl !== "string" || typeof row.anonKey !== "string" || row.anonKey.length < 1 || row.anonKey.length > 4096 || typeof row.workspaceId !== "string" || !UUID_RE16.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE16.test(row.principalId) || typeof row.credential !== "string" || !TOKEN_RE.test(row.credential) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt))) {
|
|
37304
|
+
throw new Error("stored listener hook credential is malformed");
|
|
37305
|
+
}
|
|
37306
|
+
const target2 = cloudTarget(row.targetUrl, row.anonKey);
|
|
37307
|
+
if (target2.profileId !== row.profileId) {
|
|
37308
|
+
throw new Error("stored listener hook credential target does not match its profile");
|
|
37309
|
+
}
|
|
37310
|
+
return {
|
|
37311
|
+
version: 1,
|
|
37312
|
+
profileId: row.profileId,
|
|
37313
|
+
targetUrl: target2.url,
|
|
37314
|
+
anonKey: target2.anonKey,
|
|
37315
|
+
workspaceId: row.workspaceId.toLowerCase(),
|
|
37316
|
+
principalId: row.principalId.toLowerCase(),
|
|
37317
|
+
credential: row.credential,
|
|
37318
|
+
updatedAt: row.updatedAt
|
|
37319
|
+
};
|
|
37320
|
+
}
|
|
37321
|
+
async function writeListenerCredentialState(instanceDirectory, input) {
|
|
37322
|
+
if (!(0, import_node_path18.isAbsolute)(instanceDirectory)) {
|
|
37323
|
+
throw new Error("listener hook state directory must be absolute");
|
|
37324
|
+
}
|
|
37325
|
+
const record = parseListenerCredential(JSON.stringify({
|
|
37326
|
+
version: 1,
|
|
37327
|
+
profileId: input.target.profileId,
|
|
37328
|
+
targetUrl: input.target.url,
|
|
37329
|
+
anonKey: input.target.anonKey,
|
|
37330
|
+
workspaceId: input.workspaceId,
|
|
37331
|
+
principalId: input.principalId,
|
|
37332
|
+
credential: input.credential,
|
|
37333
|
+
updatedAt: new Date(input.now ?? Date.now()).toISOString()
|
|
37334
|
+
}));
|
|
37335
|
+
await writeSecureJsonFile(
|
|
37336
|
+
(0, import_node_path18.join)(instanceDirectory, LISTENER_CREDENTIAL_FILE),
|
|
37337
|
+
JSON.stringify(record)
|
|
37338
|
+
);
|
|
37339
|
+
await deleteSecureJsonFile(
|
|
37340
|
+
(0, import_node_path18.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
37341
|
+
).catch(() => void 0);
|
|
37342
|
+
}
|
|
37343
|
+
async function readListenerCredentialState(instanceDirectory) {
|
|
37344
|
+
const raw = await readSecureJsonFile(
|
|
37345
|
+
(0, import_node_path18.join)(instanceDirectory, LISTENER_CREDENTIAL_FILE),
|
|
37346
|
+
MAX_HOOK_CREDENTIAL_BYTES
|
|
37347
|
+
);
|
|
37348
|
+
return raw === null ? null : parseListenerCredential(raw);
|
|
37349
|
+
}
|
|
37350
|
+
function parseSurface(raw) {
|
|
37351
|
+
let value;
|
|
37352
|
+
try {
|
|
37353
|
+
value = JSON.parse(raw);
|
|
37354
|
+
} catch {
|
|
37355
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37356
|
+
}
|
|
37357
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37358
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37359
|
+
}
|
|
37360
|
+
const row = value;
|
|
37361
|
+
if (Object.keys(row).some(
|
|
37362
|
+
(key2) => key2 !== "version" && key2 !== "surfacedSignalIds" && key2 !== "reportedDroppedCount" && key2 !== "credentialFailureReported"
|
|
37363
|
+
) || row.version !== 1 || !Array.isArray(row.surfacedSignalIds) || row.surfacedSignalIds.length > HOOK_SURFACED_IDS_MAX || row.surfacedSignalIds.some((id) => typeof id !== "string" || !UUID_RE16.test(id)) || !(row.reportedDroppedCount === void 0 || typeof row.reportedDroppedCount === "number" && Number.isSafeInteger(row.reportedDroppedCount) && row.reportedDroppedCount >= 0) || !(row.credentialFailureReported === void 0 || typeof row.credentialFailureReported === "boolean")) {
|
|
37364
|
+
throw new Error("stored listener hook surface state is malformed");
|
|
37365
|
+
}
|
|
37366
|
+
const ids = row.surfacedSignalIds.map((id) => String(id).toLowerCase());
|
|
37367
|
+
if (new Set(ids).size !== ids.length) {
|
|
37368
|
+
throw new Error("stored listener hook surface state repeats a signal");
|
|
37369
|
+
}
|
|
37370
|
+
return {
|
|
37371
|
+
version: 1,
|
|
37372
|
+
surfacedSignalIds: ids,
|
|
37373
|
+
reportedDroppedCount: typeof row.reportedDroppedCount === "number" ? row.reportedDroppedCount : 0,
|
|
37374
|
+
credentialFailureReported: row.credentialFailureReported === true
|
|
37375
|
+
};
|
|
37376
|
+
}
|
|
37377
|
+
var FileHookSurfaceStore = class {
|
|
37378
|
+
constructor(instanceDirectory) {
|
|
37379
|
+
this.instanceDirectory = instanceDirectory;
|
|
37380
|
+
if (!(0, import_node_path18.isAbsolute)(instanceDirectory)) {
|
|
37381
|
+
throw new Error("listener hook surface directory must be absolute");
|
|
37382
|
+
}
|
|
37383
|
+
this.path = (0, import_node_path18.join)(instanceDirectory, HOOK_SURFACE_FILE);
|
|
37384
|
+
}
|
|
37385
|
+
instanceDirectory;
|
|
37386
|
+
path;
|
|
37387
|
+
async stage(items, droppedCount) {
|
|
37388
|
+
return await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
37389
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
37390
|
+
const state = raw === null ? {
|
|
37391
|
+
version: 1,
|
|
37392
|
+
surfacedSignalIds: [],
|
|
37393
|
+
reportedDroppedCount: 0,
|
|
37394
|
+
credentialFailureReported: false
|
|
37395
|
+
} : parseSurface(raw);
|
|
37396
|
+
const seen = new Set(state.surfacedSignalIds);
|
|
37397
|
+
const unseen = [];
|
|
37398
|
+
for (const item of items) {
|
|
37399
|
+
const signalId = item.signalId.toLowerCase();
|
|
37400
|
+
if (!UUID_RE16.test(signalId) || seen.has(signalId)) continue;
|
|
37401
|
+
seen.add(signalId);
|
|
37402
|
+
unseen.push(item);
|
|
37403
|
+
}
|
|
37404
|
+
return {
|
|
37405
|
+
unseen,
|
|
37406
|
+
droppedSinceLastCheck: droppedCount < state.reportedDroppedCount ? droppedCount : droppedCount - state.reportedDroppedCount,
|
|
37407
|
+
credentialFailureReported: state.credentialFailureReported
|
|
37408
|
+
};
|
|
37409
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37410
|
+
}
|
|
37411
|
+
async commit(options) {
|
|
37412
|
+
await withFileLock(this.instanceDirectory, HOOK_SURFACE_LOCK, async () => {
|
|
37413
|
+
const raw = await readSecureJsonFile(this.path, MAX_HOOK_SURFACE_BYTES);
|
|
37414
|
+
const state = raw === null ? {
|
|
37415
|
+
version: 1,
|
|
37416
|
+
surfacedSignalIds: [],
|
|
37417
|
+
reportedDroppedCount: 0,
|
|
37418
|
+
credentialFailureReported: false
|
|
37419
|
+
} : parseSurface(raw);
|
|
37420
|
+
const seen = new Set(state.surfacedSignalIds);
|
|
37421
|
+
for (const signalId of options.signalIds ?? []) {
|
|
37422
|
+
const checked = signalId.toLowerCase();
|
|
37423
|
+
if (UUID_RE16.test(checked)) seen.add(checked);
|
|
37424
|
+
}
|
|
37425
|
+
await writeSecureJsonFile(
|
|
37426
|
+
this.path,
|
|
37427
|
+
JSON.stringify(parseSurface(JSON.stringify({
|
|
37428
|
+
version: 1,
|
|
37429
|
+
surfacedSignalIds: [...seen].slice(-HOOK_SURFACED_IDS_MAX),
|
|
37430
|
+
reportedDroppedCount: options.droppedCount ?? state.reportedDroppedCount,
|
|
37431
|
+
credentialFailureReported: options.credentialFailureReported ?? state.credentialFailureReported
|
|
37432
|
+
})))
|
|
37433
|
+
);
|
|
37434
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37435
|
+
}
|
|
37436
|
+
async claimUnseen(items) {
|
|
37437
|
+
const staged = await this.stage(items, 0);
|
|
37438
|
+
if (staged.unseen.length > 0) {
|
|
37439
|
+
await this.commit({ signalIds: staged.unseen.map((item) => item.signalId) });
|
|
37440
|
+
}
|
|
37441
|
+
return staged.unseen;
|
|
37442
|
+
}
|
|
37443
|
+
};
|
|
37444
|
+
function parseGlobalState(raw) {
|
|
37445
|
+
let value;
|
|
37446
|
+
try {
|
|
37447
|
+
value = JSON.parse(raw);
|
|
37448
|
+
} catch {
|
|
37449
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37450
|
+
}
|
|
37451
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
37452
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37453
|
+
}
|
|
37454
|
+
const row = value;
|
|
37455
|
+
if (!exactKeys2(row, ["version", "lastCheckAt"]) || row.version !== 1 || typeof row.lastCheckAt !== "number" || !Number.isSafeInteger(row.lastCheckAt) || row.lastCheckAt < 0) {
|
|
37456
|
+
throw new Error("stored hook cooldown state is malformed");
|
|
37457
|
+
}
|
|
37458
|
+
return { version: 1, lastCheckAt: row.lastCheckAt };
|
|
37459
|
+
}
|
|
37460
|
+
async function reserveCheck(stateDirectory2, cooldownMs, now) {
|
|
37461
|
+
return await withFileLock(stateDirectory2, GLOBAL_STATE_LOCK, async () => {
|
|
37462
|
+
const path = (0, import_node_path18.join)(stateDirectory2, GLOBAL_STATE_FILE);
|
|
37463
|
+
const raw = await readSecureJsonFile(path, MAX_GLOBAL_STATE_BYTES);
|
|
37464
|
+
const previous = raw === null ? null : parseGlobalState(raw);
|
|
37465
|
+
if (previous !== null && now - previous.lastCheckAt < cooldownMs) return false;
|
|
37466
|
+
await writeSecureJsonFile(path, JSON.stringify({ version: 1, lastCheckAt: now }));
|
|
37467
|
+
return true;
|
|
37468
|
+
}, { timeoutMs: HOOK_LOCK_TIMEOUT_MS });
|
|
37469
|
+
}
|
|
37470
|
+
function processIsAlive(pid) {
|
|
37471
|
+
try {
|
|
37472
|
+
process.kill(pid, 0);
|
|
37473
|
+
return true;
|
|
37474
|
+
} catch (error) {
|
|
37475
|
+
return error.code === "EPERM";
|
|
37476
|
+
}
|
|
37477
|
+
}
|
|
37478
|
+
async function statusContext(stateDirectory2, key2, instanceDirectory) {
|
|
37479
|
+
const provisional = {
|
|
37480
|
+
key: key2,
|
|
37481
|
+
instanceDirectory,
|
|
37482
|
+
statusPath: (0, import_node_path18.join)(instanceDirectory, "status.json"),
|
|
37483
|
+
logPath: (0, import_node_path18.join)(instanceDirectory, "events.ndjson"),
|
|
37484
|
+
socketPath: ""
|
|
37485
|
+
};
|
|
37486
|
+
const status = await readListenerStatus(provisional).catch(() => null);
|
|
37487
|
+
if (status === null) return null;
|
|
37488
|
+
const paths = listenerPaths({
|
|
37489
|
+
profileId: status.profileId,
|
|
37490
|
+
workspaceId: status.workspaceId,
|
|
37491
|
+
principalId: status.principalId,
|
|
37492
|
+
stateDirectory: stateDirectory2
|
|
37493
|
+
});
|
|
37494
|
+
if (paths.key !== key2 || paths.instanceDirectory !== instanceDirectory) return null;
|
|
37495
|
+
return { paths, status };
|
|
37496
|
+
}
|
|
37497
|
+
async function listenerIsLive(context) {
|
|
37498
|
+
if (context.status.state === "stopped" || context.status.state === "failed" || !processIsAlive(context.status.pid)) {
|
|
37499
|
+
return false;
|
|
37500
|
+
}
|
|
37501
|
+
try {
|
|
37502
|
+
await queryListenerControl(context.paths, "status", 250);
|
|
37503
|
+
return true;
|
|
37504
|
+
} catch {
|
|
37505
|
+
return false;
|
|
37506
|
+
}
|
|
37507
|
+
}
|
|
37508
|
+
async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive) {
|
|
37509
|
+
let entries;
|
|
37510
|
+
try {
|
|
37511
|
+
entries = await (0, import_promises10.readdir)(stateDirectory2, { withFileTypes: true });
|
|
37512
|
+
} catch (error) {
|
|
37513
|
+
if (error.code === "ENOENT") return [];
|
|
37514
|
+
throw error;
|
|
37515
|
+
}
|
|
37516
|
+
const contexts = [];
|
|
37517
|
+
for (const entry of entries) {
|
|
37518
|
+
if (!entry.isDirectory() || !INSTANCE_KEY_RE.test(entry.name)) continue;
|
|
37519
|
+
const instanceDirectory = (0, import_node_path18.join)(stateDirectory2, entry.name);
|
|
37520
|
+
const storedStatus = await statusContext(
|
|
37521
|
+
stateDirectory2,
|
|
37522
|
+
entry.name,
|
|
37523
|
+
instanceDirectory
|
|
37524
|
+
);
|
|
37525
|
+
const statusIsLive = storedStatus === null ? null : await isListenerLive(storedStatus);
|
|
37526
|
+
if (statusIsLive === false) continue;
|
|
37527
|
+
await deleteSecureJsonFile(
|
|
37528
|
+
(0, import_node_path18.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
37529
|
+
).catch(() => void 0);
|
|
37530
|
+
try {
|
|
37531
|
+
const credential = await readListenerCredentialState(instanceDirectory);
|
|
37532
|
+
contexts.push({
|
|
37533
|
+
instanceDirectory,
|
|
37534
|
+
paths: storedStatus?.paths ?? null,
|
|
37535
|
+
status: storedStatus?.status ?? null,
|
|
37536
|
+
credential,
|
|
37537
|
+
credentialReadFailed: credential === null && storedStatus !== null
|
|
37538
|
+
});
|
|
37539
|
+
} catch {
|
|
37540
|
+
if (storedStatus === null) continue;
|
|
37541
|
+
contexts.push({
|
|
37542
|
+
instanceDirectory,
|
|
37543
|
+
paths: storedStatus.paths,
|
|
37544
|
+
status: storedStatus.status,
|
|
37545
|
+
credential: null,
|
|
37546
|
+
credentialReadFailed: true
|
|
37547
|
+
});
|
|
37548
|
+
}
|
|
37549
|
+
}
|
|
37550
|
+
return contexts;
|
|
37551
|
+
}
|
|
37552
|
+
function entryFromSignal(signal, principalId, directory, now) {
|
|
37553
|
+
const senderName = signal.from_kind === "agent" ? directory?.agents.find((agent) => agent.principal_id === signal.from)?.name ?? null : directory?.members.find((member) => member.user_id === signal.from)?.display_name ?? null;
|
|
37554
|
+
return {
|
|
37555
|
+
signalId: signal.id,
|
|
37556
|
+
workspaceId: signal.workspace_id,
|
|
37557
|
+
principalId,
|
|
37558
|
+
fromId: signal.from,
|
|
37559
|
+
fromKind: signal.from_kind,
|
|
37560
|
+
senderName,
|
|
37561
|
+
body: signal.body,
|
|
37562
|
+
createdAt: signal.created_at,
|
|
37563
|
+
queuedAt: new Date(now).toISOString()
|
|
37564
|
+
};
|
|
37565
|
+
}
|
|
37566
|
+
function preview(value) {
|
|
37567
|
+
let text = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
37568
|
+
if (text.length > HOOK_BODY_PREVIEW_CHARS) {
|
|
37569
|
+
text = `${text.slice(0, HOOK_BODY_PREVIEW_CHARS - 1)}\u2026`;
|
|
37570
|
+
}
|
|
37571
|
+
return JSON.stringify(text);
|
|
37572
|
+
}
|
|
37573
|
+
function renderHookSignal(item) {
|
|
37574
|
+
const sender = item.senderName === null ? item.fromId : item.senderName;
|
|
37575
|
+
return [
|
|
37576
|
+
`[CSWARM MESSAGE from ${item.fromKind} ${JSON.stringify(sender)}; workspace ${item.workspaceId}]`,
|
|
37577
|
+
preview(item.body),
|
|
37578
|
+
`operator reply command: cswarm reply ${item.signalId} "<answer>" --workspace-id ${item.workspaceId}`
|
|
37579
|
+
].join("\n");
|
|
37580
|
+
}
|
|
37581
|
+
async function inboxItems(context, options) {
|
|
37582
|
+
const stored = context.credential;
|
|
37583
|
+
const target2 = cloudTarget(stored.targetUrl, stored.anonKey);
|
|
37584
|
+
const readOptions = {
|
|
37585
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
37586
|
+
signal: options.signal,
|
|
37587
|
+
deadlineMs: options.deadlineMs,
|
|
37588
|
+
now: options.now
|
|
37589
|
+
};
|
|
37590
|
+
const page = await readAgentSignalPage(
|
|
37591
|
+
target2,
|
|
37592
|
+
{ kind: "agent", token: stored.credential },
|
|
37593
|
+
{
|
|
37594
|
+
workspaceId: stored.workspaceId,
|
|
37595
|
+
inbox: true,
|
|
37596
|
+
ascending: false,
|
|
37597
|
+
limit: 100,
|
|
37598
|
+
includeStale: false
|
|
37599
|
+
},
|
|
37600
|
+
readOptions,
|
|
37601
|
+
{ tolerateMalformedRows: true, maxMalformedRows: 3 }
|
|
37602
|
+
);
|
|
37603
|
+
const directed = page.signals.filter(
|
|
37604
|
+
(signal) => (signal.kind === "ask" || signal.kind === "note") && signal.workspace_id === stored.workspaceId && signal.to_agent === stored.principalId
|
|
37605
|
+
);
|
|
37606
|
+
if (directed.length === 0) return [];
|
|
37607
|
+
let directory = null;
|
|
37608
|
+
try {
|
|
37609
|
+
directory = await readAgentSignalDirectory(
|
|
37610
|
+
target2,
|
|
37611
|
+
stored.credential,
|
|
37612
|
+
stored.workspaceId,
|
|
37613
|
+
readOptions
|
|
37614
|
+
);
|
|
37615
|
+
} catch {
|
|
37616
|
+
directory = null;
|
|
37617
|
+
}
|
|
37618
|
+
const candidates = directed.map((signal) => entryFromSignal(signal, stored.principalId, directory, options.now())).sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
37619
|
+
return candidates;
|
|
37620
|
+
}
|
|
37621
|
+
function renderDroppedAsks(count2) {
|
|
37622
|
+
return `${count2} routed asks were dropped from the overflow queue; check cswarm inbox. The signals remain in the inbox.`;
|
|
37623
|
+
}
|
|
37624
|
+
var CREDENTIAL_READ_WARNING = "CommonSwarm could not read the configured listener credential safely. The hook is not checking routed asks. Restart the listener with a fresh credential; any credential state file must be mode 0600. Then run cswarm listen status.";
|
|
37625
|
+
var CREDENTIAL_401_WARNING = "CommonSwarm could not authenticate a listener credential (HTTP 401). The hook is not checking routed asks. Restart the listener with a fresh credential, then run cswarm listen status.";
|
|
37626
|
+
async function checkListenerHooks(options) {
|
|
37627
|
+
try {
|
|
37628
|
+
const stateDirectory2 = options.stateDirectory ?? defaultListenerStateDirectory();
|
|
37629
|
+
if (!(0, import_node_path18.isAbsolute)(stateDirectory2)) return "";
|
|
37630
|
+
const now = options.now ?? Date.now;
|
|
37631
|
+
const cooldownSeconds = options.cooldownSeconds ?? HOOK_DEFAULT_COOLDOWN_SECONDS;
|
|
37632
|
+
if (!Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400) {
|
|
37633
|
+
return "";
|
|
37634
|
+
}
|
|
37635
|
+
const contexts = await discoverContexts(
|
|
37636
|
+
stateDirectory2,
|
|
37637
|
+
options.isListenerLive ?? listenerIsLive
|
|
37638
|
+
);
|
|
37639
|
+
if (contexts.length === 0) return "";
|
|
37640
|
+
const networkAllowed = await reserveCheck(
|
|
37641
|
+
stateDirectory2,
|
|
37642
|
+
cooldownSeconds * 1e3,
|
|
37643
|
+
now()
|
|
37644
|
+
);
|
|
37645
|
+
const checks = await Promise.all(contexts.map(async (context) => {
|
|
37646
|
+
const queue = new FilePendingMainQueue(context.instanceDirectory);
|
|
37647
|
+
const pending = await queue.read();
|
|
37648
|
+
const stats = await queue.stats();
|
|
37649
|
+
let network = [];
|
|
37650
|
+
let credentialFailure = context.credentialReadFailed ? "read" : null;
|
|
37651
|
+
let credentialHealthy = false;
|
|
37652
|
+
if (networkAllowed && !options.signal.aborted && context.credential !== null) {
|
|
37653
|
+
try {
|
|
37654
|
+
network = await inboxItems(context, {
|
|
37655
|
+
...options.fetcher ? { fetcher: options.fetcher } : {},
|
|
37656
|
+
signal: options.signal,
|
|
37657
|
+
deadlineMs: options.deadlineMs,
|
|
37658
|
+
now
|
|
37659
|
+
});
|
|
37660
|
+
credentialHealthy = true;
|
|
37661
|
+
} catch (error) {
|
|
37662
|
+
if (followHttpDetails(error)?.status === 401) credentialFailure = "401";
|
|
37663
|
+
}
|
|
37664
|
+
}
|
|
37665
|
+
return {
|
|
37666
|
+
context,
|
|
37667
|
+
queue,
|
|
37668
|
+
pending,
|
|
37669
|
+
droppedCount: stats.droppedCount,
|
|
37670
|
+
network,
|
|
37671
|
+
credentialFailure,
|
|
37672
|
+
credentialHealthy
|
|
37673
|
+
};
|
|
37674
|
+
}));
|
|
37675
|
+
const commits = [];
|
|
37676
|
+
const blocks = [];
|
|
37677
|
+
const emittedCredentialWarnings = /* @__PURE__ */ new Set();
|
|
37678
|
+
for (const check of checks) {
|
|
37679
|
+
const store2 = new FileHookSurfaceStore(check.context.instanceDirectory);
|
|
37680
|
+
const staged = await store2.stage(
|
|
37681
|
+
[...check.pending, ...check.network],
|
|
37682
|
+
check.droppedCount
|
|
37683
|
+
);
|
|
37684
|
+
blocks.push(...staged.unseen.map(renderHookSignal));
|
|
37685
|
+
const reportDrops = staged.droppedSinceLastCheck > 0;
|
|
37686
|
+
if (reportDrops) blocks.push(renderDroppedAsks(staged.droppedSinceLastCheck));
|
|
37687
|
+
const reportCredentialFailure = check.credentialFailure !== null && !staged.credentialFailureReported;
|
|
37688
|
+
if (reportCredentialFailure) {
|
|
37689
|
+
const warning = check.credentialFailure === "401" ? CREDENTIAL_401_WARNING : CREDENTIAL_READ_WARNING;
|
|
37690
|
+
if (!emittedCredentialWarnings.has(warning)) {
|
|
37691
|
+
emittedCredentialWarnings.add(warning);
|
|
37692
|
+
blocks.push(warning);
|
|
37693
|
+
}
|
|
37694
|
+
}
|
|
37695
|
+
const pendingSignalIds = new Set(check.pending.map((entry) => entry.signalId));
|
|
37696
|
+
commits.push({
|
|
37697
|
+
check,
|
|
37698
|
+
store: store2,
|
|
37699
|
+
signalIds: staged.unseen.map((item) => item.signalId),
|
|
37700
|
+
printedPendingSignalIds: staged.unseen.map((item) => item.signalId).filter((signalId) => pendingSignalIds.has(signalId)),
|
|
37701
|
+
reportDrops,
|
|
37702
|
+
reportCredentialFailure
|
|
37703
|
+
});
|
|
37704
|
+
}
|
|
37705
|
+
const output = blocks.join("\n\n");
|
|
37706
|
+
if (output.length > 0) await (options.write ?? (() => void 0))(output);
|
|
37707
|
+
for (const commit of commits) {
|
|
37708
|
+
await commit.store.commit({
|
|
37709
|
+
signalIds: commit.signalIds,
|
|
37710
|
+
...commit.reportDrops ? { droppedCount: commit.check.droppedCount } : {},
|
|
37711
|
+
...commit.reportCredentialFailure ? { credentialFailureReported: true } : commit.check.credentialHealthy ? { credentialFailureReported: false } : {}
|
|
37712
|
+
});
|
|
37713
|
+
const remainingCount = await commit.check.queue.remove(
|
|
37714
|
+
new Set(commit.printedPendingSignalIds),
|
|
37715
|
+
HOOK_LOCK_TIMEOUT_MS
|
|
37716
|
+
);
|
|
37717
|
+
if (commit.check.context.paths !== null && commit.check.context.status !== null) {
|
|
37718
|
+
const latest = await readListenerStatus(commit.check.context.paths).catch(() => null);
|
|
37719
|
+
if (latest !== null && latest.pendingForMainCount !== remainingCount) {
|
|
37720
|
+
await writeListenerStatus(commit.check.context.paths, {
|
|
37721
|
+
...latest,
|
|
37722
|
+
pendingForMainCount: remainingCount
|
|
37723
|
+
});
|
|
37724
|
+
}
|
|
37725
|
+
}
|
|
37726
|
+
}
|
|
37727
|
+
return output;
|
|
37728
|
+
} catch {
|
|
37729
|
+
return "";
|
|
37730
|
+
}
|
|
37731
|
+
}
|
|
37732
|
+
async function runListenerHookCheck(options = {}) {
|
|
37733
|
+
const controller = new AbortController();
|
|
37734
|
+
const deadlineMs = Date.now() + HOOK_CHECK_TIMEOUT_MS;
|
|
37735
|
+
let timer2;
|
|
37736
|
+
try {
|
|
37737
|
+
const checking = checkListenerHooks({
|
|
37738
|
+
...options,
|
|
37739
|
+
signal: controller.signal,
|
|
37740
|
+
deadlineMs
|
|
37741
|
+
});
|
|
37742
|
+
const timedOut = new Promise((resolve) => {
|
|
37743
|
+
timer2 = setTimeout(() => {
|
|
37744
|
+
controller.abort();
|
|
37745
|
+
resolve("");
|
|
37746
|
+
}, HOOK_CHECK_TIMEOUT_MS);
|
|
37747
|
+
});
|
|
37748
|
+
return await Promise.race([checking, timedOut]);
|
|
37749
|
+
} catch {
|
|
37750
|
+
return "";
|
|
37751
|
+
} finally {
|
|
37752
|
+
if (timer2 !== void 0) clearTimeout(timer2);
|
|
37753
|
+
}
|
|
37754
|
+
}
|
|
37755
|
+
|
|
36850
37756
|
// src/cli.ts
|
|
36851
37757
|
var import_meta = {};
|
|
36852
37758
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -36859,7 +37765,9 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36859
37765
|
"claude-executable",
|
|
36860
37766
|
"codex-executable",
|
|
36861
37767
|
"confirm",
|
|
37768
|
+
"cooldown",
|
|
36862
37769
|
"cwd",
|
|
37770
|
+
"defer-over",
|
|
36863
37771
|
"device-id",
|
|
36864
37772
|
"effort",
|
|
36865
37773
|
"email",
|
|
@@ -36891,6 +37799,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36891
37799
|
"principal-id",
|
|
36892
37800
|
"provider",
|
|
36893
37801
|
"reveal-anon-key",
|
|
37802
|
+
"route",
|
|
36894
37803
|
"run-id",
|
|
36895
37804
|
"since",
|
|
36896
37805
|
"site",
|
|
@@ -36905,7 +37814,8 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36905
37814
|
"url",
|
|
36906
37815
|
"version",
|
|
36907
37816
|
"wait",
|
|
36908
|
-
"workspace-id"
|
|
37817
|
+
"workspace-id",
|
|
37818
|
+
"write"
|
|
36909
37819
|
]);
|
|
36910
37820
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
36911
37821
|
"agent-token-stdin",
|
|
@@ -36923,9 +37833,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36923
37833
|
"local",
|
|
36924
37834
|
"ndjson",
|
|
36925
37835
|
"no-browser",
|
|
36926
|
-
"reveal-anon-key"
|
|
37836
|
+
"reveal-anon-key",
|
|
37837
|
+
"write"
|
|
36927
37838
|
]);
|
|
36928
|
-
var
|
|
37839
|
+
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;
|
|
36929
37840
|
var AGENT_CREDENTIAL_MESSAGE = "Agent credential minted. It is bound to this task and run so the agent's work stays scoped and attributable.";
|
|
36930
37841
|
var AGENT_CREDENTIAL_MESSAGE_D088 = "Agent credential minted. It is bound to this run, so the agent's work is attributable to it.";
|
|
36931
37842
|
var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
@@ -36933,8 +37844,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
36933
37844
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
36934
37845
|
];
|
|
36935
37846
|
function packageVersion() {
|
|
36936
|
-
if ("0.1.
|
|
36937
|
-
return "0.1.
|
|
37847
|
+
if ("0.1.25".length > 0) {
|
|
37848
|
+
return "0.1.25";
|
|
36938
37849
|
}
|
|
36939
37850
|
try {
|
|
36940
37851
|
const value = JSON.parse(
|
|
@@ -37062,9 +37973,12 @@ Usage:
|
|
|
37062
37973
|
cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37063
37974
|
cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37064
37975
|
cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37065
|
-
cswarm listen start --agent-token-stdin [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--foreground] [--json]
|
|
37976
|
+
cswarm listen start --agent-token-stdin [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--foreground] [--json]
|
|
37066
37977
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
37067
37978
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
37979
|
+
cswarm hook check [--cooldown <seconds>]
|
|
37980
|
+
cswarm hook install claude [--write]
|
|
37981
|
+
cswarm hook uninstall claude --write
|
|
37068
37982
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
37069
37983
|
cswarm new --name "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
37070
37984
|
cswarm workspaces [--url <url> --anon-key <key>] [--json]
|
|
@@ -37072,6 +37986,7 @@ Usage:
|
|
|
37072
37986
|
cswarm invite [--url <url> --anon-key <key>] [--workspace-id <uuid>] --email <email>
|
|
37073
37987
|
cswarm invite revoke [--url <url> --anon-key <key>] [--workspace-id <uuid>] --invitation-id <uuid> [--json]
|
|
37074
37988
|
cswarm member remove <full-user-id|exact-name> --confirm <same-selector> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
37989
|
+
cswarm workspace close <full-id|exact-name> --confirm <same-selector> [--url <url> --anon-key <key>] [--json]
|
|
37075
37990
|
cswarm accept --link-stdin [--name <name>] [--no-browser] [--json]
|
|
37076
37991
|
cswarm accept <https://...#invite=...|cswarm://accept/...> [--name <name>] [--no-browser] [--json] # unsafe: shell history/process list
|
|
37077
37992
|
cswarm accept --invitation-token-stdin [--url <url> --anon-key <key>]
|
|
@@ -37099,7 +38014,12 @@ Credential selection for command/dogfood:
|
|
|
37099
38014
|
read and command, nothing persisted -- either form
|
|
37100
38015
|
feedback command only, nothing persisted -- either form
|
|
37101
38016
|
listen start persists durable state, rotates -- needs expires_at
|
|
38017
|
+
hook check reads the listener's owned 0600 credential state;
|
|
38018
|
+
never accepts or prints a credential
|
|
38019
|
+
hook install/uninstall
|
|
38020
|
+
edits only local Claude Code settings; no credential
|
|
37102
38021
|
token revoke names what it revokes -- needs token_id
|
|
38022
|
+
workspace close is human-session-only; it never accepts an agent token
|
|
37103
38023
|
|
|
37104
38024
|
Found a bug or missing feature in cswarm itself? cswarm feedback sends it to the
|
|
37105
38025
|
deployment's operators \u2014 agents are encouraged to report friction they hit.
|
|
@@ -37108,6 +38028,8 @@ Signals (intention sharing) accept the same credential selection. Agent mode
|
|
|
37108
38028
|
never opens a browser or infers a human's saved workspace. Durations use a whole
|
|
37109
38029
|
number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
|
|
37110
38030
|
Place -- before signal text that itself begins with -- to stop option parsing.
|
|
38031
|
+
Signal text is at most 2000 characters and --about at most 500; a longer body is
|
|
38032
|
+
refused locally before any network call, so compose within the limit.
|
|
37111
38033
|
|
|
37112
38034
|
listen start --turn-budget bounds ONE worker prompt turn (default 10m): how long
|
|
37113
38035
|
the worker may think and use tools on a single message before the turn times out
|
|
@@ -37120,7 +38042,16 @@ TTL); a turn that lands just before a rotation can be clamped to the ~5m
|
|
|
37120
38042
|
renewal lead, and if it times out there, durable delivery retries it on the
|
|
37121
38043
|
fresh credential.
|
|
37122
38044
|
|
|
37123
|
-
|
|
38045
|
+
listen start --route worker|main|split chooses where directed asks go. worker
|
|
38046
|
+
is the unchanged default. main queues every ask for the interactive session.
|
|
38047
|
+
split queues asks whose body is longer than --defer-over <chars>; the bound is
|
|
38048
|
+
1..10000 and an equal-length ask stays on the worker path. Run cswarm hook check
|
|
38049
|
+
to surface queued asks. hook check has its own 3s ceiling, exits 0 on every
|
|
38050
|
+
outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
38051
|
+
hook install claude prints the UserPromptSubmit JSON by default and changes the
|
|
38052
|
+
project's .claude/settings.json only with --write; uninstall also requires --write.
|
|
38053
|
+
|
|
38054
|
+
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
37124
38055
|
stored human login. Agent self-surrender of a token uses --agent-token-stdin and never takes the secret on argv. Invite-link accept signs in when needed, then accepts and
|
|
37125
38056
|
registers one principal. Invitation links, agent credentials, and capability links
|
|
37126
38057
|
appear only in fresh success responses.
|
|
@@ -37248,7 +38179,7 @@ function parsedAgentCredential(value) {
|
|
|
37248
38179
|
const withExpiry = [...requiredKeys, "expires_at"].sort();
|
|
37249
38180
|
const actualKeys = Object.keys(artifact).sort();
|
|
37250
38181
|
const shape = actualKeys.length === requiredKeys.length ? requiredKeys : withExpiry;
|
|
37251
|
-
if (actualKeys.length !== shape.length || !actualKeys.every((key2, index) => key2 === shape[index]) || !ACCEPTED_AGENT_CREDENTIAL_MESSAGES.includes(artifact.message) || artifact.status !== "accepted" || typeof artifact.principal_id !== "string" || !
|
|
38182
|
+
if (actualKeys.length !== shape.length || !actualKeys.every((key2, index) => key2 === shape[index]) || !ACCEPTED_AGENT_CREDENTIAL_MESSAGES.includes(artifact.message) || artifact.status !== "accepted" || typeof artifact.principal_id !== "string" || !UUID_RE17.test(artifact.principal_id) || typeof artifact.token_id !== "string" || !UUID_RE17.test(artifact.token_id) || typeof artifact.run_id !== "string" || !UUID_RE17.test(artifact.run_id) || typeof artifact.agent_token !== "string") {
|
|
37252
38183
|
throw new Error("agent credential JSON is malformed");
|
|
37253
38184
|
}
|
|
37254
38185
|
let expiresAt = null;
|
|
@@ -37329,7 +38260,7 @@ async function stdinInviteLink() {
|
|
|
37329
38260
|
return link;
|
|
37330
38261
|
}
|
|
37331
38262
|
async function confirmationLine(prompt) {
|
|
37332
|
-
const reader = (0,
|
|
38263
|
+
const reader = (0, import_promises12.createInterface)({
|
|
37333
38264
|
input: process.stdin,
|
|
37334
38265
|
output: process.stderr,
|
|
37335
38266
|
terminal: Boolean(process.stdin.isTTY)
|
|
@@ -37486,7 +38417,7 @@ async function runNew(args) {
|
|
|
37486
38417
|
project: {
|
|
37487
38418
|
workspace_id: created,
|
|
37488
38419
|
name,
|
|
37489
|
-
stream_id: typeof response.stream_id === "string" &&
|
|
38420
|
+
stream_id: typeof response.stream_id === "string" && UUID_RE17.test(response.stream_id) ? response.stream_id : null
|
|
37490
38421
|
}
|
|
37491
38422
|
});
|
|
37492
38423
|
return;
|
|
@@ -37815,6 +38746,53 @@ async function runMember(args) {
|
|
|
37815
38746
|
`);
|
|
37816
38747
|
}
|
|
37817
38748
|
}
|
|
38749
|
+
async function runWorkspace(args) {
|
|
38750
|
+
args.assertShape([...TARGET_FLAGS, "confirm", "json"], 3);
|
|
38751
|
+
if (args.positionals[1] !== "close") {
|
|
38752
|
+
throw new UsageError(
|
|
38753
|
+
`unknown workspace command: ${args.positionals[1] ?? "(missing)"}`
|
|
38754
|
+
);
|
|
38755
|
+
}
|
|
38756
|
+
const selector = args.positionals[2];
|
|
38757
|
+
if (args.required("confirm") !== selector) {
|
|
38758
|
+
throw new Error(
|
|
38759
|
+
"--confirm must exactly repeat the workspace selector; no request was sent"
|
|
38760
|
+
);
|
|
38761
|
+
}
|
|
38762
|
+
const cloud = await target(args);
|
|
38763
|
+
const human = await humanCredential(args, cloud);
|
|
38764
|
+
const directory = cloudWorkspaceDirectory(cloud);
|
|
38765
|
+
const projects = await directory.list(human);
|
|
38766
|
+
const selected = resolveWorkspaceSelector(selector, projects);
|
|
38767
|
+
const response = (await sendConnectWithPending(
|
|
38768
|
+
new ThinCommandClient(cloud),
|
|
38769
|
+
human,
|
|
38770
|
+
selected.workspace_id,
|
|
38771
|
+
{ kind: "archive_workspace" }
|
|
38772
|
+
)).response;
|
|
38773
|
+
if (response.status !== "accepted") {
|
|
38774
|
+
throw new Error(
|
|
38775
|
+
`Workspace close was rejected: ${response.reason ?? "domain rejection"}. The workspace is still open.`
|
|
38776
|
+
);
|
|
38777
|
+
}
|
|
38778
|
+
const { closedWasSelected, nextWorkspace, selectedWorkspaceId } = await updateWorkspaceDefaultAfterClose(
|
|
38779
|
+
human.store,
|
|
38780
|
+
human.userId,
|
|
38781
|
+
selected.workspace_id,
|
|
38782
|
+
projects
|
|
38783
|
+
);
|
|
38784
|
+
const message = nextWorkspace ? `Closed workspace ${selected.name} (${selected.workspace_id}). It is hidden for everyone, and ${nextWorkspace.name} (${nextWorkspace.workspace_id}) is now selected.` : closedWasSelected ? `Closed workspace ${selected.name} (${selected.workspace_id}). It is hidden for everyone. No live workspace remains, so the selected workspace was cleared.` : `Closed workspace ${selected.name} (${selected.workspace_id}). It is hidden for everyone. Your selected workspace was not changed.`;
|
|
38785
|
+
const output = {
|
|
38786
|
+
message,
|
|
38787
|
+
status: response.status,
|
|
38788
|
+
workspace_id: selected.workspace_id,
|
|
38789
|
+
selected_workspace_id: selectedWorkspaceId,
|
|
38790
|
+
command_event_ids: response.event_ids
|
|
38791
|
+
};
|
|
38792
|
+
if (args.has("json")) printJson(output);
|
|
38793
|
+
else process.stdout.write(`${message}
|
|
38794
|
+
`);
|
|
38795
|
+
}
|
|
37818
38796
|
async function runLegacyAccept(args) {
|
|
37819
38797
|
const invitationToken = await invitationCredential(args);
|
|
37820
38798
|
const cloud = await target(args);
|
|
@@ -38213,7 +39191,7 @@ async function runLinkNew(args) {
|
|
|
38213
39191
|
2
|
|
38214
39192
|
);
|
|
38215
39193
|
const taskId = args.required("task-id");
|
|
38216
|
-
if (!
|
|
39194
|
+
if (!UUID_RE17.test(taskId)) {
|
|
38217
39195
|
throw new Error("--task-id must be the work item's UUID");
|
|
38218
39196
|
}
|
|
38219
39197
|
const site = capabilitySiteOrigin(
|
|
@@ -38273,7 +39251,7 @@ async function runLinkRevoke(args) {
|
|
|
38273
39251
|
2
|
|
38274
39252
|
);
|
|
38275
39253
|
const capabilityId = args.required("capability-id");
|
|
38276
|
-
if (!
|
|
39254
|
+
if (!UUID_RE17.test(capabilityId)) {
|
|
38277
39255
|
throw new Error(
|
|
38278
39256
|
"--capability-id must be the id printed when the link was created"
|
|
38279
39257
|
);
|
|
@@ -38488,6 +39466,33 @@ function listenerTurnBudgetMs(value) {
|
|
|
38488
39466
|
}
|
|
38489
39467
|
return milliseconds;
|
|
38490
39468
|
}
|
|
39469
|
+
function listenerRouteConfiguration(routeValue, deferOverValue) {
|
|
39470
|
+
const routeMode = routeValue ?? "worker";
|
|
39471
|
+
if (routeMode !== "worker" && routeMode !== "main" && routeMode !== "split") {
|
|
39472
|
+
throw new Error("--route must be worker, main, or split");
|
|
39473
|
+
}
|
|
39474
|
+
if (routeMode !== "split") {
|
|
39475
|
+
if (deferOverValue !== void 0) {
|
|
39476
|
+
throw new Error("--defer-over is only valid with --route split");
|
|
39477
|
+
}
|
|
39478
|
+
return { routeMode, deferOverChars: null };
|
|
39479
|
+
}
|
|
39480
|
+
if (deferOverValue === void 0) {
|
|
39481
|
+
throw new Error("--route split requires --defer-over <chars>");
|
|
39482
|
+
}
|
|
39483
|
+
if (!/^\d+$/.test(deferOverValue)) {
|
|
39484
|
+
throw new Error(
|
|
39485
|
+
`--defer-over must be an integer from ${LISTENER_DEFER_OVER_MIN} to ${LISTENER_DEFER_OVER_MAX}`
|
|
39486
|
+
);
|
|
39487
|
+
}
|
|
39488
|
+
const deferOverChars = Number(deferOverValue);
|
|
39489
|
+
if (!Number.isSafeInteger(deferOverChars) || deferOverChars < LISTENER_DEFER_OVER_MIN || deferOverChars > LISTENER_DEFER_OVER_MAX) {
|
|
39490
|
+
throw new Error(
|
|
39491
|
+
`--defer-over must be an integer from ${LISTENER_DEFER_OVER_MIN} to ${LISTENER_DEFER_OVER_MAX}`
|
|
39492
|
+
);
|
|
39493
|
+
}
|
|
39494
|
+
return { routeMode, deferOverChars };
|
|
39495
|
+
}
|
|
38491
39496
|
var TURN_BUDGET_CREDENTIAL_MARGIN_MS = 6e4;
|
|
38492
39497
|
function clampTurnBudgetToCredential(budgetMs, credentialExpiresAt, nowMs) {
|
|
38493
39498
|
if (credentialExpiresAt === null) return budgetMs;
|
|
@@ -38507,8 +39512,10 @@ function resolveTurnBudgetOrDefer(configuredBudgetMs, credentialExpiresAt, nowMs
|
|
|
38507
39512
|
}
|
|
38508
39513
|
return clampTurnBudgetToCredential(configuredBudgetMs, credentialExpiresAt, nowMs);
|
|
38509
39514
|
}
|
|
39515
|
+
var SIGNAL_BODY_MAX = 2e3;
|
|
39516
|
+
var SIGNAL_ABOUT_MAX = 500;
|
|
38510
39517
|
function signalText(value, label) {
|
|
38511
|
-
const maximum = label === "body" ?
|
|
39518
|
+
const maximum = label === "body" ? SIGNAL_BODY_MAX : SIGNAL_ABOUT_MAX;
|
|
38512
39519
|
if (value.length < (label === "body" ? 1 : 0) || value.length > maximum) {
|
|
38513
39520
|
throw new Error(
|
|
38514
39521
|
`${label === "body" ? "signal text" : "--about"} must be ${label === "body" ? "1.." : "at most "}${maximum} characters`
|
|
@@ -38767,6 +39774,10 @@ Check whether you are about to do the same work.
|
|
|
38767
39774
|
`}`
|
|
38768
39775
|
);
|
|
38769
39776
|
}
|
|
39777
|
+
function replyRefusalHint(error) {
|
|
39778
|
+
if (!(error instanceof CommandHttpError) || error.status !== 403) return null;
|
|
39779
|
+
return "reply was refused (403). The most common cause is that the signal was not addressed to you \u2014 you cannot reply to your own ask; reply to the other party's signal, reach someone directly with cswarm ask --to <agent>, or post a channel-visible cswarm note. If you did receive that signal, the refusal is an authorization one instead: the credential may be revoked or expired, or it may not be a member of this workspace.";
|
|
39780
|
+
}
|
|
38770
39781
|
async function runReply(args) {
|
|
38771
39782
|
args.assertShape([
|
|
38772
39783
|
...TARGET_FLAGS,
|
|
@@ -38776,7 +39787,7 @@ async function runReply(args) {
|
|
|
38776
39787
|
"json"
|
|
38777
39788
|
], 3);
|
|
38778
39789
|
const signalId = args.positionals[1];
|
|
38779
|
-
if (signalId === void 0 || !
|
|
39790
|
+
if (signalId === void 0 || !UUID_RE17.test(signalId)) {
|
|
38780
39791
|
throw new Error("reply requires the signal UUID being answered");
|
|
38781
39792
|
}
|
|
38782
39793
|
const body = args.positionals[2];
|
|
@@ -38798,7 +39809,14 @@ async function runReply(args) {
|
|
|
38798
39809
|
about: null,
|
|
38799
39810
|
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
|
|
38800
39811
|
};
|
|
38801
|
-
|
|
39812
|
+
let result;
|
|
39813
|
+
try {
|
|
39814
|
+
result = await postSignalCommand(cloud, credential, command2);
|
|
39815
|
+
} catch (error) {
|
|
39816
|
+
const hint = replyRefusalHint(error);
|
|
39817
|
+
if (hint !== null) throw new Error(hint);
|
|
39818
|
+
throw error;
|
|
39819
|
+
}
|
|
38802
39820
|
const signal = result.response.signal;
|
|
38803
39821
|
if (args.has("json")) {
|
|
38804
39822
|
printJson({
|
|
@@ -39097,7 +40115,7 @@ async function runInboxFollowCommand(args) {
|
|
|
39097
40115
|
}
|
|
39098
40116
|
}
|
|
39099
40117
|
function listenerUuid(value, flag) {
|
|
39100
|
-
if (!value || !
|
|
40118
|
+
if (!value || !UUID_RE17.test(value)) {
|
|
39101
40119
|
throw new Error(`--${flag} must be a UUID`);
|
|
39102
40120
|
}
|
|
39103
40121
|
return value.toLowerCase();
|
|
@@ -39110,7 +40128,7 @@ function listenerPermissionMode(value) {
|
|
|
39110
40128
|
function listenerStateDirectory(args) {
|
|
39111
40129
|
const value = args.optional("state-dir");
|
|
39112
40130
|
if (value === void 0) return void 0;
|
|
39113
|
-
if (!(0,
|
|
40131
|
+
if (!(0, import_node_path19.isAbsolute)(value)) {
|
|
39114
40132
|
throw new Error("--state-dir must be an absolute path");
|
|
39115
40133
|
}
|
|
39116
40134
|
return value;
|
|
@@ -39270,18 +40288,25 @@ function listenerStatusJson(status, permissionMode) {
|
|
|
39270
40288
|
lastTerminalDeliveryFailureAt: status.lastTerminalDeliveryFailureAt ?? null,
|
|
39271
40289
|
lastClaimAt: status.lastClaimAt ?? null,
|
|
39272
40290
|
lastAckAt: status.lastAckAt ?? null,
|
|
40291
|
+
routeMode: status.routeMode ?? "worker",
|
|
40292
|
+
deferOverChars: status.deferOverChars ?? null,
|
|
40293
|
+
pendingForMainCount: status.pendingForMainCount ?? 0,
|
|
40294
|
+
droppedForMainCount: status.droppedForMainCount ?? 0,
|
|
39273
40295
|
...mode3 ? {
|
|
39274
40296
|
permission_mode: mode3,
|
|
39275
40297
|
/* "allowed once" alone overstates it: allowOnceOrDeny selects allow_once only when the
|
|
39276
40298
|
* host OFFERS that option, and denies otherwise. Both review arms flagged the
|
|
39277
40299
|
* unqualified form on 4844b4e7. */
|
|
39278
|
-
same_owner_delivery: mode3 === "allow" ? "worker session; tool requests allowed once each when the host offers allow_once, otherwise denied" : "worker session; tool requests denied",
|
|
39279
|
-
cross_owner_delivery: mode3 === "allow" ? "same worker session with sender provenance; tool requests allowed once each when the host offers allow_once, otherwise denied" : "same worker session with sender provenance; tool requests denied"
|
|
40300
|
+
same_owner_delivery: status.routeMode === "main" ? "interactive session; no ACP worker prompt" : mode3 === "allow" ? "worker session; tool requests allowed once each when the host offers allow_once, otherwise denied" : "worker session; tool requests denied",
|
|
40301
|
+
cross_owner_delivery: status.routeMode === "main" ? "interactive session; no ACP worker prompt" : mode3 === "allow" ? "same worker session with sender provenance; tool requests allowed once each when the host offers allow_once, otherwise denied" : "same worker session with sender provenance; tool requests denied"
|
|
39280
40302
|
} : {},
|
|
39281
40303
|
host_limits: listenerHostLimits(status.provider)
|
|
39282
40304
|
};
|
|
39283
40305
|
}
|
|
39284
40306
|
function renderListenerStatus(status) {
|
|
40307
|
+
const routeMode = status.routeMode ?? "worker";
|
|
40308
|
+
const pendingForMainCount = status.pendingForMainCount ?? 0;
|
|
40309
|
+
const droppedForMainCount = status.droppedForMainCount ?? 0;
|
|
39285
40310
|
const lines = [
|
|
39286
40311
|
`Listener ${status.state} for agent ${status.principalId}.`,
|
|
39287
40312
|
`Provider: ${status.provider}; process: ${status.pid}; started: ${status.startedAt}.`,
|
|
@@ -39308,6 +40333,23 @@ function renderListenerStatus(status) {
|
|
|
39308
40333
|
`Pending deliveries reported by the service: ${status.pendingDeliveryCount}.`
|
|
39309
40334
|
);
|
|
39310
40335
|
}
|
|
40336
|
+
lines.push(
|
|
40337
|
+
routeMode === "split" ? `Ask route: split; bodies over ${status.deferOverChars} characters wait for this interactive session.` : routeMode === "main" ? "Ask route: main; directed asks wait for this interactive session." : "Ask route: worker (default)."
|
|
40338
|
+
);
|
|
40339
|
+
if (routeMode !== "worker") {
|
|
40340
|
+
lines.push(`Asks waiting for this session: ${pendingForMainCount}.`);
|
|
40341
|
+
lines.push(`Routed asks dropped from the overflow queue: ${droppedForMainCount}.`);
|
|
40342
|
+
if (droppedForMainCount > 0) {
|
|
40343
|
+
lines.push(
|
|
40344
|
+
"The signals remain in the inbox. Recover them with: cswarm inbox"
|
|
40345
|
+
);
|
|
40346
|
+
}
|
|
40347
|
+
if (pendingForMainCount > 0) {
|
|
40348
|
+
lines.push(
|
|
40349
|
+
`${pendingForMainCount} asks waiting for this session; they surface at your next prompt, or run cswarm hook check.`
|
|
40350
|
+
);
|
|
40351
|
+
}
|
|
40352
|
+
}
|
|
39311
40353
|
if (status.lastTerminalDeliveryFailureCount !== null && status.lastTerminalDeliveryFailureCount > 0) {
|
|
39312
40354
|
lines.push(
|
|
39313
40355
|
`The last claim reported ${status.lastTerminalDeliveryFailureCount} terminal delivery failures; they remain recorded, and the listener will keep receiving.`
|
|
@@ -39392,7 +40434,7 @@ function resolveDetachedClaudeExecutable(executable = "claude-agent-acp", pathEn
|
|
|
39392
40434
|
} catch (error) {
|
|
39393
40435
|
const code = error.code;
|
|
39394
40436
|
if (typeof code === "string") {
|
|
39395
|
-
if ((0,
|
|
40437
|
+
if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
|
|
39396
40438
|
const detail = error instanceof Error ? error.message : code;
|
|
39397
40439
|
throw new Error(
|
|
39398
40440
|
`could not use --claude-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/claude-agent-acp@0.64.2 if this path should be replaced`
|
|
@@ -39409,7 +40451,7 @@ function resolveDetachedCodexExecutable(executable = "codex-acp", pathEnv = proc
|
|
|
39409
40451
|
} catch (error) {
|
|
39410
40452
|
const code = error.code;
|
|
39411
40453
|
if (typeof code === "string") {
|
|
39412
|
-
if ((0,
|
|
40454
|
+
if ((0, import_node_path19.isAbsolute)(executable) || executable.includes("/") || executable.includes("\\")) {
|
|
39413
40455
|
const detail = error instanceof Error ? error.message : code;
|
|
39414
40456
|
throw new Error(
|
|
39415
40457
|
`could not use --codex-executable: ${detail}; reinstall the measured bridge with npm install -g @agentclientprotocol/codex-acp@1.1.9 if this path should be replaced`
|
|
@@ -39454,11 +40496,32 @@ async function runConfiguredListener(options) {
|
|
|
39454
40496
|
principalId: options.principalId,
|
|
39455
40497
|
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
39456
40498
|
});
|
|
39457
|
-
const
|
|
40499
|
+
const liveCredentialSession = await agentSession(
|
|
39458
40500
|
options.cloud,
|
|
39459
40501
|
options.workspaceId,
|
|
39460
40502
|
options.agent
|
|
39461
40503
|
);
|
|
40504
|
+
let storedCredential = null;
|
|
40505
|
+
const credentialSession = {
|
|
40506
|
+
bearer: async () => {
|
|
40507
|
+
const credential = await liveCredentialSession.bearer();
|
|
40508
|
+
if (credential !== storedCredential) {
|
|
40509
|
+
await writeListenerCredentialState(paths.instanceDirectory, {
|
|
40510
|
+
target: options.cloud,
|
|
40511
|
+
workspaceId: options.workspaceId,
|
|
40512
|
+
principalId: options.principalId,
|
|
40513
|
+
credential
|
|
40514
|
+
}).then(() => {
|
|
40515
|
+
storedCredential = credential;
|
|
40516
|
+
});
|
|
40517
|
+
}
|
|
40518
|
+
const stored = await readListenerCredentialState(paths.instanceDirectory);
|
|
40519
|
+
if (stored === null || stored.credential !== credential) {
|
|
40520
|
+
throw new Error("listener credential state did not preserve the live credential");
|
|
40521
|
+
}
|
|
40522
|
+
return stored.credential;
|
|
40523
|
+
}
|
|
40524
|
+
};
|
|
39462
40525
|
const resolveSenderProvenance = async (signal, context) => {
|
|
39463
40526
|
const credential = await credentialSession.bearer();
|
|
39464
40527
|
const senderDirectory = await readAgentSignalDirectory(
|
|
@@ -39486,7 +40549,7 @@ async function runConfiguredListener(options) {
|
|
|
39486
40549
|
}
|
|
39487
40550
|
const applied = resolveTurnBudgetOrDefer(
|
|
39488
40551
|
turnBudgetMs,
|
|
39489
|
-
|
|
40552
|
+
liveCredentialSession.expiry,
|
|
39490
40553
|
Date.now(),
|
|
39491
40554
|
renewalFailed
|
|
39492
40555
|
);
|
|
@@ -39536,6 +40599,9 @@ async function runConfiguredListener(options) {
|
|
|
39536
40599
|
};
|
|
39537
40600
|
let selectedJournal;
|
|
39538
40601
|
let selectedListenerInstanceId;
|
|
40602
|
+
const routeMode = options.routeMode ?? "worker";
|
|
40603
|
+
const deferOverChars = options.deferOverChars ?? null;
|
|
40604
|
+
const pendingMainQueue = new FilePendingMainQueue(paths.instanceDirectory);
|
|
39539
40605
|
process.on("SIGINT", onProcessSignal);
|
|
39540
40606
|
process.on("SIGTERM", onProcessSignal);
|
|
39541
40607
|
try {
|
|
@@ -39546,6 +40612,8 @@ async function runConfiguredListener(options) {
|
|
|
39546
40612
|
principalId: options.principalId,
|
|
39547
40613
|
provider: options.provider,
|
|
39548
40614
|
permissionMode: options.permissionMode,
|
|
40615
|
+
routeMode,
|
|
40616
|
+
deferOverChars,
|
|
39549
40617
|
// The bound a timeout event reports: the last turn's clamped budget when
|
|
39550
40618
|
// one has run, else the configured cap.
|
|
39551
40619
|
getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
|
|
@@ -39584,7 +40652,10 @@ async function runConfiguredListener(options) {
|
|
|
39584
40652
|
declareModel: listenerModelLabel(options.provider),
|
|
39585
40653
|
listenerInstanceId,
|
|
39586
40654
|
deliveryJournal: selectedJournal,
|
|
39587
|
-
resolveSenderProvenance
|
|
40655
|
+
resolveSenderProvenance,
|
|
40656
|
+
routeMode,
|
|
40657
|
+
deferOverChars,
|
|
40658
|
+
pendingMainQueue
|
|
39588
40659
|
});
|
|
39589
40660
|
}
|
|
39590
40661
|
});
|
|
@@ -39609,6 +40680,8 @@ async function runListenStart(args) {
|
|
|
39609
40680
|
"codex-executable",
|
|
39610
40681
|
"state-dir",
|
|
39611
40682
|
"turn-budget",
|
|
40683
|
+
"route",
|
|
40684
|
+
"defer-over",
|
|
39612
40685
|
"foreground",
|
|
39613
40686
|
"json"
|
|
39614
40687
|
], 2);
|
|
@@ -39620,6 +40693,10 @@ async function runListenStart(args) {
|
|
|
39620
40693
|
const provider = listenerProvider(args);
|
|
39621
40694
|
validateListenerProviderFlags(args, provider);
|
|
39622
40695
|
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
40696
|
+
const routing = listenerRouteConfiguration(
|
|
40697
|
+
args.optional("route"),
|
|
40698
|
+
args.optional("defer-over")
|
|
40699
|
+
);
|
|
39623
40700
|
const cloud = await target(args);
|
|
39624
40701
|
const workspaceId2 = listenerUuid(
|
|
39625
40702
|
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
@@ -39629,7 +40706,7 @@ async function runListenStart(args) {
|
|
|
39629
40706
|
assertDurableListenerCredential(agent);
|
|
39630
40707
|
const principalId = agent.principalId;
|
|
39631
40708
|
const cwd = args.optional("cwd") ?? process.cwd();
|
|
39632
|
-
if (!(0,
|
|
40709
|
+
if (!(0, import_node_path19.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
|
|
39633
40710
|
const permissionMode = listenerPermissionMode(args.optional("permissions"));
|
|
39634
40711
|
const stateDirectory2 = listenerStateDirectory(args);
|
|
39635
40712
|
const paths = listenerPaths({
|
|
@@ -39655,6 +40732,7 @@ async function runListenStart(args) {
|
|
|
39655
40732
|
permissionMode,
|
|
39656
40733
|
provider,
|
|
39657
40734
|
turnBudgetMs,
|
|
40735
|
+
...routing,
|
|
39658
40736
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39659
40737
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39660
40738
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39665,7 +40743,7 @@ async function runListenStart(args) {
|
|
|
39665
40743
|
});
|
|
39666
40744
|
} else {
|
|
39667
40745
|
const entrypoint = process.argv[1];
|
|
39668
|
-
if (!entrypoint || !(0,
|
|
40746
|
+
if (!entrypoint || !(0, import_node_path19.isAbsolute)(entrypoint)) {
|
|
39669
40747
|
throw new Error("cannot locate the cswarm executable for detached start");
|
|
39670
40748
|
}
|
|
39671
40749
|
const artifact = JSON.stringify(agentCredentialArtifact({
|
|
@@ -39702,6 +40780,8 @@ async function runListenStart(args) {
|
|
|
39702
40780
|
permissionMode,
|
|
39703
40781
|
provider,
|
|
39704
40782
|
nodeExecArgv: process.execArgv,
|
|
40783
|
+
route: routing.routeMode,
|
|
40784
|
+
...routing.deferOverChars === null ? {} : { deferOver: routing.deferOverChars },
|
|
39705
40785
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {},
|
|
39706
40786
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39707
40787
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
@@ -39735,17 +40815,36 @@ async function runListenStart(args) {
|
|
|
39735
40815
|
listenerFailureMessage(status.lastErrorCode ?? "unknown_error", provider)
|
|
39736
40816
|
);
|
|
39737
40817
|
}
|
|
40818
|
+
if ((status.routeMode ?? "worker") !== "worker") {
|
|
40819
|
+
const recordedPending = status.pendingForMainCount ?? 0;
|
|
40820
|
+
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
40821
|
+
const queueStats = await new FilePendingMainQueue(
|
|
40822
|
+
paths.instanceDirectory
|
|
40823
|
+
).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
|
|
40824
|
+
status = {
|
|
40825
|
+
...status,
|
|
40826
|
+
pendingForMainCount: queueStats.count,
|
|
40827
|
+
droppedForMainCount: queueStats.droppedCount
|
|
40828
|
+
};
|
|
40829
|
+
}
|
|
39738
40830
|
if (args.has("json")) {
|
|
39739
40831
|
printJson(listenerStatusJson(status, permissionMode));
|
|
39740
40832
|
return;
|
|
39741
40833
|
}
|
|
39742
|
-
const
|
|
40834
|
+
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.
|
|
40835
|
+
` : "";
|
|
40836
|
+
const workerAudience = routing.routeMode === "main" ? "Directed asks do not reach that worker." : routing.routeMode === "split" ? "Only asks at or below the split threshold reach that worker, with sender and operator provenance in the prompt." : "Every sender reaches that worker with sender and operator provenance in the prompt.";
|
|
40837
|
+
const hostNote = provider === "opencode" ? `The OpenCode worker uses one private auth/config home and your selected project cwd. ${workerAudience} Tool requests are approved one at a time by default, when the worker asks and the host offers a one-time approval; --permissions deny refuses them. The deny canary does not cover steady-state allow.
|
|
40838
|
+
` : provider === "claude" ? `The Claude worker uses your selected cwd and normal Claude Code keychain/OAuth state through claude-agent-acp 0.64.2. ${workerAudience}
|
|
40839
|
+
` : provider === "codex" ? `The Codex worker uses your selected cwd and normal ChatGPT/Codex auth through codex-acp 1.1.9. CommonSwarm selects read-only mode before its deny canary. ${workerAudience}
|
|
40840
|
+
` : `The Grok worker uses your selected cwd and local Grok configuration, including user and cmux hooks. ${workerAudience}
|
|
40841
|
+
`;
|
|
39743
40842
|
process.stdout.write(
|
|
39744
40843
|
`${args.has("foreground") ? "Listener stopped." : "Listener is ready and will keep receiving after this command exits."}
|
|
39745
40844
|
${renderListenerStatus(status)}
|
|
39746
40845
|
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.
|
|
39747
40846
|
The short credential rotates while this process remains alive and secure local state is available; a person reauthorises after the 30-day horizon.
|
|
39748
|
-
` + hostNote + `Use listen status/stop with --workspace-id ${workspaceId2} --principal-id ${principalId} and the same Cloud target.
|
|
40847
|
+
` + routingNote + hostNote + `Use listen status/stop with --workspace-id ${workspaceId2} --principal-id ${principalId} and the same Cloud target.
|
|
39749
40848
|
`
|
|
39750
40849
|
);
|
|
39751
40850
|
}
|
|
@@ -39764,18 +40863,24 @@ async function runListenSupervisor(args) {
|
|
|
39764
40863
|
"claude-executable",
|
|
39765
40864
|
"codex-executable",
|
|
39766
40865
|
"state-dir",
|
|
39767
|
-
"turn-budget"
|
|
40866
|
+
"turn-budget",
|
|
40867
|
+
"route",
|
|
40868
|
+
"defer-over"
|
|
39768
40869
|
], 1);
|
|
39769
40870
|
const provider = listenerProvider(args);
|
|
39770
40871
|
validateListenerProviderFlags(args, provider);
|
|
39771
40872
|
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
40873
|
+
const routing = listenerRouteConfiguration(
|
|
40874
|
+
args.optional("route"),
|
|
40875
|
+
args.optional("defer-over")
|
|
40876
|
+
);
|
|
39772
40877
|
const cloud = await target(args);
|
|
39773
40878
|
const workspaceId2 = listenerUuid(args.optional("workspace-id"), "workspace-id");
|
|
39774
40879
|
const principalId = listenerUuid(args.optional("principal-id"), "principal-id");
|
|
39775
40880
|
const agent = await stdinCredential();
|
|
39776
40881
|
assertDurableListenerCredential(agent, principalId);
|
|
39777
40882
|
const cwd = args.required("cwd");
|
|
39778
|
-
if (!(0,
|
|
40883
|
+
if (!(0, import_node_path19.isAbsolute)(cwd)) throw new Error("--cwd must be an absolute path");
|
|
39779
40884
|
const status = await runConfiguredListener({
|
|
39780
40885
|
cloud,
|
|
39781
40886
|
workspaceId: workspaceId2,
|
|
@@ -39785,6 +40890,7 @@ async function runListenSupervisor(args) {
|
|
|
39785
40890
|
permissionMode: listenerPermissionMode(args.optional("permissions")),
|
|
39786
40891
|
provider,
|
|
39787
40892
|
turnBudgetMs,
|
|
40893
|
+
...routing,
|
|
39788
40894
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39789
40895
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39790
40896
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39817,7 +40923,7 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
39817
40923
|
principalId,
|
|
39818
40924
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {}
|
|
39819
40925
|
});
|
|
39820
|
-
|
|
40926
|
+
let status = command2 === "stop" ? await stopListener(paths) : await effectiveListenerStatus(paths);
|
|
39821
40927
|
if (status === null) {
|
|
39822
40928
|
if (args.has("json")) {
|
|
39823
40929
|
printJson({ status: "not_found", workspace_id: workspaceId2, principal_id: principalId });
|
|
@@ -39826,6 +40932,18 @@ async function runListenStatusOrStop(args, command2) {
|
|
|
39826
40932
|
}
|
|
39827
40933
|
return;
|
|
39828
40934
|
}
|
|
40935
|
+
if ((status.routeMode ?? "worker") !== "worker") {
|
|
40936
|
+
const recordedPending = status.pendingForMainCount ?? 0;
|
|
40937
|
+
const recordedDropped = status.droppedForMainCount ?? 0;
|
|
40938
|
+
const queueStats = await new FilePendingMainQueue(
|
|
40939
|
+
paths.instanceDirectory
|
|
40940
|
+
).stats().catch(() => ({ count: recordedPending, droppedCount: recordedDropped }));
|
|
40941
|
+
status = {
|
|
40942
|
+
...status,
|
|
40943
|
+
pendingForMainCount: queueStats.count,
|
|
40944
|
+
droppedForMainCount: queueStats.droppedCount
|
|
40945
|
+
};
|
|
40946
|
+
}
|
|
39829
40947
|
if (args.has("json")) {
|
|
39830
40948
|
printJson(listenerStatusJson(status));
|
|
39831
40949
|
} else {
|
|
@@ -39845,6 +40963,148 @@ async function runListen(args) {
|
|
|
39845
40963
|
}
|
|
39846
40964
|
throw new UsageError("listen requires start, status, or stop");
|
|
39847
40965
|
}
|
|
40966
|
+
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
40967
|
+
function claudeUserPromptHookSnippet() {
|
|
40968
|
+
return {
|
|
40969
|
+
hooks: {
|
|
40970
|
+
UserPromptSubmit: [
|
|
40971
|
+
{
|
|
40972
|
+
hooks: [
|
|
40973
|
+
{
|
|
40974
|
+
type: "command",
|
|
40975
|
+
command: CLAUDE_HOOK_COMMAND
|
|
40976
|
+
}
|
|
40977
|
+
]
|
|
40978
|
+
}
|
|
40979
|
+
]
|
|
40980
|
+
}
|
|
40981
|
+
};
|
|
40982
|
+
}
|
|
40983
|
+
function projectClaudeSettingsPath() {
|
|
40984
|
+
return (0, import_node_path19.join)(process.cwd(), ".claude", "settings.json");
|
|
40985
|
+
}
|
|
40986
|
+
function readProjectSettings(path) {
|
|
40987
|
+
let raw;
|
|
40988
|
+
try {
|
|
40989
|
+
raw = (0, import_node_fs7.readFileSync)(path, "utf8");
|
|
40990
|
+
} catch (error) {
|
|
40991
|
+
if (error.code === "ENOENT") return {};
|
|
40992
|
+
throw error;
|
|
40993
|
+
}
|
|
40994
|
+
const value = JSON.parse(raw);
|
|
40995
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
40996
|
+
throw new Error("local .claude/settings.json must contain a JSON object");
|
|
40997
|
+
}
|
|
40998
|
+
return value;
|
|
40999
|
+
}
|
|
41000
|
+
function installClaudeHook(settings) {
|
|
41001
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? { ...settings.hooks } : {};
|
|
41002
|
+
const current = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
|
|
41003
|
+
const alreadyInstalled = current.some((group) => {
|
|
41004
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) return false;
|
|
41005
|
+
const commands = group.hooks;
|
|
41006
|
+
return Array.isArray(commands) && commands.some(
|
|
41007
|
+
(hook) => hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === CLAUDE_HOOK_COMMAND
|
|
41008
|
+
);
|
|
41009
|
+
});
|
|
41010
|
+
if (!alreadyInstalled) {
|
|
41011
|
+
const snippetHooks = claudeUserPromptHookSnippet().hooks.UserPromptSubmit;
|
|
41012
|
+
current.push(snippetHooks[0]);
|
|
41013
|
+
}
|
|
41014
|
+
hooks.UserPromptSubmit = current;
|
|
41015
|
+
return { ...settings, hooks };
|
|
41016
|
+
}
|
|
41017
|
+
function uninstallClaudeHook(settings) {
|
|
41018
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
|
|
41019
|
+
return settings;
|
|
41020
|
+
}
|
|
41021
|
+
const hooks = { ...settings.hooks };
|
|
41022
|
+
if (!Array.isArray(hooks.UserPromptSubmit)) return settings;
|
|
41023
|
+
const groups = [];
|
|
41024
|
+
for (const group of hooks.UserPromptSubmit) {
|
|
41025
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) {
|
|
41026
|
+
groups.push(group);
|
|
41027
|
+
continue;
|
|
41028
|
+
}
|
|
41029
|
+
const row = { ...group };
|
|
41030
|
+
if (!Array.isArray(row.hooks)) {
|
|
41031
|
+
groups.push(group);
|
|
41032
|
+
continue;
|
|
41033
|
+
}
|
|
41034
|
+
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === CLAUDE_HOOK_COMMAND));
|
|
41035
|
+
if (row.hooks.length > 0) groups.push(row);
|
|
41036
|
+
}
|
|
41037
|
+
if (groups.length > 0) hooks.UserPromptSubmit = groups;
|
|
41038
|
+
else delete hooks.UserPromptSubmit;
|
|
41039
|
+
if (Object.keys(hooks).length === 0) {
|
|
41040
|
+
const result = { ...settings };
|
|
41041
|
+
delete result.hooks;
|
|
41042
|
+
return result;
|
|
41043
|
+
}
|
|
41044
|
+
return { ...settings, hooks };
|
|
41045
|
+
}
|
|
41046
|
+
async function runHook(args) {
|
|
41047
|
+
const command2 = args.positionals[1];
|
|
41048
|
+
if (command2 === "check") {
|
|
41049
|
+
args.assertShape(["cooldown"], 2);
|
|
41050
|
+
const rawCooldown = args.optional("cooldown");
|
|
41051
|
+
const cooldownSeconds = rawCooldown === void 0 ? void 0 : Number(rawCooldown);
|
|
41052
|
+
if (cooldownSeconds !== void 0 && (!/^\d+$/.test(rawCooldown) || !Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400)) {
|
|
41053
|
+
return;
|
|
41054
|
+
}
|
|
41055
|
+
const hardExit = setTimeout(() => {
|
|
41056
|
+
process.exit(0);
|
|
41057
|
+
}, 3e3);
|
|
41058
|
+
hardExit.unref();
|
|
41059
|
+
try {
|
|
41060
|
+
await runListenerHookCheck({
|
|
41061
|
+
...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
|
|
41062
|
+
write: async (output) => {
|
|
41063
|
+
await new Promise((resolve, reject) => {
|
|
41064
|
+
process.stdout.write(`${output}
|
|
41065
|
+
`, (error) => {
|
|
41066
|
+
if (error) reject(error);
|
|
41067
|
+
else resolve();
|
|
41068
|
+
});
|
|
41069
|
+
});
|
|
41070
|
+
}
|
|
41071
|
+
});
|
|
41072
|
+
return;
|
|
41073
|
+
} finally {
|
|
41074
|
+
clearTimeout(hardExit);
|
|
41075
|
+
}
|
|
41076
|
+
}
|
|
41077
|
+
if (command2 !== "install" && command2 !== "uninstall") {
|
|
41078
|
+
throw new UsageError("hook requires check, install, or uninstall");
|
|
41079
|
+
}
|
|
41080
|
+
args.assertShape(["write"], 3);
|
|
41081
|
+
if (args.positionals[2] !== "claude") {
|
|
41082
|
+
throw new Error("hook install/uninstall currently supports claude");
|
|
41083
|
+
}
|
|
41084
|
+
if (command2 === "uninstall" && !args.has("write")) {
|
|
41085
|
+
throw new Error("hook uninstall claude requires --write");
|
|
41086
|
+
}
|
|
41087
|
+
const snippet = claudeUserPromptHookSnippet();
|
|
41088
|
+
if (!args.has("write")) {
|
|
41089
|
+
process.stdout.write(`${JSON.stringify(snippet, null, 2)}
|
|
41090
|
+
`);
|
|
41091
|
+
return;
|
|
41092
|
+
}
|
|
41093
|
+
const path = projectClaudeSettingsPath();
|
|
41094
|
+
const settings = readProjectSettings(path);
|
|
41095
|
+
const updated = command2 === "install" ? installClaudeHook(settings) : uninstallClaudeHook(settings);
|
|
41096
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path19.dirname)(path), { recursive: true });
|
|
41097
|
+
(0, import_node_fs7.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
|
|
41098
|
+
`, {
|
|
41099
|
+
encoding: "utf8",
|
|
41100
|
+
mode: 384
|
|
41101
|
+
});
|
|
41102
|
+
process.stdout.write(
|
|
41103
|
+
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${CLAUDE_HOOK_COMMAND}
|
|
41104
|
+
` : `Removed the CommonSwarm UserPromptSubmit hook from ${path}. Other settings were kept.
|
|
41105
|
+
`
|
|
41106
|
+
);
|
|
41107
|
+
}
|
|
39848
41108
|
function formatFileSize(value) {
|
|
39849
41109
|
const bytes = Number(value ?? 0);
|
|
39850
41110
|
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
|
@@ -39879,7 +41139,7 @@ async function fileRows(context) {
|
|
|
39879
41139
|
);
|
|
39880
41140
|
}
|
|
39881
41141
|
async function resolveFileSelector(context, selector) {
|
|
39882
|
-
if (
|
|
41142
|
+
if (UUID_RE17.test(selector)) return selector.toLowerCase();
|
|
39883
41143
|
const rows3 = await fileRows(context);
|
|
39884
41144
|
const match = rows3.find(
|
|
39885
41145
|
(row) => row.name.toLowerCase() === selector.toLowerCase()
|
|
@@ -39901,7 +41161,7 @@ async function runFilePut(args) {
|
|
|
39901
41161
|
} catch {
|
|
39902
41162
|
throw new Error(`could not read ${localPath}; check the path and permissions`);
|
|
39903
41163
|
}
|
|
39904
|
-
const name = args.optional("name") ?? (0,
|
|
41164
|
+
const name = args.optional("name") ?? (0, import_node_path19.basename)(localPath);
|
|
39905
41165
|
if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
|
|
39906
41166
|
throw new Error(
|
|
39907
41167
|
`this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
|
|
@@ -40003,7 +41263,7 @@ async function runFileGet(args) {
|
|
|
40003
41263
|
credential: context.selected.bearer
|
|
40004
41264
|
};
|
|
40005
41265
|
const grant = await fileDownloadUrl(send, { fileId, versionN });
|
|
40006
|
-
const destination = args.optional("out") ?? (0,
|
|
41266
|
+
const destination = args.optional("out") ?? (0, import_node_path19.basename)(grant.name);
|
|
40007
41267
|
const bytes = await getObject(context.cloud, grant.download_path);
|
|
40008
41268
|
writeDestination(destination, bytes, args.has("force"), import_node_fs7.writeFileSync);
|
|
40009
41269
|
if (args.has("json")) {
|
|
@@ -40219,10 +41479,10 @@ async function runSeed(args) {
|
|
|
40219
41479
|
throw new Error("DATABASE_URL is required for the fixture bridge");
|
|
40220
41480
|
}
|
|
40221
41481
|
const tokenOut = process.env.SEED_TOKEN_OUT;
|
|
40222
|
-
if (!tokenOut || !(0,
|
|
41482
|
+
if (!tokenOut || !(0, import_node_path19.isAbsolute)(tokenOut)) {
|
|
40223
41483
|
throw new Error("SEED_TOKEN_OUT must be an absolute path");
|
|
40224
41484
|
}
|
|
40225
|
-
const tokenFile = await (0,
|
|
41485
|
+
const tokenFile = await (0, import_promises11.open)(tokenOut, "wx", 384).catch((error) => {
|
|
40226
41486
|
if (error.code === "EEXIST") {
|
|
40227
41487
|
throw new Error("SEED_TOKEN_OUT already exists; refusing to overwrite it");
|
|
40228
41488
|
}
|
|
@@ -40261,7 +41521,7 @@ async function runSeed(args) {
|
|
|
40261
41521
|
tokenWritten = true;
|
|
40262
41522
|
}
|
|
40263
41523
|
await tokenFile.close();
|
|
40264
|
-
if (!tokenWritten) await (0,
|
|
41524
|
+
if (!tokenWritten) await (0, import_promises11.unlink)(tokenOut);
|
|
40265
41525
|
process.stdout.write(`${JSON.stringify({
|
|
40266
41526
|
userId: result.userId,
|
|
40267
41527
|
membershipRole: result.membershipRole,
|
|
@@ -40274,7 +41534,7 @@ async function runSeed(args) {
|
|
|
40274
41534
|
`);
|
|
40275
41535
|
} catch (error) {
|
|
40276
41536
|
await tokenFile.close().catch(() => void 0);
|
|
40277
|
-
if (!tokenWritten) await (0,
|
|
41537
|
+
if (!tokenWritten) await (0, import_promises11.unlink)(tokenOut).catch(() => void 0);
|
|
40278
41538
|
throw error;
|
|
40279
41539
|
}
|
|
40280
41540
|
}
|
|
@@ -40299,6 +41559,10 @@ async function main() {
|
|
|
40299
41559
|
await runListenSupervisor(args);
|
|
40300
41560
|
return;
|
|
40301
41561
|
}
|
|
41562
|
+
if (verb === "hook") {
|
|
41563
|
+
await runHook(args);
|
|
41564
|
+
return;
|
|
41565
|
+
}
|
|
40302
41566
|
if (verb === "listen") {
|
|
40303
41567
|
await runListen(args);
|
|
40304
41568
|
return;
|
|
@@ -40355,6 +41619,10 @@ async function main() {
|
|
|
40355
41619
|
await runMember(args);
|
|
40356
41620
|
return;
|
|
40357
41621
|
}
|
|
41622
|
+
if (verb === "workspace") {
|
|
41623
|
+
await runWorkspace(args);
|
|
41624
|
+
return;
|
|
41625
|
+
}
|
|
40358
41626
|
if (verb === "target") {
|
|
40359
41627
|
await runTarget(args);
|
|
40360
41628
|
return;
|
|
@@ -40446,6 +41714,10 @@ function safeParagraph(message) {
|
|
|
40446
41714
|
return message.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, " ").slice(0, 2e3);
|
|
40447
41715
|
}
|
|
40448
41716
|
main().catch((error) => {
|
|
41717
|
+
if (process.argv[2] === "hook" && process.argv[3] === "check") {
|
|
41718
|
+
process.exitCode = 0;
|
|
41719
|
+
return;
|
|
41720
|
+
}
|
|
40449
41721
|
if (error instanceof RenewalReauthorisationRequired || error instanceof RenewalRevoked) {
|
|
40450
41722
|
process.stderr.write(`${safeParagraph(error.message)}
|
|
40451
41723
|
`);
|
|
@@ -40494,12 +41766,16 @@ ${usage()}
|
|
|
40494
41766
|
EXIT_RESTARTABLE,
|
|
40495
41767
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
40496
41768
|
clampTurnBudgetToCredential,
|
|
41769
|
+
claudeUserPromptHookSnippet,
|
|
40497
41770
|
describeAudience,
|
|
40498
41771
|
listenerFailureMessage,
|
|
40499
41772
|
listenerHostLimits,
|
|
40500
41773
|
listenerPermissionMode,
|
|
41774
|
+
listenerRouteConfiguration,
|
|
40501
41775
|
listenerStatusJson,
|
|
41776
|
+
renderListenerStatus,
|
|
40502
41777
|
renderRoster,
|
|
41778
|
+
replyRefusalHint,
|
|
40503
41779
|
resolveDetachedClaudeExecutable,
|
|
40504
41780
|
resolveDetachedCodexExecutable,
|
|
40505
41781
|
resolveTurnBudgetOrDefer
|