commonswarm 0.1.21 → 0.1.22
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 +438 -32
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -13503,6 +13503,8 @@ 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,
|
|
@@ -13510,7 +13512,8 @@ __export(cli_exports, {
|
|
|
13510
13512
|
listenerStatusJson: () => listenerStatusJson,
|
|
13511
13513
|
renderRoster: () => renderRoster,
|
|
13512
13514
|
resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
|
|
13513
|
-
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable
|
|
13515
|
+
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable,
|
|
13516
|
+
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer
|
|
13514
13517
|
});
|
|
13515
13518
|
module.exports = __toCommonJS(cli_exports);
|
|
13516
13519
|
var import_node_crypto19 = require("node:crypto");
|
|
@@ -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");
|
|
@@ -29373,6 +29437,59 @@ async function runInboxFollow(options) {
|
|
|
29373
29437
|
// src/host/opencode.ts
|
|
29374
29438
|
var import_node_child_process3 = require("node:child_process");
|
|
29375
29439
|
var import_node_crypto12 = require("node:crypto");
|
|
29440
|
+
|
|
29441
|
+
// src/host/stderr-tail.ts
|
|
29442
|
+
var RING_CAPACITY_BYTES = 4096;
|
|
29443
|
+
var TAIL_MAX_CHARS = 2048;
|
|
29444
|
+
var EXOTIC_SEPARATORS = "\\u00a0\\u1680\\u2000-\\u200d\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\u202f\\u205f\\u3000\\ufeff";
|
|
29445
|
+
var SEPARATOR_CLASS_SOURCE = "\\t\\n\\x0b\\f\\r " + EXOTIC_SEPARATORS;
|
|
29446
|
+
var ANSI_ESCAPE_GLOBAL_RE2 = new RegExp("\\u001b\\[[0-?]*[ -\\/]*[@-~]", "g");
|
|
29447
|
+
var CONTROL_AND_SEPARATOR_STRIP_RE = new RegExp(
|
|
29448
|
+
"[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f" + EXOTIC_SEPARATORS + "]",
|
|
29449
|
+
"g"
|
|
29450
|
+
);
|
|
29451
|
+
var CREDENTIAL_PREFIX_RE = new RegExp(
|
|
29452
|
+
`swm_(?:agt|inv|cap)_[^${SEPARATOR_CLASS_SOURCE}]*`,
|
|
29453
|
+
"gi"
|
|
29454
|
+
);
|
|
29455
|
+
function sanitizeStderrTail(raw) {
|
|
29456
|
+
return raw.replace(ANSI_ESCAPE_GLOBAL_RE2, "").replace(CONTROL_AND_SEPARATOR_STRIP_RE, "").replace(CREDENTIAL_PREFIX_RE, "[redacted-credential]").slice(-TAIL_MAX_CHARS).trim();
|
|
29457
|
+
}
|
|
29458
|
+
function attachStderrTailRing(stderr) {
|
|
29459
|
+
const chunks = [];
|
|
29460
|
+
let total = 0;
|
|
29461
|
+
let evicted = false;
|
|
29462
|
+
stderr.on("data", (chunk) => {
|
|
29463
|
+
const buffer2 = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
29464
|
+
chunks.push(buffer2);
|
|
29465
|
+
total += buffer2.length;
|
|
29466
|
+
while (total > RING_CAPACITY_BYTES && chunks.length > 0) {
|
|
29467
|
+
evicted = true;
|
|
29468
|
+
const head2 = chunks[0];
|
|
29469
|
+
const excess = total - RING_CAPACITY_BYTES;
|
|
29470
|
+
if (head2.length <= excess) {
|
|
29471
|
+
chunks.shift();
|
|
29472
|
+
total -= head2.length;
|
|
29473
|
+
} else {
|
|
29474
|
+
chunks[0] = head2.subarray(excess);
|
|
29475
|
+
total -= excess;
|
|
29476
|
+
}
|
|
29477
|
+
}
|
|
29478
|
+
});
|
|
29479
|
+
stderr.resume();
|
|
29480
|
+
return {
|
|
29481
|
+
read() {
|
|
29482
|
+
let text = Buffer.concat(chunks).toString("utf8");
|
|
29483
|
+
if (evicted) {
|
|
29484
|
+
const newline = text.indexOf("\n");
|
|
29485
|
+
text = newline === -1 ? "" : text.slice(newline + 1);
|
|
29486
|
+
}
|
|
29487
|
+
return sanitizeStderrTail(text);
|
|
29488
|
+
}
|
|
29489
|
+
};
|
|
29490
|
+
}
|
|
29491
|
+
|
|
29492
|
+
// src/host/opencode.ts
|
|
29376
29493
|
var import_node_fs3 = require("node:fs");
|
|
29377
29494
|
var import_promises4 = require("node:fs/promises");
|
|
29378
29495
|
var import_node_os4 = require("node:os");
|
|
@@ -31027,8 +31144,18 @@ async function openOpenCodeAcpSession(options) {
|
|
|
31027
31144
|
await disposeHome();
|
|
31028
31145
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31029
31146
|
}
|
|
31030
|
-
child.stderr
|
|
31031
|
-
|
|
31147
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31148
|
+
if (options.onStderrTail) {
|
|
31149
|
+
const deliverTail = options.onStderrTail;
|
|
31150
|
+
let tailDelivered = false;
|
|
31151
|
+
const publishTail = () => {
|
|
31152
|
+
if (tailDelivered) return;
|
|
31153
|
+
tailDelivered = true;
|
|
31154
|
+
deliverTail(stderrTail.read());
|
|
31155
|
+
};
|
|
31156
|
+
child.once("exit", publishTail);
|
|
31157
|
+
child.once("close", publishTail);
|
|
31158
|
+
}
|
|
31032
31159
|
let sessionRef = null;
|
|
31033
31160
|
const transport = createBoundTransport({
|
|
31034
31161
|
readable: child.stdout,
|
|
@@ -31310,8 +31437,18 @@ async function openClaudeAcpSession(options) {
|
|
|
31310
31437
|
await terminateClaudeChild(child);
|
|
31311
31438
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31312
31439
|
}
|
|
31313
|
-
child.stderr
|
|
31314
|
-
|
|
31440
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31441
|
+
if (options.onStderrTail) {
|
|
31442
|
+
const deliverTail = options.onStderrTail;
|
|
31443
|
+
let tailDelivered = false;
|
|
31444
|
+
const publishTail = () => {
|
|
31445
|
+
if (tailDelivered) return;
|
|
31446
|
+
tailDelivered = true;
|
|
31447
|
+
deliverTail(stderrTail.read());
|
|
31448
|
+
};
|
|
31449
|
+
child.once("exit", publishTail);
|
|
31450
|
+
child.once("close", publishTail);
|
|
31451
|
+
}
|
|
31315
31452
|
let sessionRef = null;
|
|
31316
31453
|
const transport = createBoundTransport({
|
|
31317
31454
|
readable: child.stdout,
|
|
@@ -31586,8 +31723,18 @@ async function openCodexAcpSession(options) {
|
|
|
31586
31723
|
await terminateCodexChild(child);
|
|
31587
31724
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
31588
31725
|
}
|
|
31589
|
-
child.stderr
|
|
31590
|
-
|
|
31726
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
31727
|
+
if (options.onStderrTail) {
|
|
31728
|
+
const deliverTail = options.onStderrTail;
|
|
31729
|
+
let tailDelivered = false;
|
|
31730
|
+
const publishTail = () => {
|
|
31731
|
+
if (tailDelivered) return;
|
|
31732
|
+
tailDelivered = true;
|
|
31733
|
+
deliverTail(stderrTail.read());
|
|
31734
|
+
};
|
|
31735
|
+
child.once("exit", publishTail);
|
|
31736
|
+
child.once("close", publishTail);
|
|
31737
|
+
}
|
|
31591
31738
|
let sessionRef = null;
|
|
31592
31739
|
const transport = createBoundTransport({
|
|
31593
31740
|
readable: child.stdout,
|
|
@@ -31637,6 +31784,19 @@ async function openCodexAcpSession(options) {
|
|
|
31637
31784
|
}
|
|
31638
31785
|
}
|
|
31639
31786
|
|
|
31787
|
+
// src/listener/types.ts
|
|
31788
|
+
var LISTENER_PROMPT_TIMEOUT_MS = 6e5;
|
|
31789
|
+
var ListenerRenewalUnavailableError = class extends Error {
|
|
31790
|
+
constructor(message) {
|
|
31791
|
+
super(message);
|
|
31792
|
+
this.name = "renewal_unavailable";
|
|
31793
|
+
}
|
|
31794
|
+
};
|
|
31795
|
+
async function resolveBudgetAndPrompt(session, prompt, budget) {
|
|
31796
|
+
const timeoutMs = typeof budget === "number" ? budget : await budget();
|
|
31797
|
+
return await session.prompt(prompt, { timeoutMs });
|
|
31798
|
+
}
|
|
31799
|
+
|
|
31640
31800
|
// src/listener/engine.ts
|
|
31641
31801
|
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
31802
|
var TERMINAL_STATES = /* @__PURE__ */ new Set(["done", "expired", "failed"]);
|
|
@@ -31751,6 +31911,7 @@ function abortError() {
|
|
|
31751
31911
|
}
|
|
31752
31912
|
function defaultRetryablePromptError(error) {
|
|
31753
31913
|
if (error instanceof SenderProvenanceUnavailableError) return true;
|
|
31914
|
+
if (error instanceof ListenerRenewalUnavailableError) return true;
|
|
31754
31915
|
if (error instanceof AcpHostError) return TRANSIENT_ACP_CODES.has(error.code);
|
|
31755
31916
|
return false;
|
|
31756
31917
|
}
|
|
@@ -32517,8 +32678,18 @@ async function openGrokAcpSession(options) {
|
|
|
32517
32678
|
child.kill("SIGKILL");
|
|
32518
32679
|
throw new AcpHostError("spawn_failed", "child missing stdio pipes");
|
|
32519
32680
|
}
|
|
32520
|
-
child.stderr
|
|
32521
|
-
|
|
32681
|
+
const stderrTail = attachStderrTailRing(child.stderr);
|
|
32682
|
+
if (options.onStderrTail) {
|
|
32683
|
+
const deliverTail = options.onStderrTail;
|
|
32684
|
+
let tailDelivered = false;
|
|
32685
|
+
const publishTail = () => {
|
|
32686
|
+
if (tailDelivered) return;
|
|
32687
|
+
tailDelivered = true;
|
|
32688
|
+
deliverTail(stderrTail.read());
|
|
32689
|
+
};
|
|
32690
|
+
child.once("exit", publishTail);
|
|
32691
|
+
child.once("close", publishTail);
|
|
32692
|
+
}
|
|
32522
32693
|
let sessionRef = null;
|
|
32523
32694
|
const transport = createBoundTransport({
|
|
32524
32695
|
readable: child.stdout,
|
|
@@ -32583,8 +32754,9 @@ var GrokListenerModel = class {
|
|
|
32583
32754
|
async prompt(_signal, _mode, prompt) {
|
|
32584
32755
|
if (this.closed) throw new Error("listener model is closed");
|
|
32585
32756
|
const worker = await this.ensureWorker();
|
|
32757
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
32586
32758
|
try {
|
|
32587
|
-
return await worker.session
|
|
32759
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
32588
32760
|
} catch (error) {
|
|
32589
32761
|
if (error instanceof AcpChildExitError) {
|
|
32590
32762
|
try {
|
|
@@ -32619,6 +32791,7 @@ var GrokListenerModel = class {
|
|
|
32619
32791
|
...this.options.model ? { model: this.options.model } : {},
|
|
32620
32792
|
...this.options.effort ? { effort: this.options.effort } : {},
|
|
32621
32793
|
...this.options.env ? { env: this.options.env } : {},
|
|
32794
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
32622
32795
|
clientName: "cswarm-listener"
|
|
32623
32796
|
});
|
|
32624
32797
|
try {
|
|
@@ -32735,8 +32908,9 @@ var OpenCodeListenerModel = class {
|
|
|
32735
32908
|
async prompt(_signal, _mode, prompt) {
|
|
32736
32909
|
if (this.closed) throw new Error("listener model is closed");
|
|
32737
32910
|
const worker = await this.ensureWorker();
|
|
32911
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
32738
32912
|
try {
|
|
32739
|
-
return await worker.session
|
|
32913
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
32740
32914
|
} catch (error) {
|
|
32741
32915
|
if (error instanceof AcpChildExitError) {
|
|
32742
32916
|
const home = this.workerHome;
|
|
@@ -33026,6 +33200,7 @@ var OpenCodeListenerModel = class {
|
|
|
33026
33200
|
...this.options.model ? { model: this.options.model } : {},
|
|
33027
33201
|
...this.options.env ? { env: this.options.env } : {},
|
|
33028
33202
|
...this.options.allowMissingAuth === true ? { allowMissingAuth: true } : {},
|
|
33203
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33029
33204
|
clientName: "cswarm-listener"
|
|
33030
33205
|
});
|
|
33031
33206
|
pending.phase = "opening";
|
|
@@ -33150,8 +33325,9 @@ var ClaudeListenerModel = class {
|
|
|
33150
33325
|
async prompt(_signal, _mode, prompt) {
|
|
33151
33326
|
if (this.closed) throw new Error("listener model is closed");
|
|
33152
33327
|
const worker = await this.ensureWorker();
|
|
33328
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
33153
33329
|
try {
|
|
33154
|
-
return await worker.session
|
|
33330
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
33155
33331
|
} catch (error) {
|
|
33156
33332
|
if (error instanceof AcpChildExitError) {
|
|
33157
33333
|
try {
|
|
@@ -33233,6 +33409,7 @@ var ClaudeListenerModel = class {
|
|
|
33233
33409
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
33234
33410
|
...this.options.env ? { env: this.options.env } : {},
|
|
33235
33411
|
signal: controller.signal,
|
|
33412
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33236
33413
|
clientName: "cswarm-listener"
|
|
33237
33414
|
});
|
|
33238
33415
|
this.openingHandle = handle;
|
|
@@ -33316,8 +33493,9 @@ var CodexListenerModel = class {
|
|
|
33316
33493
|
async prompt(_signal, _mode, prompt) {
|
|
33317
33494
|
if (this.closed) throw new Error("listener model is closed");
|
|
33318
33495
|
const worker = await this.ensureWorker();
|
|
33496
|
+
const budget = this.options.promptTimeoutMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
33319
33497
|
try {
|
|
33320
|
-
return await worker.session
|
|
33498
|
+
return await resolveBudgetAndPrompt(worker.session, prompt, budget);
|
|
33321
33499
|
} catch (error) {
|
|
33322
33500
|
if (error instanceof AcpChildExitError) {
|
|
33323
33501
|
try {
|
|
@@ -33399,6 +33577,7 @@ var CodexListenerModel = class {
|
|
|
33399
33577
|
...this.options.executable ? { executable: this.options.executable } : {},
|
|
33400
33578
|
...this.options.env ? { env: this.options.env } : {},
|
|
33401
33579
|
signal: controller.signal,
|
|
33580
|
+
...this.options.onWorkerStderrTail ? { onStderrTail: this.options.onWorkerStderrTail } : {},
|
|
33402
33581
|
clientName: "cswarm-listener"
|
|
33403
33582
|
});
|
|
33404
33583
|
this.openingHandle = handle;
|
|
@@ -35043,6 +35222,7 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
35043
35222
|
"stoppedAt",
|
|
35044
35223
|
"lastSignalId",
|
|
35045
35224
|
"lastErrorCode",
|
|
35225
|
+
"lastWorkerStderrTail",
|
|
35046
35226
|
"logPath",
|
|
35047
35227
|
"deliveryMode",
|
|
35048
35228
|
"pendingDeliveryCount",
|
|
@@ -35097,7 +35277,7 @@ function parseStatus(raw) {
|
|
|
35097
35277
|
const nullableUuid2 = (candidate) => candidate === null || typeof candidate === "string" && UUID_RE12.test(candidate);
|
|
35098
35278
|
const nullableCount = (candidate) => candidate === null || typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0;
|
|
35099
35279
|
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))) {
|
|
35280
|
+
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
35281
|
throw new Error("stored listener status is malformed");
|
|
35102
35282
|
}
|
|
35103
35283
|
return {
|
|
@@ -35107,7 +35287,8 @@ function parseStatus(raw) {
|
|
|
35107
35287
|
lastTerminalDeliveryFailureCount: row.lastTerminalDeliveryFailureCount ?? null,
|
|
35108
35288
|
lastTerminalDeliveryFailureAt: row.lastTerminalDeliveryFailureAt ?? null,
|
|
35109
35289
|
lastClaimAt: row.lastClaimAt ?? null,
|
|
35110
|
-
lastAckAt: row.lastAckAt ?? null
|
|
35290
|
+
lastAckAt: row.lastAckAt ?? null,
|
|
35291
|
+
lastWorkerStderrTail: row.lastWorkerStderrTail ?? null
|
|
35111
35292
|
};
|
|
35112
35293
|
}
|
|
35113
35294
|
async function writeListenerStatus(paths, status) {
|
|
@@ -35144,7 +35325,12 @@ async function appendListenerEvent(paths, event) {
|
|
|
35144
35325
|
// D-051 companion 2: why a listener is down, and why it stopped trying.
|
|
35145
35326
|
"restart_attempts",
|
|
35146
35327
|
"restartable",
|
|
35147
|
-
"restarts_exhausted"
|
|
35328
|
+
"restarts_exhausted",
|
|
35329
|
+
// D-090 family: the last lines of a dead worker's stderr, sanitized and
|
|
35330
|
+
// bounded by the supervisor, and the prompt-turn budget behind a timeout.
|
|
35331
|
+
// Local log only — this file never feeds a server payload.
|
|
35332
|
+
"worker_stderr_tail",
|
|
35333
|
+
"turn_budget_ms"
|
|
35148
35334
|
]);
|
|
35149
35335
|
const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
|
|
35150
35336
|
const deliveryOutcomes = /* @__PURE__ */ new Set([
|
|
@@ -35166,7 +35352,16 @@ async function appendListenerEvent(paths, event) {
|
|
|
35166
35352
|
if (key2 === "outcome" && !(value === null || typeof value === "string" && deliveryOutcomes.has(value))) {
|
|
35167
35353
|
throw new Error("listener event outcome is not allowed");
|
|
35168
35354
|
}
|
|
35169
|
-
if (typeof value === "string" &&
|
|
35355
|
+
if (key2 === "worker_stderr_tail" && !(typeof value === "string" && value.length > 0 && value.length <= 2048)) {
|
|
35356
|
+
throw new Error("listener event stderr tail is not allowed");
|
|
35357
|
+
}
|
|
35358
|
+
if (key2 === "turn_budget_ms" && !(typeof value === "number" && Number.isSafeInteger(value) && value > 0)) {
|
|
35359
|
+
throw new Error("listener event turn budget is not allowed");
|
|
35360
|
+
}
|
|
35361
|
+
if (typeof value === "string" && // worker_stderr_tail is deliberately exempt from the generic 128-char
|
|
35362
|
+
// cap (its own bound is 2048, above); the secret scan still applies to
|
|
35363
|
+
// every string, the tail included.
|
|
35364
|
+
(key2 !== "worker_stderr_tail" && value.length > 128 || /swm_(?:agt|inv|cap)_/i.test(value))) {
|
|
35170
35365
|
throw new Error("listener event contains unsafe text");
|
|
35171
35366
|
}
|
|
35172
35367
|
}
|
|
@@ -35443,6 +35638,23 @@ function safeErrorCode(error) {
|
|
|
35443
35638
|
const name = error.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "_");
|
|
35444
35639
|
return name.slice(0, 96) || "listener_error";
|
|
35445
35640
|
}
|
|
35641
|
+
var TAIL_SERIALIZED_BUDGET_BYTES = 3e3;
|
|
35642
|
+
function fitWorkerStderrTailForLog(tail) {
|
|
35643
|
+
let fitted = tail.trim();
|
|
35644
|
+
for (; ; ) {
|
|
35645
|
+
if (fitted.length === 0) return fitted;
|
|
35646
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(fitted), "utf8");
|
|
35647
|
+
if (fitted.length <= 2048 && serializedBytes <= TAIL_SERIALIZED_BUDGET_BYTES) {
|
|
35648
|
+
return fitted;
|
|
35649
|
+
}
|
|
35650
|
+
const dropChars = Math.max(
|
|
35651
|
+
fitted.length - 2048,
|
|
35652
|
+
Math.ceil((serializedBytes - TAIL_SERIALIZED_BUDGET_BYTES) / 6),
|
|
35653
|
+
1
|
|
35654
|
+
);
|
|
35655
|
+
fitted = fitted.slice(dropChars);
|
|
35656
|
+
}
|
|
35657
|
+
}
|
|
35446
35658
|
async function runListenerSupervisor(options) {
|
|
35447
35659
|
const now = options.now ?? Date.now;
|
|
35448
35660
|
const startedAt = iso2(now);
|
|
@@ -35464,6 +35676,7 @@ async function runListenerSupervisor(options) {
|
|
|
35464
35676
|
stoppedAt: null,
|
|
35465
35677
|
lastSignalId: null,
|
|
35466
35678
|
lastErrorCode: null,
|
|
35679
|
+
lastWorkerStderrTail: null,
|
|
35467
35680
|
deliveryMode: null,
|
|
35468
35681
|
pendingDeliveryCount: null,
|
|
35469
35682
|
lastTerminalDeliveryFailureCount: null,
|
|
@@ -35518,9 +35731,21 @@ async function runListenerSupervisor(options) {
|
|
|
35518
35731
|
instance_id: status.instanceId,
|
|
35519
35732
|
pid: process.pid
|
|
35520
35733
|
});
|
|
35734
|
+
const takeWorkerStderrTail = options.takeWorkerStderrTail;
|
|
35735
|
+
const takeTail = () => {
|
|
35736
|
+
if (!takeWorkerStderrTail) return null;
|
|
35737
|
+
const tail = takeWorkerStderrTail();
|
|
35738
|
+
if (typeof tail !== "string") return null;
|
|
35739
|
+
const fitted = fitWorkerStderrTailForLog(tail);
|
|
35740
|
+
return fitted.length > 0 ? fitted : null;
|
|
35741
|
+
};
|
|
35521
35742
|
const onEvent = (event) => {
|
|
35522
35743
|
if (event.type === "ready") {
|
|
35523
|
-
transition("ready", {
|
|
35744
|
+
transition("ready", {
|
|
35745
|
+
readyAt: event.ts,
|
|
35746
|
+
lastErrorCode: null,
|
|
35747
|
+
lastWorkerStderrTail: null
|
|
35748
|
+
});
|
|
35524
35749
|
log({ ts: event.ts, event: "listener_ready" });
|
|
35525
35750
|
return;
|
|
35526
35751
|
}
|
|
@@ -35537,7 +35762,15 @@ async function runListenerSupervisor(options) {
|
|
|
35537
35762
|
event: "listener_effect",
|
|
35538
35763
|
signal_id: event.signalId,
|
|
35539
35764
|
status: event.status,
|
|
35540
|
-
failure_code: event.failureCode
|
|
35765
|
+
failure_code: event.failureCode,
|
|
35766
|
+
// Code comparison, not message matching (D-053). The budget rides
|
|
35767
|
+
// only the timeout class so a reader can see what bound was hit — and
|
|
35768
|
+
// it is the CLAMPED budget actually in force, not the configured cap.
|
|
35769
|
+
...(() => {
|
|
35770
|
+
if (event.failureCode !== "acptimeouterror") return {};
|
|
35771
|
+
const budget = options.getTurnBudgetMs?.();
|
|
35772
|
+
return typeof budget === "number" && budget > 0 ? { turn_budget_ms: budget } : {};
|
|
35773
|
+
})()
|
|
35541
35774
|
});
|
|
35542
35775
|
return;
|
|
35543
35776
|
}
|
|
@@ -35667,14 +35900,20 @@ async function runListenerSupervisor(options) {
|
|
|
35667
35900
|
restarts += 1;
|
|
35668
35901
|
const delayMs = nextListenerRestartMs(restarts, policy, restartRandom);
|
|
35669
35902
|
const restartCode = safeErrorCode(stop.error);
|
|
35903
|
+
const restartStderrTail = takeTail();
|
|
35670
35904
|
log({
|
|
35671
35905
|
ts: iso2(now),
|
|
35672
35906
|
event: "listener_restarting",
|
|
35673
35907
|
attempt: restarts,
|
|
35674
35908
|
delay_ms: delayMs,
|
|
35675
|
-
failure_code: restartCode
|
|
35909
|
+
failure_code: restartCode,
|
|
35910
|
+
...restartStderrTail !== null ? { worker_stderr_tail: restartStderrTail } : {}
|
|
35911
|
+
});
|
|
35912
|
+
transition("starting", {
|
|
35913
|
+
readyAt: null,
|
|
35914
|
+
lastErrorCode: restartCode,
|
|
35915
|
+
lastWorkerStderrTail: restartStderrTail
|
|
35676
35916
|
});
|
|
35677
|
-
transition("starting", { readyAt: null, lastErrorCode: restartCode });
|
|
35678
35917
|
await restartSleep(delayMs, controller.signal);
|
|
35679
35918
|
if (controller.signal.aborted) {
|
|
35680
35919
|
stop = { reason: "cancelled" };
|
|
@@ -35685,14 +35924,17 @@ async function runListenerSupervisor(options) {
|
|
|
35685
35924
|
if (stop.reason === "cancelled") {
|
|
35686
35925
|
transition("stopped", {
|
|
35687
35926
|
stoppedAt,
|
|
35688
|
-
lastErrorCode: null
|
|
35927
|
+
lastErrorCode: null,
|
|
35928
|
+
lastWorkerStderrTail: null
|
|
35689
35929
|
});
|
|
35690
35930
|
log({ ts: stoppedAt, event: "listener_stopped" });
|
|
35691
35931
|
} else {
|
|
35692
35932
|
const code = stop.reason === "credential" ? "credential_stopped" : safeErrorCode(stop.error);
|
|
35933
|
+
const failedStderrTail = takeTail();
|
|
35693
35934
|
transition("failed", {
|
|
35694
35935
|
stoppedAt,
|
|
35695
|
-
lastErrorCode: code
|
|
35936
|
+
lastErrorCode: code,
|
|
35937
|
+
lastWorkerStderrTail: failedStderrTail
|
|
35696
35938
|
});
|
|
35697
35939
|
log({
|
|
35698
35940
|
ts: stoppedAt,
|
|
@@ -35700,7 +35942,8 @@ async function runListenerSupervisor(options) {
|
|
|
35700
35942
|
failure_code: code,
|
|
35701
35943
|
restart_attempts: restarts,
|
|
35702
35944
|
restartable: eligible,
|
|
35703
|
-
restarts_exhausted: exhausted
|
|
35945
|
+
restarts_exhausted: exhausted,
|
|
35946
|
+
...failedStderrTail !== null ? { worker_stderr_tail: failedStderrTail } : {}
|
|
35704
35947
|
});
|
|
35705
35948
|
}
|
|
35706
35949
|
} catch (error) {
|
|
@@ -35708,11 +35951,17 @@ async function runListenerSupervisor(options) {
|
|
|
35708
35951
|
const code = safeErrorCode(
|
|
35709
35952
|
error instanceof Error ? error : new Error(String(error))
|
|
35710
35953
|
);
|
|
35711
|
-
|
|
35954
|
+
const failedStderrTail = takeTail();
|
|
35955
|
+
transition("failed", {
|
|
35956
|
+
stoppedAt,
|
|
35957
|
+
lastErrorCode: code,
|
|
35958
|
+
lastWorkerStderrTail: failedStderrTail
|
|
35959
|
+
});
|
|
35712
35960
|
log({
|
|
35713
35961
|
ts: stoppedAt,
|
|
35714
35962
|
event: "listener_failed",
|
|
35715
|
-
failure_code: code
|
|
35963
|
+
failure_code: code,
|
|
35964
|
+
...failedStderrTail !== null ? { worker_stderr_tail: failedStderrTail } : {}
|
|
35716
35965
|
});
|
|
35717
35966
|
} finally {
|
|
35718
35967
|
await writes.catch(() => void 0);
|
|
@@ -36564,7 +36813,8 @@ function buildListenerChildArgs(spec) {
|
|
|
36564
36813
|
...provider === "claude" && claudeExe ? ["--claude-executable", claudeExe] : [],
|
|
36565
36814
|
...provider === "codex" && codexExe ? ["--codex-executable", codexExe] : [],
|
|
36566
36815
|
...spec.model ? ["--model", spec.model] : [],
|
|
36567
|
-
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : []
|
|
36816
|
+
...provider === "grok" && spec.effort ? ["--effort", spec.effort] : [],
|
|
36817
|
+
...spec.turnBudget ? ["--turn-budget", spec.turnBudget] : []
|
|
36568
36818
|
];
|
|
36569
36819
|
}
|
|
36570
36820
|
async function spawnDetachedListener(options) {
|
|
@@ -36649,6 +36899,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
36649
36899
|
"to",
|
|
36650
36900
|
"token-id",
|
|
36651
36901
|
"ttl-ms",
|
|
36902
|
+
"turn-budget",
|
|
36652
36903
|
"uid",
|
|
36653
36904
|
"until",
|
|
36654
36905
|
"url",
|
|
@@ -36682,8 +36933,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
36682
36933
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
36683
36934
|
];
|
|
36684
36935
|
function packageVersion() {
|
|
36685
|
-
if ("0.1.
|
|
36686
|
-
return "0.1.
|
|
36936
|
+
if ("0.1.22".length > 0) {
|
|
36937
|
+
return "0.1.22";
|
|
36687
36938
|
}
|
|
36688
36939
|
try {
|
|
36689
36940
|
const value = JSON.parse(
|
|
@@ -36810,7 +37061,8 @@ Usage:
|
|
|
36810
37061
|
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
37062
|
cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
36812
37063
|
cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
|
|
36813
|
-
cswarm
|
|
37064
|
+
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]
|
|
36814
37066
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
36815
37067
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
36816
37068
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
@@ -36845,14 +37097,29 @@ Credential selection for command/dogfood:
|
|
|
36845
37097
|
members reads only -- either form
|
|
36846
37098
|
file put, file ls, file get, file rm, file restore
|
|
36847
37099
|
read and command, nothing persisted -- either form
|
|
37100
|
+
feedback command only, nothing persisted -- either form
|
|
36848
37101
|
listen start persists durable state, rotates -- needs expires_at
|
|
36849
37102
|
token revoke names what it revokes -- needs token_id
|
|
36850
37103
|
|
|
37104
|
+
Found a bug or missing feature in cswarm itself? cswarm feedback sends it to the
|
|
37105
|
+
deployment's operators \u2014 agents are encouraged to report friction they hit.
|
|
37106
|
+
|
|
36851
37107
|
Signals (intention sharing) accept the same credential selection. Agent mode
|
|
36852
37108
|
never opens a browser or infers a human's saved workspace. Durations use a whole
|
|
36853
37109
|
number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
|
|
36854
37110
|
Place -- before signal text that itself begins with -- to stop option parsing.
|
|
36855
37111
|
|
|
37112
|
+
listen start --turn-budget bounds ONE worker prompt turn (default 10m): how long
|
|
37113
|
+
the worker may think and use tools on a single message before the turn times out
|
|
37114
|
+
and durable delivery retries it. A whole number plus s, m, or h (for example
|
|
37115
|
+
90s, 5m, 1h), at least 30s and at most 60m. Each turn is additionally clamped
|
|
37116
|
+
to the live credential's remaining lifetime minus 60s, after renewing it when
|
|
37117
|
+
due \u2014 a turn never outlives its credential. Right after a rotation the full
|
|
37118
|
+
budget is available up to the token TTL minus 60s (about 59m on the default 1h
|
|
37119
|
+
TTL); a turn that lands just before a rotation can be clamped to the ~5m
|
|
37120
|
+
renewal lead, and if it times out there, durable delivery retries it on the
|
|
37121
|
+
fresh credential.
|
|
37122
|
+
|
|
36856
37123
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, and new require a
|
|
36857
37124
|
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
37125
|
registers one principal. Invitation links, agent credentials, and capability links
|
|
@@ -38208,6 +38475,38 @@ function signalDuration(value) {
|
|
|
38208
38475
|
}
|
|
38209
38476
|
return milliseconds;
|
|
38210
38477
|
}
|
|
38478
|
+
function listenerTurnBudgetMs(value) {
|
|
38479
|
+
if (value === void 0) return LISTENER_PROMPT_TIMEOUT_MS;
|
|
38480
|
+
const match = /^([1-9]\d*)(s|m|h)$/.exec(value);
|
|
38481
|
+
if (!match) {
|
|
38482
|
+
throw new Error("--turn-budget must be a duration such as 90s, 5m, or 1h");
|
|
38483
|
+
}
|
|
38484
|
+
const unit = match[2] === "s" ? 1e3 : match[2] === "m" ? 6e4 : 36e5;
|
|
38485
|
+
const milliseconds = Number(match[1]) * unit;
|
|
38486
|
+
if (!Number.isSafeInteger(milliseconds) || milliseconds < 3e4 || milliseconds > 36e5) {
|
|
38487
|
+
throw new Error("--turn-budget must be between 30s and 60m");
|
|
38488
|
+
}
|
|
38489
|
+
return milliseconds;
|
|
38490
|
+
}
|
|
38491
|
+
var TURN_BUDGET_CREDENTIAL_MARGIN_MS = 6e4;
|
|
38492
|
+
function clampTurnBudgetToCredential(budgetMs, credentialExpiresAt, nowMs) {
|
|
38493
|
+
if (credentialExpiresAt === null) return budgetMs;
|
|
38494
|
+
const horizonMs = credentialExpiresAt - nowMs - TURN_BUDGET_CREDENTIAL_MARGIN_MS;
|
|
38495
|
+
return Math.max(1e3, Math.min(budgetMs, horizonMs));
|
|
38496
|
+
}
|
|
38497
|
+
function resolveTurnBudgetOrDefer(configuredBudgetMs, credentialExpiresAt, nowMs, renewalFailed) {
|
|
38498
|
+
if (renewalFailed) {
|
|
38499
|
+
throw new ListenerRenewalUnavailableError(
|
|
38500
|
+
"the worker credential could not be renewed before this turn; deferring the ask for durable redelivery"
|
|
38501
|
+
);
|
|
38502
|
+
}
|
|
38503
|
+
if (credentialExpiresAt !== null && credentialExpiresAt - nowMs <= TURN_BUDGET_CREDENTIAL_MARGIN_MS) {
|
|
38504
|
+
throw new ListenerRenewalUnavailableError(
|
|
38505
|
+
"the live worker credential is inside its rotation margin and was not renewed; deferring the ask for durable redelivery"
|
|
38506
|
+
);
|
|
38507
|
+
}
|
|
38508
|
+
return clampTurnBudgetToCredential(configuredBudgetMs, credentialExpiresAt, nowMs);
|
|
38509
|
+
}
|
|
38211
38510
|
function signalText(value, label) {
|
|
38212
38511
|
const maximum = label === "body" ? 2e3 : 500;
|
|
38213
38512
|
if (value.length < (label === "body" ? 1 : 0) || value.length > maximum) {
|
|
@@ -38990,6 +39289,13 @@ function renderListenerStatus(status) {
|
|
|
38990
39289
|
status.lastSignalId ? `Last handled signal: ${status.lastSignalId}.` : "No signal has been handled yet.",
|
|
38991
39290
|
status.lastErrorCode ? `Last status code: ${status.lastErrorCode}.` : "No listener error is recorded."
|
|
38992
39291
|
];
|
|
39292
|
+
if (status.lastWorkerStderrTail) {
|
|
39293
|
+
const tailLines = status.lastWorkerStderrTail.split("\n").filter((line) => line.trim().length > 0);
|
|
39294
|
+
lines.push("Worker stderr (local log only):");
|
|
39295
|
+
for (const line of tailLines.slice(-3)) {
|
|
39296
|
+
lines.push(` ${line}`);
|
|
39297
|
+
}
|
|
39298
|
+
}
|
|
38993
39299
|
if (status.deliveryMode === "durable_claim") {
|
|
38994
39300
|
lines.push("Delivery mode: durable claim and acknowledgement.");
|
|
38995
39301
|
} else if (status.deliveryMode === "cursor_fallback") {
|
|
@@ -39169,22 +39475,58 @@ async function runConfiguredListener(options) {
|
|
|
39169
39475
|
principalId: options.principalId,
|
|
39170
39476
|
...options.stateDirectory ? { stateDirectory: options.stateDirectory } : {}
|
|
39171
39477
|
});
|
|
39478
|
+
const turnBudgetMs = options.turnBudgetMs ?? LISTENER_PROMPT_TIMEOUT_MS;
|
|
39479
|
+
let lastAppliedTurnBudgetMs = null;
|
|
39480
|
+
const resolveTurnBudgetMs = async () => {
|
|
39481
|
+
let renewalFailed = false;
|
|
39482
|
+
try {
|
|
39483
|
+
await credentialSession.bearer();
|
|
39484
|
+
} catch {
|
|
39485
|
+
renewalFailed = true;
|
|
39486
|
+
}
|
|
39487
|
+
const applied = resolveTurnBudgetOrDefer(
|
|
39488
|
+
turnBudgetMs,
|
|
39489
|
+
credentialSession.expiry,
|
|
39490
|
+
Date.now(),
|
|
39491
|
+
renewalFailed
|
|
39492
|
+
);
|
|
39493
|
+
lastAppliedTurnBudgetMs = applied;
|
|
39494
|
+
return applied;
|
|
39495
|
+
};
|
|
39496
|
+
let lastWorkerStderrTail = null;
|
|
39497
|
+
let workerStderrGeneration = 0;
|
|
39498
|
+
const newWorkerStderrTailSink = () => {
|
|
39499
|
+
const generation = ++workerStderrGeneration;
|
|
39500
|
+
lastWorkerStderrTail = null;
|
|
39501
|
+
return (tail) => {
|
|
39502
|
+
if (generation !== workerStderrGeneration) return;
|
|
39503
|
+
lastWorkerStderrTail = tail.length > 0 ? tail : null;
|
|
39504
|
+
};
|
|
39505
|
+
};
|
|
39172
39506
|
const newModel = () => options.provider === "opencode" ? new OpenCodeListenerModel({
|
|
39173
39507
|
cwd: options.cwd,
|
|
39174
39508
|
permissionMode: options.permissionMode,
|
|
39509
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39510
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39175
39511
|
...options.model ? { model: options.model } : {},
|
|
39176
39512
|
...options.opencodeExecutable ? { executable: options.opencodeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39177
39513
|
}) : options.provider === "claude" ? new ClaudeListenerModel({
|
|
39178
39514
|
cwd: options.cwd,
|
|
39179
39515
|
permissionMode: options.permissionMode,
|
|
39516
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39517
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39180
39518
|
...options.claudeExecutable ? { executable: options.claudeExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39181
39519
|
}) : options.provider === "codex" ? new CodexListenerModel({
|
|
39182
39520
|
cwd: options.cwd,
|
|
39183
39521
|
permissionMode: options.permissionMode,
|
|
39522
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39523
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39184
39524
|
...options.codexExecutable ? { executable: options.codexExecutable } : options.executable ? { executable: options.executable } : {}
|
|
39185
39525
|
}) : new GrokListenerModel({
|
|
39186
39526
|
cwd: options.cwd,
|
|
39187
39527
|
permissionMode: options.permissionMode,
|
|
39528
|
+
promptTimeoutMs: resolveTurnBudgetMs,
|
|
39529
|
+
onWorkerStderrTail: newWorkerStderrTailSink(),
|
|
39188
39530
|
...options.model ? { model: options.model } : {},
|
|
39189
39531
|
...options.effort ? { effort: options.effort } : {},
|
|
39190
39532
|
...options.executable ? { executable: options.executable } : {}
|
|
@@ -39204,6 +39546,14 @@ async function runConfiguredListener(options) {
|
|
|
39204
39546
|
principalId: options.principalId,
|
|
39205
39547
|
provider: options.provider,
|
|
39206
39548
|
permissionMode: options.permissionMode,
|
|
39549
|
+
// The bound a timeout event reports: the last turn's clamped budget when
|
|
39550
|
+
// one has run, else the configured cap.
|
|
39551
|
+
getTurnBudgetMs: () => lastAppliedTurnBudgetMs ?? turnBudgetMs,
|
|
39552
|
+
takeWorkerStderrTail: () => {
|
|
39553
|
+
const tail = lastWorkerStderrTail;
|
|
39554
|
+
lastWorkerStderrTail = null;
|
|
39555
|
+
return tail;
|
|
39556
|
+
},
|
|
39207
39557
|
prepare: async (proposedInstanceId) => {
|
|
39208
39558
|
const selected = await openListenerDeliveryJournal({
|
|
39209
39559
|
profileId: options.cloud.profileId,
|
|
@@ -39258,6 +39608,7 @@ async function runListenStart(args) {
|
|
|
39258
39608
|
"claude-executable",
|
|
39259
39609
|
"codex-executable",
|
|
39260
39610
|
"state-dir",
|
|
39611
|
+
"turn-budget",
|
|
39261
39612
|
"foreground",
|
|
39262
39613
|
"json"
|
|
39263
39614
|
], 2);
|
|
@@ -39268,6 +39619,7 @@ async function runListenStart(args) {
|
|
|
39268
39619
|
}
|
|
39269
39620
|
const provider = listenerProvider(args);
|
|
39270
39621
|
validateListenerProviderFlags(args, provider);
|
|
39622
|
+
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
39271
39623
|
const cloud = await target(args);
|
|
39272
39624
|
const workspaceId2 = listenerUuid(
|
|
39273
39625
|
args.optional("workspace-id") ?? process.env.SWARM_CLOUD_WORKSPACE_ID,
|
|
@@ -39302,6 +39654,7 @@ async function runListenStart(args) {
|
|
|
39302
39654
|
cwd,
|
|
39303
39655
|
permissionMode,
|
|
39304
39656
|
provider,
|
|
39657
|
+
turnBudgetMs,
|
|
39305
39658
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39306
39659
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39307
39660
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39352,6 +39705,7 @@ async function runListenStart(args) {
|
|
|
39352
39705
|
...stateDirectory2 ? { stateDirectory: stateDirectory2 } : {},
|
|
39353
39706
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39354
39707
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39708
|
+
...args.optional("turn-budget") ? { turnBudget: args.required("turn-budget") } : {},
|
|
39355
39709
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
39356
39710
|
...opencodeExecutable ? { opencodeExecutable } : {},
|
|
39357
39711
|
...claudeExecutable ? { claudeExecutable } : {},
|
|
@@ -39409,10 +39763,12 @@ async function runListenSupervisor(args) {
|
|
|
39409
39763
|
"opencode-executable",
|
|
39410
39764
|
"claude-executable",
|
|
39411
39765
|
"codex-executable",
|
|
39412
|
-
"state-dir"
|
|
39766
|
+
"state-dir",
|
|
39767
|
+
"turn-budget"
|
|
39413
39768
|
], 1);
|
|
39414
39769
|
const provider = listenerProvider(args);
|
|
39415
39770
|
validateListenerProviderFlags(args, provider);
|
|
39771
|
+
const turnBudgetMs = listenerTurnBudgetMs(args.optional("turn-budget"));
|
|
39416
39772
|
const cloud = await target(args);
|
|
39417
39773
|
const workspaceId2 = listenerUuid(args.optional("workspace-id"), "workspace-id");
|
|
39418
39774
|
const principalId = listenerUuid(args.optional("principal-id"), "principal-id");
|
|
@@ -39428,6 +39784,7 @@ async function runListenSupervisor(args) {
|
|
|
39428
39784
|
cwd,
|
|
39429
39785
|
permissionMode: listenerPermissionMode(args.optional("permissions")),
|
|
39430
39786
|
provider,
|
|
39787
|
+
turnBudgetMs,
|
|
39431
39788
|
...args.optional("model") ? { model: args.required("model") } : {},
|
|
39432
39789
|
...args.optional("effort") ? { effort: args.required("effort") } : {},
|
|
39433
39790
|
...args.optional("grok-executable") ? { executable: args.required("grok-executable") } : {},
|
|
@@ -39709,6 +40066,48 @@ async function runFileRestore(args) {
|
|
|
39709
40066
|
`
|
|
39710
40067
|
);
|
|
39711
40068
|
}
|
|
40069
|
+
async function runFeedback(args) {
|
|
40070
|
+
const body = args.positionals[1];
|
|
40071
|
+
if (!body) {
|
|
40072
|
+
throw new UsageError(
|
|
40073
|
+
'cswarm feedback needs the feedback text: cswarm feedback "<text>" --kind bug|idea|friction'
|
|
40074
|
+
);
|
|
40075
|
+
}
|
|
40076
|
+
const kind = args.required("kind");
|
|
40077
|
+
if (kind !== "bug" && kind !== "idea" && kind !== "friction") {
|
|
40078
|
+
throw new UsageError("--kind must be bug, idea, or friction");
|
|
40079
|
+
}
|
|
40080
|
+
const about = args.optional("about");
|
|
40081
|
+
const context = await fileContext(args, ["kind", "about"], 2);
|
|
40082
|
+
const submitted = await submitFeedback({
|
|
40083
|
+
target: context.cloud,
|
|
40084
|
+
workspaceId: context.selected.selectedWorkspace,
|
|
40085
|
+
credential: context.selected.bearer
|
|
40086
|
+
}, {
|
|
40087
|
+
category: kind,
|
|
40088
|
+
body,
|
|
40089
|
+
context: {
|
|
40090
|
+
surface: "cli",
|
|
40091
|
+
cswarm_version: CLI_BUILD_VERSION,
|
|
40092
|
+
platform: process.platform,
|
|
40093
|
+
...about ? { about } : {}
|
|
40094
|
+
}
|
|
40095
|
+
});
|
|
40096
|
+
if (args.has("json")) {
|
|
40097
|
+
process.stdout.write(`${JSON.stringify(submitted, null, 2)}
|
|
40098
|
+
`);
|
|
40099
|
+
return;
|
|
40100
|
+
}
|
|
40101
|
+
if (submitted.duplicate === true) {
|
|
40102
|
+
process.stdout.write(
|
|
40103
|
+
"This matches feedback you sent within the hour, so it was not recorded twice. It is already with the operators of this deployment.\n"
|
|
40104
|
+
);
|
|
40105
|
+
return;
|
|
40106
|
+
}
|
|
40107
|
+
process.stdout.write(
|
|
40108
|
+
"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"
|
|
40109
|
+
);
|
|
40110
|
+
}
|
|
39712
40111
|
async function runFile(args) {
|
|
39713
40112
|
const action = args.positionals[1];
|
|
39714
40113
|
if (action === "put") return await runFilePut(args);
|
|
@@ -39964,6 +40363,10 @@ async function main() {
|
|
|
39964
40363
|
await runStatus(args);
|
|
39965
40364
|
return;
|
|
39966
40365
|
}
|
|
40366
|
+
if (verb === "feedback") {
|
|
40367
|
+
await runFeedback(args);
|
|
40368
|
+
return;
|
|
40369
|
+
}
|
|
39967
40370
|
if (verb === "file") {
|
|
39968
40371
|
await runFile(args);
|
|
39969
40372
|
return;
|
|
@@ -40089,6 +40492,8 @@ ${usage()}
|
|
|
40089
40492
|
// Annotate the CommonJS export names for ESM import in node:
|
|
40090
40493
|
0 && (module.exports = {
|
|
40091
40494
|
EXIT_RESTARTABLE,
|
|
40495
|
+
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
40496
|
+
clampTurnBudgetToCredential,
|
|
40092
40497
|
describeAudience,
|
|
40093
40498
|
listenerFailureMessage,
|
|
40094
40499
|
listenerHostLimits,
|
|
@@ -40096,5 +40501,6 @@ ${usage()}
|
|
|
40096
40501
|
listenerStatusJson,
|
|
40097
40502
|
renderRoster,
|
|
40098
40503
|
resolveDetachedClaudeExecutable,
|
|
40099
|
-
resolveDetachedCodexExecutable
|
|
40504
|
+
resolveDetachedCodexExecutable,
|
|
40505
|
+
resolveTurnBudgetOrDefer
|
|
40100
40506
|
});
|