commonswarm 0.1.21 → 0.1.23
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 +578 -74
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -13503,14 +13503,18 @@ var require_main3 = __commonJS({
|
|
|
13503
13503
|
var cli_exports = {};
|
|
13504
13504
|
__export(cli_exports, {
|
|
13505
13505
|
EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
|
|
13506
|
+
TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
13507
|
+
clampTurnBudgetToCredential: () => clampTurnBudgetToCredential,
|
|
13506
13508
|
describeAudience: () => describeAudience,
|
|
13507
13509
|
listenerFailureMessage: () => listenerFailureMessage,
|
|
13508
13510
|
listenerHostLimits: () => listenerHostLimits,
|
|
13509
13511
|
listenerPermissionMode: () => listenerPermissionMode,
|
|
13510
13512
|
listenerStatusJson: () => listenerStatusJson,
|
|
13511
13513
|
renderRoster: () => renderRoster,
|
|
13514
|
+
replyRefusalHint: () => replyRefusalHint,
|
|
13512
13515
|
resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
|
|
13513
|
-
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable
|
|
13516
|
+
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable,
|
|
13517
|
+
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer
|
|
13514
13518
|
});
|
|
13515
13519
|
module.exports = __toCommonJS(cli_exports);
|
|
13516
13520
|
var import_node_crypto19 = require("node:crypto");
|
|
@@ -22159,15 +22163,8 @@ function createWorkspaceError(status, body) {
|
|
|
22159
22163
|
return new CreateWorkspaceError(
|
|
22160
22164
|
status,
|
|
22161
22165
|
code,
|
|
22162
|
-
/*
|
|
22163
|
-
|
|
22164
|
-
* is unreachable from every surface — `archived_at` exists and nothing writes it — and on a
|
|
22165
|
-
* self-serve deployment the reader IS the operator, so it named a person who does not exist
|
|
22166
|
-
* to perform an action that does not exist.
|
|
22167
|
-
*
|
|
22168
|
-
* It now states the limit, does not offer a remedy that is unimplemented, and names the one
|
|
22169
|
-
* route that does work: someone else's invitation, which is not capped. */
|
|
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.`} 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.`
|
|
22166
|
+
/* Closing one live workspace now frees a slot; name the exact confirmed command. */
|
|
22167
|
+
`${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.`
|
|
22171
22168
|
);
|
|
22172
22169
|
}
|
|
22173
22170
|
if (status === 403) {
|
|
@@ -22794,6 +22791,12 @@ var CONTENT_TYPES = /* @__PURE__ */ new Map([
|
|
|
22794
22791
|
[".md", "text/markdown"],
|
|
22795
22792
|
[".txt", "text/plain"],
|
|
22796
22793
|
[".csv", "text/csv"],
|
|
22794
|
+
// .html/.htm: a web/marketing team's deliverables (Fastio feedback 2026-08-19). Every
|
|
22795
|
+
// download is served Content-Disposition: attachment (§5), never rendered inline, so HTML
|
|
22796
|
+
// is no more dangerous than the .svg already permitted — the spec treats all downloads as
|
|
22797
|
+
// untrusted attachments the consumer must not execute.
|
|
22798
|
+
[".html", "text/html"],
|
|
22799
|
+
[".htm", "text/html"],
|
|
22797
22800
|
[".json", "application/json"],
|
|
22798
22801
|
[".yaml", "application/yaml"],
|
|
22799
22802
|
[".yml", "application/yaml"],
|
|
@@ -23058,6 +23061,67 @@ async function listFilesAsHuman(target2, accessToken, workspaceId2, fetcher = fe
|
|
|
23058
23061
|
return body;
|
|
23059
23062
|
}
|
|
23060
23063
|
|
|
23064
|
+
// src/cloud/feedback.ts
|
|
23065
|
+
var FeedbackTransportError = class extends Error {
|
|
23066
|
+
name = "FeedbackTransportError";
|
|
23067
|
+
};
|
|
23068
|
+
var FeedbackRefusedError = class extends Error {
|
|
23069
|
+
constructor(code, message) {
|
|
23070
|
+
super(message);
|
|
23071
|
+
this.code = code;
|
|
23072
|
+
}
|
|
23073
|
+
code;
|
|
23074
|
+
name = "FeedbackRefusedError";
|
|
23075
|
+
};
|
|
23076
|
+
var REQUEST_TIMEOUT_MS2 = 3e4;
|
|
23077
|
+
async function submitFeedback(options, request) {
|
|
23078
|
+
const fetcher = options.fetcher ?? fetch;
|
|
23079
|
+
const controller = new AbortController();
|
|
23080
|
+
const timer2 = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
23081
|
+
let response;
|
|
23082
|
+
try {
|
|
23083
|
+
response = await fetcher(commandEndpoint(options.target), {
|
|
23084
|
+
method: "POST",
|
|
23085
|
+
headers: {
|
|
23086
|
+
authorization: `Bearer ${options.credential}`,
|
|
23087
|
+
apikey: options.target.anonKey,
|
|
23088
|
+
"content-type": "application/json"
|
|
23089
|
+
},
|
|
23090
|
+
body: JSON.stringify({
|
|
23091
|
+
command_id: newCommandId(),
|
|
23092
|
+
client_version: "0.1.0",
|
|
23093
|
+
workspace_id: options.workspaceId,
|
|
23094
|
+
stream: { kind: "workspace" },
|
|
23095
|
+
command: {
|
|
23096
|
+
kind: "submit_feedback",
|
|
23097
|
+
feedback_id: crypto.randomUUID(),
|
|
23098
|
+
category: request.category,
|
|
23099
|
+
body: request.body,
|
|
23100
|
+
context: request.context ?? null
|
|
23101
|
+
}
|
|
23102
|
+
}),
|
|
23103
|
+
signal: controller.signal
|
|
23104
|
+
});
|
|
23105
|
+
} catch (error) {
|
|
23106
|
+
if (error.name === "AbortError") {
|
|
23107
|
+
throw new FeedbackTransportError("feedback submission timed out");
|
|
23108
|
+
}
|
|
23109
|
+
throw new FeedbackTransportError("feedback submission failed before a response");
|
|
23110
|
+
} finally {
|
|
23111
|
+
clearTimeout(timer2);
|
|
23112
|
+
}
|
|
23113
|
+
const body = await response.json().catch(() => null);
|
|
23114
|
+
if (!response.ok) {
|
|
23115
|
+
const code = typeof body?.error === "string" ? body.error : "http_error";
|
|
23116
|
+
const message = typeof body?.message === "string" ? body.message : `feedback submission was refused (HTTP ${response.status})`;
|
|
23117
|
+
throw new FeedbackRefusedError(code, message);
|
|
23118
|
+
}
|
|
23119
|
+
if (body === null || typeof body.status !== "string") {
|
|
23120
|
+
throw new FeedbackTransportError("the deployment answered without a readable result");
|
|
23121
|
+
}
|
|
23122
|
+
return body;
|
|
23123
|
+
}
|
|
23124
|
+
|
|
23061
23125
|
// src/cloud/current-target.ts
|
|
23062
23126
|
var import_node_crypto6 = require("node:crypto");
|
|
23063
23127
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -27666,6 +27730,7 @@ var DEFAULT_MEMBERSHIP_REVOKED = {
|
|
|
27666
27730
|
message: "Your previously selected workspace is no longer available to this account. CommonSwarm cleared that saved selection."
|
|
27667
27731
|
};
|
|
27668
27732
|
var PROJECT_NOT_AVAILABLE = "That workspace is not available to this account. Run cswarm workspaces to see workspaces you can select.";
|
|
27733
|
+
var ARCHIVED_PROJECT_NOT_AVAILABLE = "That workspace is closed and cannot be selected. Run cswarm workspaces to see live workspaces you can select.";
|
|
27669
27734
|
function compareText(left, right) {
|
|
27670
27735
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
27671
27736
|
}
|
|
@@ -27703,8 +27768,8 @@ var WorkspaceResolutionError = class extends WorkspaceCliError {
|
|
|
27703
27768
|
};
|
|
27704
27769
|
var WorkspaceUnavailableError = class extends WorkspaceCliError {
|
|
27705
27770
|
code = "project_not_available";
|
|
27706
|
-
constructor() {
|
|
27707
|
-
super(
|
|
27771
|
+
constructor(message = PROJECT_NOT_AVAILABLE) {
|
|
27772
|
+
super(message);
|
|
27708
27773
|
this.name = "WorkspaceUnavailableError";
|
|
27709
27774
|
}
|
|
27710
27775
|
structured() {
|
|
@@ -27808,41 +27873,41 @@ function cloudWorkspaceDirectory(target2, fetcher = fetch) {
|
|
|
27808
27873
|
"workspaces",
|
|
27809
27874
|
{
|
|
27810
27875
|
select: "workspace_id,name,archived_at",
|
|
27876
|
+
archived_at: "is.null",
|
|
27811
27877
|
order: "workspace_id.asc"
|
|
27812
27878
|
},
|
|
27813
27879
|
fetcher
|
|
27814
27880
|
)
|
|
27815
27881
|
]);
|
|
27816
|
-
const
|
|
27882
|
+
const roles = /* @__PURE__ */ new Map();
|
|
27883
|
+
for (const row of membershipRows) {
|
|
27884
|
+
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27885
|
+
roles.set(workspaceId2, checkedRole(row.role));
|
|
27886
|
+
}
|
|
27887
|
+
const result = [];
|
|
27817
27888
|
for (const row of workspaceRows) {
|
|
27818
27889
|
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27819
27890
|
const archivedAt = checkedNullableTimestamp(
|
|
27820
27891
|
row.archived_at,
|
|
27821
27892
|
"archived_at"
|
|
27822
27893
|
);
|
|
27823
|
-
|
|
27894
|
+
if (archivedAt !== null) continue;
|
|
27895
|
+
const role = roles.get(workspaceId2);
|
|
27896
|
+
if (!role) {
|
|
27897
|
+
throw new Error(
|
|
27898
|
+
"workspace read omitted the current user's live membership"
|
|
27899
|
+
);
|
|
27900
|
+
}
|
|
27901
|
+
result.push({
|
|
27902
|
+
workspace_id: workspaceId2,
|
|
27824
27903
|
name: sanitizeDisplayLabel(
|
|
27825
27904
|
checkedString(row.name, "workspace name"),
|
|
27826
27905
|
"Unnamed workspace"
|
|
27827
27906
|
),
|
|
27828
|
-
|
|
27907
|
+
role,
|
|
27908
|
+
archived: false
|
|
27829
27909
|
});
|
|
27830
27910
|
}
|
|
27831
|
-
const result = membershipRows.map((row) => {
|
|
27832
|
-
const workspaceId2 = checkedUuid(row.workspace_id, "workspace_id");
|
|
27833
|
-
const project = names.get(workspaceId2);
|
|
27834
|
-
if (!project) {
|
|
27835
|
-
throw new Error(
|
|
27836
|
-
"workspace read omitted a workspace for a live membership"
|
|
27837
|
-
);
|
|
27838
|
-
}
|
|
27839
|
-
return {
|
|
27840
|
-
workspace_id: workspaceId2,
|
|
27841
|
-
name: project.name,
|
|
27842
|
-
role: checkedRole(row.role),
|
|
27843
|
-
archived: project.archived
|
|
27844
|
-
};
|
|
27845
|
-
});
|
|
27846
27911
|
return sortWorkspaces(result);
|
|
27847
27912
|
},
|
|
27848
27913
|
async status(session, workspaceId2) {
|
|
@@ -27897,6 +27962,9 @@ function cloudWorkspaceDirectory(target2, fetcher = fetch) {
|
|
|
27897
27962
|
you: userId === session.userId
|
|
27898
27963
|
};
|
|
27899
27964
|
});
|
|
27965
|
+
if (!members.some((member) => member.you)) {
|
|
27966
|
+
throw new WorkspaceUnavailableError();
|
|
27967
|
+
}
|
|
27900
27968
|
const memberNames = new Map(
|
|
27901
27969
|
members.map((member) => [member.user_id, member.name])
|
|
27902
27970
|
);
|
|
@@ -28022,6 +28090,32 @@ async function clearWorkspaceDefault(store2, userId, expectedWorkspaceId) {
|
|
|
28022
28090
|
return true;
|
|
28023
28091
|
});
|
|
28024
28092
|
}
|
|
28093
|
+
async function updateWorkspaceDefaultAfterClose(store2, userId, closedWorkspaceId, workspaces) {
|
|
28094
|
+
return await store2.withLock(async () => {
|
|
28095
|
+
const current = await store2.readProfile();
|
|
28096
|
+
if (current.userId !== userId || current.workspaceId !== closedWorkspaceId) {
|
|
28097
|
+
return {
|
|
28098
|
+
closedWasSelected: false,
|
|
28099
|
+
nextWorkspace: null,
|
|
28100
|
+
selectedWorkspaceId: current.userId === userId ? current.workspaceId : null
|
|
28101
|
+
};
|
|
28102
|
+
}
|
|
28103
|
+
const nextWorkspace = sortWorkspaces(workspaces).find(
|
|
28104
|
+
(workspace) => workspace.workspace_id !== closedWorkspaceId && !workspace.archived
|
|
28105
|
+
) ?? null;
|
|
28106
|
+
await store2.writeProfile({
|
|
28107
|
+
...current,
|
|
28108
|
+
workspaceId: nextWorkspace?.workspace_id ?? null,
|
|
28109
|
+
principalId: null,
|
|
28110
|
+
principalName: null
|
|
28111
|
+
});
|
|
28112
|
+
return {
|
|
28113
|
+
closedWasSelected: true,
|
|
28114
|
+
nextWorkspace,
|
|
28115
|
+
selectedWorkspaceId: nextWorkspace?.workspace_id ?? null
|
|
28116
|
+
};
|
|
28117
|
+
});
|
|
28118
|
+
}
|
|
28025
28119
|
function workspaceOverride(explicit, environmental) {
|
|
28026
28120
|
if (explicit !== void 0) {
|
|
28027
28121
|
if (!UUID_RE6.test(explicit)) {
|
|
@@ -28080,6 +28174,14 @@ async function resolveWorkspace(options) {
|
|
|
28080
28174
|
throw new WorkspaceResolutionError(workspaces);
|
|
28081
28175
|
}
|
|
28082
28176
|
async function selectWorkspace(selector, workspaces, store2, userId) {
|
|
28177
|
+
const selected = resolveWorkspaceSelector(selector, workspaces);
|
|
28178
|
+
if (selected.archived) {
|
|
28179
|
+
throw new WorkspaceUnavailableError(ARCHIVED_PROJECT_NOT_AVAILABLE);
|
|
28180
|
+
}
|
|
28181
|
+
await writeWorkspaceDefault(store2, userId, selected.workspace_id);
|
|
28182
|
+
return selected;
|
|
28183
|
+
}
|
|
28184
|
+
function resolveWorkspaceSelector(selector, workspaces) {
|
|
28083
28185
|
const sorted = sortWorkspaces(workspaces);
|
|
28084
28186
|
let selected;
|
|
28085
28187
|
if (UUID_RE6.test(selector)) {
|
|
@@ -28098,7 +28200,6 @@ async function selectWorkspace(selector, workspaces, store2, userId) {
|
|
|
28098
28200
|
selected = matches[0];
|
|
28099
28201
|
}
|
|
28100
28202
|
if (!selected) throw new WorkspaceUnavailableError();
|
|
28101
|
-
await writeWorkspaceDefault(store2, userId, selected.workspace_id);
|
|
28102
28203
|
return selected;
|
|
28103
28204
|
}
|
|
28104
28205
|
function holderLabel(holder) {
|
|
@@ -28117,13 +28218,8 @@ function relativeExpiry(expiry, now = Date.now()) {
|
|
|
28117
28218
|
const amount = relativeMagnitude(remaining);
|
|
28118
28219
|
return remaining >= 0 ? `expires in ${amount}` : `expired ${amount} ago`;
|
|
28119
28220
|
}
|
|
28120
|
-
var ARCHIVE_NOT_ENFORCED_CODE = "workspace_archive_not_enforced";
|
|
28121
|
-
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.";
|
|
28122
28221
|
function archiveKnownGaps() {
|
|
28123
|
-
return [
|
|
28124
|
-
code: ARCHIVE_NOT_ENFORCED_CODE,
|
|
28125
|
-
message: ARCHIVE_NOT_ENFORCED_MESSAGE
|
|
28126
|
-
}];
|
|
28222
|
+
return [];
|
|
28127
28223
|
}
|
|
28128
28224
|
function renderWorkspaces(workspaces, currentWorkspaceId) {
|
|
28129
28225
|
if (workspaces.length === 0) {
|
|
@@ -28147,9 +28243,6 @@ function renderWorkspaces(workspaces, currentWorkspaceId) {
|
|
|
28147
28243
|
"No workspace is selected. Run cswarm use <full-id|exact-name>."
|
|
28148
28244
|
);
|
|
28149
28245
|
}
|
|
28150
|
-
if (workspaces.some((workspace) => workspace.archived)) {
|
|
28151
|
-
lines.push(ARCHIVE_NOT_ENFORCED_MESSAGE);
|
|
28152
|
-
}
|
|
28153
28246
|
return lines.join("\n");
|
|
28154
28247
|
}
|
|
28155
28248
|
function renderStatus(options) {
|
|
@@ -29373,6 +29466,59 @@ async function runInboxFollow(options) {
|
|
|
29373
29466
|
// src/host/opencode.ts
|
|
29374
29467
|
var import_node_child_process3 = require("node:child_process");
|
|
29375
29468
|
var import_node_crypto12 = require("node:crypto");
|
|
29469
|
+
|
|
29470
|
+
// src/host/stderr-tail.ts
|
|
29471
|
+
var RING_CAPACITY_BYTES = 4096;
|
|
29472
|
+
var TAIL_MAX_CHARS = 2048;
|
|
29473
|
+
var EXOTIC_SEPARATORS = "\\u00a0\\u1680\\u2000-\\u200d\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\u202f\\u205f\\u3000\\ufeff";
|
|
29474
|
+
var SEPARATOR_CLASS_SOURCE = "\\t\\n\\x0b\\f\\r " + EXOTIC_SEPARATORS;
|
|
29475
|
+
var ANSI_ESCAPE_GLOBAL_RE2 = new RegExp("\\u001b\\[[0-?]*[ -\\/]*[@-~]", "g");
|
|
29476
|
+
var CONTROL_AND_SEPARATOR_STRIP_RE = new RegExp(
|
|
29477
|
+
"[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f" + EXOTIC_SEPARATORS + "]",
|
|
29478
|
+
"g"
|
|
29479
|
+
);
|
|
29480
|
+
var CREDENTIAL_PREFIX_RE = new RegExp(
|
|
29481
|
+
`swm_(?:agt|inv|cap)_[^${SEPARATOR_CLASS_SOURCE}]*`,
|
|
29482
|
+
"gi"
|
|
29483
|
+
);
|
|
29484
|
+
function sanitizeStderrTail(raw) {
|
|
29485
|
+
return raw.replace(ANSI_ESCAPE_GLOBAL_RE2, "").replace(CONTROL_AND_SEPARATOR_STRIP_RE, "").replace(CREDENTIAL_PREFIX_RE, "[redacted-credential]").slice(-TAIL_MAX_CHARS).trim();
|
|
29486
|
+
}
|
|
29487
|
+
function attachStderrTailRing(stderr) {
|
|
29488
|
+
const chunks = [];
|
|
29489
|
+
let total = 0;
|
|
29490
|
+
let evicted = false;
|
|
29491
|
+
stderr.on("data", (chunk) => {
|
|
29492
|
+
const buffer2 = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
29493
|
+
chunks.push(buffer2);
|
|
29494
|
+
total += buffer2.length;
|
|
29495
|
+
while (total > RING_CAPACITY_BYTES && chunks.length > 0) {
|
|
29496
|
+
evicted = true;
|
|
29497
|
+
const head2 = chunks[0];
|
|
29498
|
+
const excess = total - RING_CAPACITY_BYTES;
|
|
29499
|
+
if (head2.length <= excess) {
|
|
29500
|
+
chunks.shift();
|
|
29501
|
+
total -= head2.length;
|
|
29502
|
+
} else {
|
|
29503
|
+
chunks[0] = head2.subarray(excess);
|
|
29504
|
+
total -= excess;
|
|
29505
|
+
}
|
|
29506
|
+
}
|
|
29507
|
+
});
|
|
29508
|
+
stderr.resume();
|
|
29509
|
+
return {
|
|
29510
|
+
read() {
|
|
29511
|
+
let text = Buffer.concat(chunks).toString("utf8");
|
|
29512
|
+
if (evicted) {
|
|
29513
|
+
const newline = text.indexOf("\n");
|
|
29514
|
+
text = newline === -1 ? "" : text.slice(newline + 1);
|
|
29515
|
+
}
|
|
29516
|
+
return sanitizeStderrTail(text);
|
|
29517
|
+
}
|
|
29518
|
+
};
|
|
29519
|
+
}
|
|
29520
|
+
|
|
29521
|
+
// src/host/opencode.ts
|
|
29376
29522
|
var import_node_fs3 = require("node:fs");
|
|
29377
29523
|
var import_promises4 = require("node:fs/promises");
|
|
29378
29524
|
var import_node_os4 = require("node:os");
|
|
@@ -31027,8 +31173,18 @@ async function openOpenCodeAcpSession(options) {
|
|
|
31027
31173
|
await disposeHome();
|
|
31028
31174
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31029
31175
|
}
|
|
31030
|
-
child.stderr
|
|
31031
|
-
|
|
31176
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31177
|
+
if (options.onStderrTail) {
|
|
31178
|
+
const deliverTail = options.onStderrTail;
|
|
31179
|
+
let tailDelivered = false;
|
|
31180
|
+
const publishTail = () => {
|
|
31181
|
+
if (tailDelivered) return;
|
|
31182
|
+
tailDelivered = true;
|
|
31183
|
+
deliverTail(stderrTail.read());
|
|
31184
|
+
};
|
|
31185
|
+
child.once("exit", publishTail);
|
|
31186
|
+
child.once("close", publishTail);
|
|
31187
|
+
}
|
|
31032
31188
|
let sessionRef = null;
|
|
31033
31189
|
const transport = createBoundTransport({
|
|
31034
31190
|
readable: child.stdout,
|
|
@@ -31310,8 +31466,18 @@ async function openClaudeAcpSession(options) {
|
|
|
31310
31466
|
await terminateClaudeChild(child);
|
|
31311
31467
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31312
31468
|
}
|
|
31313
|
-
child.stderr
|
|
31314
|
-
|
|
31469
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31470
|
+
if (options.onStderrTail) {
|
|
31471
|
+
const deliverTail = options.onStderrTail;
|
|
31472
|
+
let tailDelivered = false;
|
|
31473
|
+
const publishTail = () => {
|
|
31474
|
+
if (tailDelivered) return;
|
|
31475
|
+
tailDelivered = true;
|
|
31476
|
+
deliverTail(stderrTail.read());
|
|
31477
|
+
};
|
|
31478
|
+
child.once("exit", publishTail);
|
|
31479
|
+
child.once("close", publishTail);
|
|
31480
|
+
}
|
|
31315
31481
|
let sessionRef = null;
|
|
31316
31482
|
const transport = createBoundTransport({
|
|
31317
31483
|
readable: child.stdout,
|
|
@@ -31586,8 +31752,18 @@ async function openCodexAcpSession(options) {
|
|
|
31586
31752
|
await terminateCodexChild(child);
|
|
31587
31753
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31588
31754
|
}
|
|
31589
|
-
child.stderr
|
|
31590
|
-
|
|
31755
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31756
|
+
if (options.onStderrTail) {
|
|
31757
|
+
const deliverTail = options.onStderrTail;
|
|
31758
|
+
let tailDelivered = false;
|
|
31759
|
+
const publishTail = () => {
|
|
31760
|
+
if (tailDelivered) return;
|
|
31761
|
+
tailDelivered = true;
|
|
31762
|
+
deliverTail(stderrTail.read());
|
|
31763
|
+
};
|
|
31764
|
+
child.once("exit", publishTail);
|
|
31765
|
+
child.once("close", publishTail);
|
|
31766
|
+
}
|
|
31591
31767
|
let sessionRef = null;
|
|
31592
31768
|
const transport = createBoundTransport({
|
|
31593
31769
|
readable: child.stdout,
|
|
@@ -31637,6 +31813,19 @@ async function openCodexAcpSession(options) {
|
|
|
31637
31813
|
}
|
|
31638
31814
|
}
|
|
31639
31815
|
|
|
31816
|
+
// src/listener/types.ts
|
|
31817
|
+
var LISTENER_PROMPT_TIMEOUT_MS = 6e5;
|
|
31818
|
+
var ListenerRenewalUnavailableError = class extends Error {
|
|
31819
|
+
constructor(message) {
|
|
31820
|
+
super(message);
|
|
31821
|
+
this.name = "renewal_unavailable";
|
|
31822
|
+
}
|
|
31823
|
+
};
|
|
31824
|
+
async function resolveBudgetAndPrompt(session, prompt, budget) {
|
|
31825
|
+
const timeoutMs = typeof budget === "number" ? budget : await budget();
|
|
31826
|
+
return await session.prompt(prompt, { timeoutMs });
|
|
31827
|
+
}
|
|
31828
|
+
|
|
31640
31829
|
// src/listener/engine.ts
|
|
31641
31830
|
var UUID_RE8 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
31642
31831
|
var TERMINAL_STATES = /* @__PURE__ */ new Set(["done", "expired", "failed"]);
|
|
@@ -31751,6 +31940,7 @@ function abortError() {
|
|
|
31751
31940
|
}
|
|
31752
31941
|
function defaultRetryablePromptError(error) {
|
|
31753
31942
|
if (error instanceof SenderProvenanceUnavailableError) return true;
|
|
31943
|
+
if (error instanceof ListenerRenewalUnavailableError) return true;
|
|
31754
31944
|
if (error instanceof AcpHostError) return TRANSIENT_ACP_CODES.has(error.code);
|
|
31755
31945
|
return false;
|
|
31756
31946
|
}
|
|
@@ -32517,8 +32707,18 @@ async function openGrokAcpSession(options) {
|
|
|
32517
32707
|
child.kill("SIGKILL");
|
|
32518
32708
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
32519
32709
|
}
|
|
32520
|
-
child.stderr
|
|
32521
|
-
|
|
32710
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
32711
|
+
if (options.onStderrTail) {
|
|
32712
|
+
const deliverTail = options.onStderrTail;
|
|
32713
|
+
let tailDelivered = false;
|
|
32714
|
+
const publishTail = () => {
|
|
32715
|
+
if (tailDelivered) return;
|
|
32716
|
+
tailDelivered = true;
|
|
32717
|
+
deliverTail(stderrTail.read());
|
|
32718
|
+
};
|
|
32719
|
+
child.once("exit", publishTail);
|
|
32720
|
+
child.once("close", publishTail);
|
|
32721
|
+
}
|
|
32522
32722
|
let sessionRef = null;
|
|
32523
32723
|
const transport = createBoundTransport({
|
|
32524
32724
|
readable: child.stdout,
|
|
@@ -32583,8 +32783,9 @@ var GrokListenerModel = class {
|
|
|
32583
32783
|
async prompt(_signal, _mode, prompt) {
|
|
32584
32784
|
if (this.closed) throw new Error("listener model is closed");
|
|
32585
32785
|
const worker = await this.ensureWorker();
|
|
32786
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
32586
32787
|
try {
|
|
32587
|
-
return await worker.session
|
|
32788
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
32588
32789
|
} catch (error) {
|
|
32589
32790
|
if (error instanceof AcpChildExitError) {
|
|
32590
32791
|
try {
|
|
@@ -32619,6 +32820,7 @@ var GrokListenerModel = class {
|
|
|
32619
32820
|
...this.options.model ? { model: this.options.model } : {},
|
|
32620
32821
|
...this.options.effort ? { effort: this.options.effort } : {},
|
|
32621
32822
|
...this.options.env ? { env: this.options.env } : {},
|
|
32823
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
32622
32824
|
clientName: "cswarm-listener"
|
|
32623
32825
|
});
|
|
32624
32826
|
try {
|
|
@@ -32735,8 +32937,9 @@ var OpenCodeListenerModel = class {
|
|
|
32735
32937
|
async prompt(_signal, _mode, prompt) {
|
|
32736
32938
|
if (this.closed) throw new Error("listener model is closed");
|
|
32737
32939
|
const worker = await this.ensureWorker();
|
|
32940
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
32738
32941
|
try {
|
|
32739
|
-
return await worker.session
|
|
32942
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
32740
32943
|
} catch (error) {
|
|
32741
32944
|
if (error instanceof AcpChildExitError) {
|
|
32742
32945
|
const home = this.workerHome;
|
|
@@ -33026,6 +33229,7 @@ var OpenCodeListenerModel = class {
|
|
|
33026
33229
|
...this.options.model ? { model: this.options.model } : {},
|
|
33027
33230
|
...this.options.env ? { env: this.options.env } : {},
|
|
33028
33231
|
...this.options.allowMissingAuth === true ? { allowMissingAuth: true } : {},
|
|
33232
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33029
33233
|
clientName: "cswarm-listener"
|
|
33030
33234
|
});
|
|
33031
33235
|
pending.phase = "opening";
|
|
@@ -33150,8 +33354,9 @@ var ClaudeListenerModel = class {
|
|
|
33150
33354
|
async prompt(_signal, _mode, prompt) {
|
|
33151
33355
|
if (this.closed) throw new Error("listener model is closed");
|
|
33152
33356
|
const worker = await this.ensureWorker();
|
|
33357
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
33153
33358
|
try {
|
|
33154
|
-
return await worker.session
|
|
33359
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
33155
33360
|
} catch (error) {
|
|
33156
33361
|
if (error instanceof AcpChildExitError) {
|
|
33157
33362
|
try {
|
|
@@ -33233,6 +33438,7 @@ var ClaudeListenerModel = class {
|
|
|
33233
33438
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
33234
33439
|
...this.options.env ? { env: this.options.env } : {},
|
|
33235
33440
|
signal: controller.signal,
|
|
33441
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33236
33442
|
clientName: "cswarm-listener"
|
|
33237
33443
|
});
|
|
33238
33444
|
this.openingHandle = handle;
|
|
@@ -33316,8 +33522,9 @@ var CodexListenerModel = class {
|
|
|
33316
33522
|
async prompt(_signal, _mode, prompt) {
|
|
33317
33523
|
if (this.closed) throw new Error("listener model is closed");
|
|
33318
33524
|
const worker = await this.ensureWorker();
|
|
33525
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
33319
33526
|
try {
|
|
33320
|
-
return await worker.session
|
|
33527
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
33321
33528
|
} catch (error) {
|
|
33322
33529
|
if (error instanceof AcpChildExitError) {
|
|
33323
33530
|
try {
|
|
@@ -33399,6 +33606,7 @@ var CodexListenerModel = class {
|
|
|
33399
33606
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
33400
33607
|
...this.options.env ? { env: this.options.env } : {},
|
|
33401
33608
|
signal: controller.signal,
|
|
33609
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33402
33610
|
clientName: "cswarm-listener"
|
|
33403
33611
|
});
|
|
33404
33612
|
this.openingHandle = handle;
|
|
@@ -35043,6 +35251,7 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
35043
35251
|
"stoppedAt",
|
|
35044
35252
|
"lastSignalId",
|
|
35045
35253
|
"lastErrorCode",
|
|
35254
|
+
"lastWorkerStderrTail",
|
|
35046
35255
|
"logPath",
|
|
35047
35256
|
"deliveryMode",
|
|
35048
35257
|
"pendingDeliveryCount",
|
|
@@ -35097,7 +35306,7 @@ function parseStatus(raw) {
|
|
|
35097
35306
|
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE12.test(candidate);
|
|
35098
35307
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
35099
35308
|
const nullableTimestamp = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
35100
|
-
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE12.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE12.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE12.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)) || typeof row.logPath !== "string" || !(0, import_node_path14.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))) {
|
|
35309
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE12.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE12.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE12.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_path14.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))) {
|
|
35101
35310
|
throw new Error("stored listener status is malformed");
|
|
35102
35311
|
}
|
|
35103
35312
|
return {
|
|
@@ -35107,7 +35316,8 @@ function parseStatus(raw) {
|
|
|
35107
35316
|
lastTerminalDeliveryFailureCount: row.lastTerminalDeliveryFailureCount ?? null,
|
|
35108
35317
|
lastTerminalDeliveryFailureAt: row.lastTerminalDeliveryFailureAt ?? null,
|
|
35109
35318
|
lastClaimAt: row.lastClaimAt ?? null,
|
|
35110
|
-
lastAckAt: row.lastAckAt ?? null
|
|
35319
|
+
lastAckAt: row.lastAckAt ?? null,
|
|
35320
|
+
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null
|
|
35111
35321
|
};
|
|
35112
35322
|
}
|
|
35113
35323
|
async function writeListenerStatus(paths, status) {
|
|
@@ -35144,7 +35354,12 @@ async function appendListenerEvent(paths, event) {
|
|
|
35144
35354
|
// D-051 companion 2: why a listener is down, and why it stopped trying.
|
|
35145
35355
|
"restart_attempts",
|
|
35146
35356
|
"restartable",
|
|
35147
|
-
"restarts_exhausted"
|
|
35357
|
+
"restarts_exhausted",
|
|
35358
|
+
// D-090 family: the last lines of a dead worker's stderr, sanitized and
|
|
35359
|
+
// bounded by the supervisor, and the prompt-turn budget behind a timeout.
|
|
35360
|
+
// Local log only — this file never feeds a server payload.
|
|
35361
|
+
"worker_stderr_tail",
|
|
35362
|
+
"turn_budget_ms"
|
|
35148
35363
|
]);
|
|
35149
35364
|
const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
|
|
35150
35365
|
const deliveryOutcomes = /* @__PURE__ */ new Set([
|
|
@@ -35166,7 +35381,16 @@ async function appendListenerEvent(paths, event) {
|
|
|
35166
35381
|
if (key2 === "outcome" && !(value === null || typeof value === "string" && deliveryOutcomes.has(value))) {
|
|
35167
35382
|
throw new Error("listener event outcome is not allowed");
|
|
35168
35383
|
}
|
|
35169
|
-
if (typeof value === "string" &&
|
|
35384
|
+
if (key2 === "worker_stderr_tail" && !(typeof value === "string" && value.length > 0 && value.length <= 2048)) {
|
|
35385
|
+
throw new Error("listener event stderr tail is not allowed");
|
|
35386
|
+
}
|
|
35387
|
+
if (key2 === "turn_budget_ms" && !(typeof value === "number" && Number.isSafeInteger(value) && value > 0)) {
|
|
35388
|
+
throw new Error("listener event turn budget is not allowed");
|
|
35389
|
+
}
|
|
35390
|
+
if (typeof value === "string" && // worker_stderr_tail is deliberately exempt from the generic 128-char
|
|
35391
|
+
// cap (its own bound is 2048, above); the secret scan still applies to
|
|
35392
|
+
// every string, the tail included.
|
|
35393
|
+
(key2 !== "worker_stderr_tail" && value.length > 128 || /swm_(?:agt|inv|cap)_/i.test(value))) {
|
|
35170
35394
|
throw new Error("listener event contains unsafe text");
|
|
35171
35395
|
}
|
|
35172
35396
|
}
|
|
@@ -35443,6 +35667,23 @@ function safeErrorCode(error) {
|
|
|
35443
35667
|
const name = error.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "_");
|
|
35444
35668
|
return name.slice(0, 96) || "listener_error";
|
|
35445
35669
|
}
|
|
35670
|
+
var TAIL_SERIALIZED_BUDGET_BYTES = 3e3;
|
|
35671
|
+
function fitWorkerStderrTailForLog(tail) {
|
|
35672
|
+
let fitted = tail.trim();
|
|
35673
|
+
for (; ; ) {
|
|
35674
|
+
if (fitted.length === 0) return fitted;
|
|
35675
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(fitted), "utf8");
|
|
35676
|
+
if (fitted.length <= 2048 && serializedBytes <= TAIL_SERIALIZED_BUDGET_BYTES) {
|
|
35677
|
+
return fitted;
|
|
35678
|
+
}
|
|
35679
|
+
const dropChars = Math.max(
|
|
35680
|
+
fitted.length - 2048,
|
|
35681
|
+
Math.ceil((serializedBytes - TAIL_SERIALIZED_BUDGET_BYTES) / 6),
|
|
35682
|
+
1
|
|
35683
|
+
);
|
|
35684
|
+
fitted = fitted.slice(dropChars);
|
|
35685
|
+
}
|
|
35686
|
+
}
|
|
35446
35687
|
async function runListenerSupervisor(options) {
|
|
35447
35688
|
const now = options.now ?? Date.now;
|
|
35448
35689
|
const startedAt = iso2(now);
|
|
@@ -35464,6 +35705,7 @@ async function runListenerSupervisor(options) {
|
|
|
35464
35705
|
stoppedAt: null,
|
|
35465
35706
|
lastSignalId: null,
|
|
35466
35707
|
lastErrorCode: null,
|
|
35708
|
+
lastWorkerStderrTail: null,
|
|
35467
35709
|
deliveryMode: null,
|
|
35468
35710
|
pendingDeliveryCount: null,
|
|
35469
35711
|
lastTerminalDeliveryFailureCount: null,
|
|
@@ -35518,9 +35760,21 @@ async function runListenerSupervisor(options) {
|
|
|
35518
35760
|
instance_id: status.instanceId,
|
|
35519
35761
|
pid: process.pid
|
|
35520
35762
|
});
|
|
35763
|
+
const takeWorkerStderrTail = options.takeWorkerStderrTail;
|
|
35764
|
+
const takeTail = () => {
|
|
35765
|
+
if (!takeWorkerStderrTail) return null;
|
|
35766
|
+
const tail = takeWorkerStderrTail();
|
|
35767
|
+
if (typeof tail !== "string") return null;
|
|
35768
|
+
const fitted = fitWorkerStderrTailForLog(tail);
|
|
35769
|
+
return fitted.length > 0 ? fitted : null;
|
|
35770
|
+
};
|
|
35521
35771
|
const onEvent = (event) => {
|
|
35522
35772
|
if (event.type === "ready") {
|
|
35523
|
-
transition("ready", {
|
|
35773
|
+
transition("ready", {
|
|
35774
|
+
readyAt: event.ts,
|
|
35775
|
+
lastErrorCode: null,
|
|
35776
|
+
lastWorkerStderrTail: null
|
|
35777
|
+
});
|
|
35524
35778
|
log({ ts: event.ts, event: "listener_ready" });
|
|
35525
35779
|
return;
|
|
35526
35780
|
}
|
|
@@ -35537,7 +35791,15 @@ async function runListenerSupervisor(options) {
|
|
|
35537
35791
|
event: "listener_effect",
|
|
35538
35792
|
signal_id: event.signalId,
|
|
35539
35793
|
status: event.status,
|
|
35540
|
-
failure_code: event.failureCode
|
|
35794
|
+
failure_code: event.failureCode,
|
|
35795
|
+
// Code comparison, not message matching (D-053). The budget rides
|
|
35796
|
+
// only the timeout class so a reader can see what bound was hit — and
|
|
35797
|
+
// it is the CLAMPED budget actually in force, not the configured cap.
|
|
35798
|
+
...(() => {
|
|
35799
|
+
if (event.failureCode !== "acptimeouterror") return {};
|
|
35800
|
+
const budget = options.getTurnBudgetMs?.();
|
|
35801
|
+
return typeof budget === "number" && budget > 0 ? { turn_budget_ms: budget } : {};
|
|
35802
|
+
})()
|
|
35541
35803
|
});
|
|
35542
35804
|
return;
|
|
35543
35805
|
}
|
|
@@ -35667,14 +35929,20 @@ async function runListenerSupervisor(options) {
|
|
|
35667
35929
|
restarts += 1;
|
|
35668
35930
|
const delayMs = nextListenerRestartMs(restarts, policy, restartRandom);
|
|
35669
35931
|
const restartCode = safeErrorCode(stop.error);
|
|
35932
|
+
const restartStderrTail = takeTail();
|
|
35670
35933
|
log({
|
|
35671
35934
|
ts: iso2(now),
|
|
35672
35935
|
event: "listener_restarting",
|
|
35673
35936
|
attempt: restarts,
|
|
35674
35937
|
delay_ms: delayMs,
|
|
35675
|
-
failure_code: restartCode
|
|
35938
|
+
failure_code: restartCode,
|
|
35939
|
+
...restartStderrTail !== null ? { worker_stderr_tail: restartStderrTail } : {}
|
|
35940
|
+
});
|
|
35941
|
+
transition("starting", {
|
|
35942
|
+
readyAt: null,
|
|
35943
|
+
lastErrorCode: restartCode,
|
|
35944
|
+
lastWorkerStderrTail: restartStderrTail
|
|
35676
35945
|
});
|
|
35677
|
-
transition("starting", { readyAt: null, lastErrorCode: restartCode });
|
|
35678
35946
|
await restartSleep(delayMs, controller.signal);
|
|
35679
35947
|
if (controller.signal.aborted) {
|
|
35680
35948
|
stop = { reason: "cancelled" };
|
|
@@ -35685,14 +35953,17 @@ async function runListenerSupervisor(options) {
|
|
|
35685
35953
|
if (stop.reason === "cancelled") {
|
|
35686
35954
|
transition("stopped", {
|
|
35687
35955
|
stoppedAt,
|
|
35688
|
-
lastErrorCode: null
|
|
35956
|
+
lastErrorCode: null,
|
|
35957
|
+
lastWorkerStderrTail: null
|
|
35689
35958
|
});
|
|
35690
35959
|
log({ ts: stoppedAt, event: "listener_stopped" });
|
|
35691
35960
|
} else {
|
|
35692
35961
|
const code = stop.reason === "credential" ? "credential_stopped" : safeErrorCode(stop.error);
|
|
35962
|
+
const failedStderrTail = takeTail();
|
|
35693
35963
|
transition("failed", {
|
|
35694
35964
|
stoppedAt,
|
|
35695
|
-
lastErrorCode: code
|
|
35965
|
+
lastErrorCode: code,
|
|
35966
|
+
lastWorkerStderrTail: failedStderrTail
|
|
35696
35967
|
});
|
|
35697
35968
|
log({
|
|
35698
35969
|
ts: stoppedAt,
|
|
@@ -35700,7 +35971,8 @@ async function runListenerSupervisor(options) {
|
|
|
35700
35971
|
failure_code: code,
|
|
35701
35972
|
restart_attempts: restarts,
|
|
35702
35973
|
restartable: eligible,
|
|
35703
|
-
restarts_exhausted: exhausted
|
|
35974
|
+
restarts_exhausted: exhausted,
|
|
35975
|
+
...failedStderrTail !== null ? { worker_stderr_tail: failedStderrTail } : {}
|
|
35704
35976
|
});
|
|
35705
35977
|
}
|
|
35706
35978
|
} catch (error) {
|
|
@@ -35708,11 +35980,17 @@ async function runListenerSupervisor(options) {
|
|
|
35708
35980
|
const code = safeErrorCode(
|
|
35709
35981
|
error instanceof Error ? error : new Error(String(error))
|
|
35710
35982
|
);
|
|
35711
|
-
|
|
35983
|
+
const failedStderrTail = takeTail();
|
|
35984
|
+
transition("failed", {
|
|
35985
|
+
stoppedAt,
|
|
35986
|
+
lastErrorCode: code,
|
|
35987
|
+
lastWorkerStderrTail: failedStderrTail
|
|
35988
|
+
});
|
|
35712
35989
|
log({
|
|
35713
35990
|
ts: stoppedAt,
|
|
35714
35991
|
event: "listener_failed",
|
|
35715
|
-
failure_code: code
|
|
35992
|
+
failure_code: code,
|
|
35993
|
+
...failedStderrTail !== null ? { worker_stderr_tail: failedStderrTail } : {}
|
|
35716
35994
|
});
|
|
35717
35995
|
} finally {
|
|
35718
35996
|
await writes.catch(() => void 0);
|
|
@@ -36564,7 +36842,8 @@ function buildListenerChildArgs(spec) {
|
|
|
36564
36842
|
...provider === "claude" && claudeExe ? ["--claude-executable", claudeExe] : [],
|
|
36565
36843
|
...provider === "codex" && codexExe ? ["--codex-executable", codexExe] : [],
|
|
36566
36844
|
...spec.model ? ["--model", spec.model] : [],
|
|
36567
|
-
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : []
|
|
36845
|
+
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : [],
|
|
36846
|
+
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : []
|
|
36568
36847
|
];
|
|
36569
36848
|
}
|
|
36570
36849
|
async function spawnDetachedListener(options) {
|
|
@@ -36649,6 +36928,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36649
36928
|
"to",
|
|
36650
36929
|
"token-id",
|
|
36651
36930
|
"ttl-ms",
|
|
36931
|
+
"turn-budget",
|
|
36652
36932
|
"uid",
|
|
36653
36933
|
"until",
|
|
36654
36934
|
"url",
|
|
@@ -36682,8 +36962,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
36682
36962
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
36683
36963
|
];
|
|
36684
36964
|
function packageVersion() {
|
|
36685
|
-
if ("0.1.
|
|
36686
|
-
return "0.1.
|
|
36965
|
+
if ("0.1.23".length > 0) {
|
|
36966
|
+
return "0.1.23";
|
|
36687
36967
|
}
|
|
36688
36968
|
try {
|
|
36689
36969
|
const value = JSON.parse(
|
|
@@ -36810,7 +37090,8 @@ Usage:
|
|
|
36810
37090
|
cswarm file get <name|file-id> [--version <n>] [--out <local-path>] [--force] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
36811
37091
|
cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
36812
37092
|
cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
36813
|
-
cswarm
|
|
37093
|
+
cswarm feedback "<text>" --kind bug|idea|friction [--about <ref>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
37094
|
+
cswarm listen start --agent-token-stdin [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--foreground] [--json]
|
|
36814
37095
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
36815
37096
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
36816
37097
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
@@ -36820,6 +37101,7 @@ Usage:
|
|
|
36820
37101
|
cswarm invite [--url <url> --anon-key <key>] [--workspace-id <uuid>] --email <email>
|
|
36821
37102
|
cswarm invite revoke [--url <url> --anon-key <key>] [--workspace-id <uuid>] --invitation-id <uuid> [--json]
|
|
36822
37103
|
cswarm member remove <full-user-id|exact-name> --confirm <same-selector> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
37104
|
+
cswarm workspace close <full-id|exact-name> --confirm <same-selector> [--url <url> --anon-key <key>] [--json]
|
|
36823
37105
|
cswarm accept --link-stdin [--name <name>] [--no-browser] [--json]
|
|
36824
37106
|
cswarm accept <https://...#invite=...|cswarm://accept/...> [--name <name>] [--no-browser] [--json] # unsafe: shell history/process list
|
|
36825
37107
|
cswarm accept --invitation-token-stdin [--url <url> --anon-key <key>]
|
|
@@ -36845,15 +37127,33 @@ Credential selection for command/dogfood:
|
|
|
36845
37127
|
members reads only -- either form
|
|
36846
37128
|
file put, file ls, file get, file rm, file restore
|
|
36847
37129
|
read and command, nothing persisted -- either form
|
|
37130
|
+
feedback command only, nothing persisted -- either form
|
|
36848
37131
|
listen start persists durable state, rotates -- needs expires_at
|
|
36849
37132
|
token revoke names what it revokes -- needs token_id
|
|
37133
|
+
workspace close is human-session-only; it never accepts an agent token
|
|
37134
|
+
|
|
37135
|
+
Found a bug or missing feature in cswarm itself? cswarm feedback sends it to the
|
|
37136
|
+
deployment's operators \u2014 agents are encouraged to report friction they hit.
|
|
36850
37137
|
|
|
36851
37138
|
Signals (intention sharing) accept the same credential selection. Agent mode
|
|
36852
37139
|
never opens a browser or infers a human's saved workspace. Durations use a whole
|
|
36853
37140
|
number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
|
|
36854
37141
|
Place -- before signal text that itself begins with -- to stop option parsing.
|
|
37142
|
+
Signal text is at most 2000 characters and --about at most 500; a longer body is
|
|
37143
|
+
refused locally before any network call, so compose within the limit.
|
|
36855
37144
|
|
|
36856
|
-
|
|
37145
|
+
listen start --turn-budget bounds ONE worker prompt turn (default 10m): how long
|
|
37146
|
+
the worker may think and use tools on a single message before the turn times out
|
|
37147
|
+
and durable delivery retries it. A whole number plus s, m, or h (for example
|
|
37148
|
+
90s, 5m, 1h), at least 30s and at most 60m. Each turn is additionally clamped
|
|
37149
|
+
to the live credential's remaining lifetime minus 60s, after renewing it when
|
|
37150
|
+
due \u2014 a turn never outlives its credential. Right after a rotation the full
|
|
37151
|
+
budget is available up to the token TTL minus 60s (about 59m on the default 1h
|
|
37152
|
+
TTL); a turn that lands just before a rotation can be clamped to the ~5m
|
|
37153
|
+
renewal lead, and if it times out there, durable delivery retries it on the
|
|
37154
|
+
fresh credential.
|
|
37155
|
+
|
|
37156
|
+
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
36857
37157
|
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
|
|
36858
37158
|
registers one principal. Invitation links, agent credentials, and capability links
|
|
36859
37159
|
appear only in fresh success responses.
|
|
@@ -37548,6 +37848,53 @@ async function runMember(args) {
|
|
|
37548
37848
|
`);
|
|
37549
37849
|
}
|
|
37550
37850
|
}
|
|
37851
|
+
async function runWorkspace(args) {
|
|
37852
|
+
args.assertShape([...TARGET_FLAGS, "confirm", "json"], 3);
|
|
37853
|
+
if (args.positionals[1] !== "close") {
|
|
37854
|
+
throw new UsageError(
|
|
37855
|
+
`unknown workspace command: ${args.positionals[1] ?? "(missing)"}`
|
|
37856
|
+
);
|
|
37857
|
+
}
|
|
37858
|
+
const selector = args.positionals[2];
|
|
37859
|
+
if (args.required("confirm") !== selector) {
|
|
37860
|
+
throw new Error(
|
|
37861
|
+
"--confirm must exactly repeat the workspace selector; no request was sent"
|
|
37862
|
+
);
|
|
37863
|
+
}
|
|
37864
|
+
const cloud = await target(args);
|
|
37865
|
+
const human = await humanCredential(args, cloud);
|
|
37866
|
+
const directory = cloudWorkspaceDirectory(cloud);
|
|
37867
|
+
const projects = await directory.list(human);
|
|
37868
|
+
const selected = resolveWorkspaceSelector(selector, projects);
|
|
37869
|
+
const response = (await sendConnectWithPending(
|
|
37870
|
+
new ThinCommandClient(cloud),
|
|
37871
|
+
human,
|
|
37872
|
+
selected.workspace_id,
|
|
37873
|
+
{ kind: "archive_workspace" }
|
|
37874
|
+
)).response;
|
|
37875
|
+
if (response.status !== "accepted") {
|
|
37876
|
+
throw new Error(
|
|
37877
|
+
`Workspace close was rejected: ${response.reason ?? "domain rejection"}. The workspace is still open.`
|
|
37878
|
+
);
|
|
37879
|
+
}
|
|
37880
|
+
const { closedWasSelected, nextWorkspace, selectedWorkspaceId } = await updateWorkspaceDefaultAfterClose(
|
|
37881
|
+
human.store,
|
|
37882
|
+
human.userId,
|
|
37883
|
+
selected.workspace_id,
|
|
37884
|
+
projects
|
|
37885
|
+
);
|
|
37886
|
+
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.`;
|
|
37887
|
+
const output = {
|
|
37888
|
+
message,
|
|
37889
|
+
status: response.status,
|
|
37890
|
+
workspace_id: selected.workspace_id,
|
|
37891
|
+
selected_workspace_id: selectedWorkspaceId,
|
|
37892
|
+
command_event_ids: response.event_ids
|
|
37893
|
+
};
|
|
37894
|
+
if (args.has("json")) printJson(output);
|
|
37895
|
+
else process.stdout.write(`${message}
|
|
37896
|
+
`);
|
|
37897
|
+
}
|
|
37551
37898
|
async function runLegacyAccept(args) {
|
|
37552
37899
|
const invitationToken = await invitationCredential(args);
|
|
37553
37900
|
const cloud = await target(args);
|
|
@@ -38208,8 +38555,42 @@ function signalDuration(value) {
|
|
|
38208
38555
|
}
|
|
38209
38556
|
return milliseconds;
|
|
38210
38557
|
}
|
|
38558
|
+
function listenerTurnBudgetMs(value) {
|
|
38559
|
+
if (value === void 0) return LISTENER_PROMPT_TIMEOUT_MS;
|
|
38560
|
+
const match = /^([1-9]\d*)(s|m|h)$/.exec(value);
|
|
38561
|
+
if (!match) {
|
|
38562
|
+
throw new Error("--turn-budget must be a duration such as 90s, 5m, or 1h");
|
|
38563
|
+
}
|
|
38564
|
+
const unit = match[2] === "s" ? 1e3 : match[2] === "m" ? 6e4 : 36e5;
|
|
38565
|
+
const milliseconds = Number(match[1]) * unit;
|
|
38566
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds < 3e4 || milliseconds > 36e5) {
|
|
38567
|
+
throw new Error("--turn-budget must be between 30s and 60m");
|
|
38568
|
+
}
|
|
38569
|
+
return milliseconds;
|
|
38570
|
+
}
|
|
38571
|
+
var TURN_BUDGET_CREDENTIAL_MARGIN_MS = 6e4;
|
|
38572
|
+
function clampTurnBudgetToCredential(budgetMs, credentialExpiresAt, nowMs) {
|
|
38573
|
+
if (credentialExpiresAt === null) return budgetMs;
|
|
38574
|
+
const horizonMs = credentialExpiresAt - nowMs - TURN_BUDGET_CREDENTIAL_MARGIN_MS;
|
|
38575
|
+
return Math.max(1e3, Math.min(budgetMs, horizonMs));
|
|
38576
|
+
}
|
|
38577
|
+
function resolveTurnBudgetOrDefer(configuredBudgetMs, credentialExpiresAt, nowMs, renewalFailed) {
|
|
38578
|
+
if (renewalFailed) {
|
|
38579
|
+
throw new ListenerRenewalUnavailableError(
|
|
38580
|
+
"the worker credential could not be renewed before this turn; deferring the ask for durable redelivery"
|
|
38581
|
+
);
|
|
38582
|
+
}
|
|
38583
|
+
if (credentialExpiresAt !== null && credentialExpiresAt - nowMs <= TURN_BUDGET_CREDENTIAL_MARGIN_MS) {
|
|
38584
|
+
throw new ListenerRenewalUnavailableError(
|
|
38585
|
+
"the live worker credential is inside its rotation margin and was not renewed; deferring the ask for durable redelivery"
|
|
38586
|
+
);
|
|
38587
|
+
}
|
|
38588
|
+
return clampTurnBudgetToCredential(configuredBudgetMs, credentialExpiresAt, nowMs);
|
|
38589
|
+
}
|
|
38590
|
+
var SIGNAL_BODY_MAX = 2e3;
|
|
38591
|
+
var SIGNAL_ABOUT_MAX = 500;
|
|
38211
38592
|
function signalText(value, label) {
|
|
38212
|
-
const maximum = label === "body" ?
|
|
38593
|
+
const maximum = label === "body" ? SIGNAL_BODY_MAX : SIGNAL_ABOUT_MAX;
|
|
38213
38594
|
if (value.length < (label === "body" ? 1 : 0) || value.length > maximum) {
|
|
38214
38595
|
throw new Error(
|
|
38215
38596
|
`${label === "body" ? "signal text" : "--about"} must be ${label === "body" ? "1.." : "at most "}${maximum} characters`
|
|
@@ -38468,6 +38849,10 @@ Check whether you are about to do the same work.
|
|
|
38468
38849
|
`}`
|
|
38469
38850
|
);
|
|
38470
38851
|
}
|
|
38852
|
+
function replyRefusalHint(error) {
|
|
38853
|
+
if (!(error instanceof CommandHttpError) || error.status !== 403) return null;
|
|
38854
|
+
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.";
|
|
38855
|
+
}
|
|
38471
38856
|
async function runReply(args) {
|
|
38472
38857
|
args.assertShape([
|
|
38473
38858
|
...TARGET_FLAGS,
|
|
@@ -38499,7 +38884,14 @@ async function runReply(args) {
|
|
|
38499
38884
|
about: null,
|
|
38500
38885
|
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
|
|
38501
38886
|
};
|
|
38502
|
-
|
|
38887
|
+
let result;
|
|
38888
|
+
try {
|
|
38889
|
+
result = await postSignalCommand(cloud, credential, command2);
|
|
38890
|
+
} catch (error) {
|
|
38891
|
+
const hint = replyRefusalHint(error);
|
|
38892
|
+
if (hint !== null) throw new Error(hint);
|
|
38893
|
+
throw error;
|
|
38894
|
+
}
|
|
38503
38895
|
const signal = result.response.signal;
|
|
38504
38896
|
if (args.has("json")) {
|
|
38505
38897
|
printJson({
|
|
@@ -38990,6 +39382,13 @@ function renderListenerStatus(status) {
|
|
|
38990
39382
|
status.lastSignalId ? `Last handled signal: ${status.lastSignalId}.` : "No signal has been handled yet.",
|
|
38991
39383
|
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener error is recorded."
|
|
38992
39384
|
];
|
|
39385
|
+
if (status.lastWorkerStderrTail) {
|
|
39386
|
+
const tailLines = status.lastWorkerStderrTail.split("\n").filter((line) => line.trim().length > 0);
|
|
39387
|
+
lines.push("Worker stderr (local log only):");
|
|
39388
|
+
for (const line of tailLines.slice(-3)) {
|
|
39389
|
+
lines.push(` ${line}`);
|
|
39390
|
+
}
|
|
39391
|
+
}
|
|
38993
39392
|
if (status.deliveryMode === "durable_claim") {
|
|
38994
39393
|
lines.push("Delivery mode: durable claim and acknowledgement.");
|
|
38995
39394
|
} else if (status.deliveryMode === "cursor_fallback") {
|
|
@@ -39169,22 +39568,58 @@ async function runConfiguredListener(options) {
|
|
|
39169
39568
|
principalId: options.principalId,
|
|
39170
39569
|
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
39171
39570
|
});
|
|
39571
|
+
const turnBudgetMs = options.turnBudgetMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
39572
|
+
let lastAppliedTurnBudgetMs = null;
|
|
39573
|
+
const resolveTurnBudgetMs = async () => {
|
|
39574
|
+
let renewalFailed = false;
|
|
39575
|
+
try {
|
|
39576
|
+
await credentialSession.bearer();
|
|
39577
|
+
} catch {
|
|
39578
|
+
renewalFailed = true;
|
|
39579
|
+
}
|
|
39580
|
+
const applied = resolveTurnBudgetOrDefer(
|
|
39581
|
+
turnBudgetMs,
|
|
39582
|
+
credentialSession.expiry,
|
|
39583
|
+
Date.now(),
|
|
39584
|
+
renewalFailed
|
|
39585
|
+
);
|
|
39586
|
+
lastAppliedTurnBudgetMs = applied;
|
|
39587
|
+
return applied;
|
|
39588
|
+
};
|
|
39589
|
+
let lastWorkerStderrTail = null;
|
|
39590
|
+
let workerStderrGeneration = 0;
|
|
39591
|
+
const newWorkerStderrTailSink = () => {
|
|
39592
|
+
const generation = ++workerStderrGeneration;
|
|
39593
|
+
lastWorkerStderrTail = null;
|
|
39594
|
+
return (tail) => {
|
|
39595
|
+
if (generation !== workerStderrGeneration) return;
|
|
39596
|
+
lastWorkerStderrTail = tail.length > 0 ? tail : null;
|
|
39597
|
+
};
|
|
39598
|
+
};
|
|
39172
39599
|
const newModel = () => options.provider === "opencode" ? new OpenCodeListenerModel({
|
|
39173
39600
|
cwd: options.cwd,
|
|
39174
39601
|
permissionMode: options.permissionMode,
|
|
39602
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39603
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39175
39604
|
...options.model ? { model: options.model } : {},
|
|
39176
39605
|
...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39177
39606
|
}) : options.provider === "claude" ? new ClaudeListenerModel({
|
|
39178
39607
|
cwd: options.cwd,
|
|
39179
39608
|
permissionMode: options.permissionMode,
|
|
39609
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39610
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39180
39611
|
...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39181
39612
|
}) : options.provider === "codex" ? new CodexListenerModel({
|
|
39182
39613
|
cwd: options.cwd,
|
|
39183
39614
|
permissionMode: options.permissionMode,
|
|
39615
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39616
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39184
39617
|
...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39185
39618
|
}) : new GrokListenerModel({
|
|
39186
39619
|
cwd: options.cwd,
|
|
39187
39620
|
permissionMode: options.permissionMode,
|
|
39621
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39622
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39188
39623
|
...options.model ? { model: options.model } : {},
|
|
39189
39624
|
...options.effort ? { effort: options.effort } : {},
|
|
39190
39625
|
...options.executable ? { executable: options.executable } : {}
|
|
@@ -39204,6 +39639,14 @@ async function runConfiguredListener(options) {
|
|
|
39204
39639
|
principalId: options.principalId,
|
|
39205
39640
|
provider: options.provider,
|
|
39206
39641
|
permissionMode: options.permissionMode,
|
|
39642
|
+
// The bound a timeout event reports: the last turn's clamped budget when
|
|
39643
|
+
// one has run, else the configured cap.
|
|
39644
|
+
getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
|
|
39645
|
+
takeWorkerStderrTail: () => {
|
|
39646
|
+
const tail = lastWorkerStderrTail;
|
|
39647
|
+
lastWorkerStderrTail = null;
|
|
39648
|
+
return tail;
|
|
39649
|
+
},
|
|
39207
39650
|
prepare: async (proposedInstanceId) => {
|
|
39208
39651
|
const selected = await openListenerDeliveryJournal({
|
|
39209
39652
|
profileId: options.cloud.profileId,
|
|
@@ -39258,6 +39701,7 @@ async function runListenStart(args) {
|
|
|
39258
39701
|
"claude-executable",
|
|
39259
39702
|
"codex-executable",
|
|
39260
39703
|
"state-dir",
|
|
39704
|
+
"turn-budget",
|
|
39261
39705
|
"foreground",
|
|
39262
39706
|
"json"
|
|
39263
39707
|
], 2);
|
|
@@ -39268,6 +39712,7 @@ async function runListenStart(args) {
|
|
|
39268
39712
|
}
|
|
39269
39713
|
const provider = listenerProvider(args);
|
|
39270
39714
|
validateListenerProviderFlags(args, provider);
|
|
39715
|
+
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
39271
39716
|
const cloud = await target(args);
|
|
39272
39717
|
const workspaceId2 = listenerUuid(
|
|
39273
39718
|
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
@@ -39302,6 +39747,7 @@ async function runListenStart(args) {
|
|
|
39302
39747
|
cwd,
|
|
39303
39748
|
permissionMode,
|
|
39304
39749
|
provider,
|
|
39750
|
+
turnBudgetMs,
|
|
39305
39751
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39306
39752
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39307
39753
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39352,6 +39798,7 @@ async function runListenStart(args) {
|
|
|
39352
39798
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {},
|
|
39353
39799
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39354
39800
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39801
|
+
...args.optional("turn-budget") ? { turnBudget: args.required("turn-budget") } : {},
|
|
39355
39802
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
39356
39803
|
...opencodeExecutable ? { opencodeExecutable } : {},
|
|
39357
39804
|
...claudeExecutable ? { claudeExecutable } : {},
|
|
@@ -39409,10 +39856,12 @@ async function runListenSupervisor(args) {
|
|
|
39409
39856
|
"opencode-executable",
|
|
39410
39857
|
"claude-executable",
|
|
39411
39858
|
"codex-executable",
|
|
39412
|
-
"state-dir"
|
|
39859
|
+
"state-dir",
|
|
39860
|
+
"turn-budget"
|
|
39413
39861
|
], 1);
|
|
39414
39862
|
const provider = listenerProvider(args);
|
|
39415
39863
|
validateListenerProviderFlags(args, provider);
|
|
39864
|
+
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
39416
39865
|
const cloud = await target(args);
|
|
39417
39866
|
const workspaceId2 = listenerUuid(args.optional("workspace-id"), "workspace-id");
|
|
39418
39867
|
const principalId = listenerUuid(args.optional("principal-id"), "principal-id");
|
|
@@ -39428,6 +39877,7 @@ async function runListenSupervisor(args) {
|
|
|
39428
39877
|
cwd,
|
|
39429
39878
|
permissionMode: listenerPermissionMode(args.optional("permissions")),
|
|
39430
39879
|
provider,
|
|
39880
|
+
turnBudgetMs,
|
|
39431
39881
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39432
39882
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39433
39883
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39709,6 +40159,48 @@ async function runFileRestore(args) {
|
|
|
39709
40159
|
`
|
|
39710
40160
|
);
|
|
39711
40161
|
}
|
|
40162
|
+
async function runFeedback(args) {
|
|
40163
|
+
const body = args.positionals[1];
|
|
40164
|
+
if (!body) {
|
|
40165
|
+
throw new UsageError(
|
|
40166
|
+
'cswarm feedback needs the feedback text: cswarm feedback "<text>" --kind bug|idea|friction'
|
|
40167
|
+
);
|
|
40168
|
+
}
|
|
40169
|
+
const kind = args.required("kind");
|
|
40170
|
+
if (kind !== "bug" && kind !== "idea" && kind !== "friction") {
|
|
40171
|
+
throw new UsageError("--kind must be bug, idea, or friction");
|
|
40172
|
+
}
|
|
40173
|
+
const about = args.optional("about");
|
|
40174
|
+
const context = await fileContext(args, ["kind", "about"], 2);
|
|
40175
|
+
const submitted = await submitFeedback({
|
|
40176
|
+
target: context.cloud,
|
|
40177
|
+
workspaceId: context.selected.selectedWorkspace,
|
|
40178
|
+
credential: context.selected.bearer
|
|
40179
|
+
}, {
|
|
40180
|
+
category: kind,
|
|
40181
|
+
body,
|
|
40182
|
+
context: {
|
|
40183
|
+
surface: "cli",
|
|
40184
|
+
cswarm_version: CLI_BUILD_VERSION,
|
|
40185
|
+
platform: process.platform,
|
|
40186
|
+
...about ? { about } : {}
|
|
40187
|
+
}
|
|
40188
|
+
});
|
|
40189
|
+
if (args.has("json")) {
|
|
40190
|
+
process.stdout.write(`${JSON.stringify(submitted, null, 2)}
|
|
40191
|
+
`);
|
|
40192
|
+
return;
|
|
40193
|
+
}
|
|
40194
|
+
if (submitted.duplicate === true) {
|
|
40195
|
+
process.stdout.write(
|
|
40196
|
+
"This matches feedback you sent within the hour, so it was not recorded twice. It is already with the operators of this deployment.\n"
|
|
40197
|
+
);
|
|
40198
|
+
return;
|
|
40199
|
+
}
|
|
40200
|
+
process.stdout.write(
|
|
40201
|
+
"Feedback recorded for the operators of this deployment. It is stored durably with your workspace and identity attached, and it is read when they review feedback - there is no reply channel, so nothing further will happen in this session.\n"
|
|
40202
|
+
);
|
|
40203
|
+
}
|
|
39712
40204
|
async function runFile(args) {
|
|
39713
40205
|
const action = args.positionals[1];
|
|
39714
40206
|
if (action === "put") return await runFilePut(args);
|
|
@@ -39956,6 +40448,10 @@ async function main() {
|
|
|
39956
40448
|
await runMember(args);
|
|
39957
40449
|
return;
|
|
39958
40450
|
}
|
|
40451
|
+
if (verb === "workspace") {
|
|
40452
|
+
await runWorkspace(args);
|
|
40453
|
+
return;
|
|
40454
|
+
}
|
|
39959
40455
|
if (verb === "target") {
|
|
39960
40456
|
await runTarget(args);
|
|
39961
40457
|
return;
|
|
@@ -39964,6 +40460,10 @@ async function main() {
|
|
|
39964
40460
|
await runStatus(args);
|
|
39965
40461
|
return;
|
|
39966
40462
|
}
|
|
40463
|
+
if (verb === "feedback") {
|
|
40464
|
+
await runFeedback(args);
|
|
40465
|
+
return;
|
|
40466
|
+
}
|
|
39967
40467
|
if (verb === "file") {
|
|
39968
40468
|
await runFile(args);
|
|
39969
40469
|
return;
|
|
@@ -40089,12 +40589,16 @@ ${usage()}
|
|
|
40089
40589
|
// Annotate the CommonJS export names for ESM import in node:
|
|
40090
40590
|
0 && (module.exports = {
|
|
40091
40591
|
EXIT_RESTARTABLE,
|
|
40592
|
+
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
40593
|
+
clampTurnBudgetToCredential,
|
|
40092
40594
|
describeAudience,
|
|
40093
40595
|
listenerFailureMessage,
|
|
40094
40596
|
listenerHostLimits,
|
|
40095
40597
|
listenerPermissionMode,
|
|
40096
40598
|
listenerStatusJson,
|
|
40097
40599
|
renderRoster,
|
|
40600
|
+
replyRefusalHint,
|
|
40098
40601
|
resolveDetachedClaudeExecutable,
|
|
40099
|
-
resolveDetachedCodexExecutable
|
|
40602
|
+
resolveDetachedCodexExecutable,
|
|
40603
|
+
resolveTurnBudgetOrDefer
|
|
40100
40604
|
});
|