@mutagent/cli 0.2.207 → 0.2.209
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/dist/bin/cli.js +133 -25
- package/package.json +3 -3
package/dist/bin/cli.js
CHANGED
|
@@ -28444,10 +28444,8 @@ async function* readSseEvents(body) {
|
|
|
28444
28444
|
}
|
|
28445
28445
|
}
|
|
28446
28446
|
|
|
28447
|
-
// src/lib/sandbox-stream.ts
|
|
28448
|
-
init_errors();
|
|
28449
|
-
|
|
28450
28447
|
// src/lib/stream-reconnect.ts
|
|
28448
|
+
init_errors();
|
|
28451
28449
|
var QUICK_CLOSE_MS = 1000;
|
|
28452
28450
|
var CLEAN_BACKOFF_BASE_MS = 1000;
|
|
28453
28451
|
var CLEAN_BACKOFF_CAP_MS = 5000;
|
|
@@ -28465,13 +28463,62 @@ class CleanClosePacer {
|
|
|
28465
28463
|
return Math.min(CLEAN_BACKOFF_BASE_MS * 2 ** (this.streak - 2), CLEAN_BACKOFF_CAP_MS);
|
|
28466
28464
|
}
|
|
28467
28465
|
}
|
|
28468
|
-
function retryFailedNotice(failedRetries,
|
|
28469
|
-
return `Reconnect failed (${String(failedRetries)}
|
|
28466
|
+
function retryFailedNotice(failedRetries, _max, lastSeq) {
|
|
28467
|
+
return `Reconnect failed (attempt ${String(failedRetries)}). Retrying from seq ${String(lastSeq)}...`;
|
|
28468
|
+
}
|
|
28469
|
+
var OUTAGE_WINDOW_MS = 300000;
|
|
28470
|
+
var OUTAGE_BACKOFF_CAP_MS = 15000;
|
|
28471
|
+
var CHECK_TIMEOUT_MS = 1e4;
|
|
28472
|
+
function outageBackoffMs(baseMs, attempt, capMs = Infinity) {
|
|
28473
|
+
return Math.min(baseMs * 2 ** (attempt - 1), capMs);
|
|
28474
|
+
}
|
|
28475
|
+
async function askBounded(check, timeoutMs = CHECK_TIMEOUT_MS) {
|
|
28476
|
+
let timer;
|
|
28477
|
+
const timeout = new Promise((resolve) => {
|
|
28478
|
+
timer = setTimeout(() => {
|
|
28479
|
+
resolve({ state: "unknown", detail: `no answer within ${String(timeoutMs / 1000)} s` });
|
|
28480
|
+
}, timeoutMs);
|
|
28481
|
+
});
|
|
28482
|
+
try {
|
|
28483
|
+
return await Promise.race([
|
|
28484
|
+
check().catch((error) => ({
|
|
28485
|
+
state: "unknown",
|
|
28486
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
28487
|
+
})),
|
|
28488
|
+
timeout
|
|
28489
|
+
]);
|
|
28490
|
+
} finally {
|
|
28491
|
+
clearTimeout(timer);
|
|
28492
|
+
}
|
|
28493
|
+
}
|
|
28494
|
+
function lostStreamGuidance(lastSeq, checked, resume) {
|
|
28495
|
+
const at = `Resume from seq ${String(lastSeq)} with: ${resume}`;
|
|
28496
|
+
if (checked === undefined) {
|
|
28497
|
+
return `Could not check whether the sandbox is still running. ${at}`;
|
|
28498
|
+
}
|
|
28499
|
+
if (checked.state === "running") {
|
|
28500
|
+
return `The server reports the sandbox still running${checked.detail === undefined ? "" : ` (${checked.detail})`}. ${at}`;
|
|
28501
|
+
}
|
|
28502
|
+
if (checked.state === "not-running") {
|
|
28503
|
+
return `The sandbox is not running: ${checked.detail}. Its output up to seq ${String(lastSeq)} is recorded. ${at}`;
|
|
28504
|
+
}
|
|
28505
|
+
return `Could not check whether the sandbox is still running (${checked.detail}). ${at}`;
|
|
28506
|
+
}
|
|
28507
|
+
async function lostStreamError(input) {
|
|
28508
|
+
const { lastSeq } = input;
|
|
28509
|
+
const detail = input.cause instanceof Error ? ` (${input.cause.message})` : "";
|
|
28510
|
+
const guidance = input.released ? "This was a single-command run: the server deletes the sandbox when the connection drops, so nothing is left running and there is nothing to re-attach to. Run the turn again, or use --no-stream to wait for the whole result in one reply." : lostStreamGuidance(lastSeq, input.check === undefined ? undefined : await askBounded(input.check), input.resume?.(lastSeq) ?? `mutagent sandbox attach <id> --since ${String(lastSeq)}`);
|
|
28511
|
+
return new MutagentError("STREAM_DISCONNECTED", `Lost the sandbox stream at seq ${String(lastSeq)} and could not reconnect${detail}.`, guidance);
|
|
28470
28512
|
}
|
|
28471
28513
|
|
|
28472
28514
|
// src/lib/sandbox-stream.ts
|
|
28473
|
-
var DEFAULT_RETRY = {
|
|
28474
|
-
|
|
28515
|
+
var DEFAULT_RETRY = {
|
|
28516
|
+
attempts: 40,
|
|
28517
|
+
baseDelayMs: 500,
|
|
28518
|
+
maxDelayMs: OUTAGE_BACKOFF_CAP_MS,
|
|
28519
|
+
windowMs: OUTAGE_WINDOW_MS
|
|
28520
|
+
};
|
|
28521
|
+
var MAX_TOTAL_RECONNECTS = 40;
|
|
28475
28522
|
async function runStreamSession(options) {
|
|
28476
28523
|
const retry = options.retry ?? DEFAULT_RETRY;
|
|
28477
28524
|
const sleep = options.sleep ?? delay;
|
|
@@ -28484,6 +28531,7 @@ async function runStreamSession(options) {
|
|
|
28484
28531
|
let exitCode = null;
|
|
28485
28532
|
let reconnects = 0;
|
|
28486
28533
|
let errorReconnects = 0;
|
|
28534
|
+
let outageStartedAt = 0;
|
|
28487
28535
|
let gaps = 0;
|
|
28488
28536
|
let attempt = 0;
|
|
28489
28537
|
let sandbox;
|
|
@@ -28505,18 +28553,29 @@ async function runStreamSession(options) {
|
|
|
28505
28553
|
const detached = () => outcome("detached");
|
|
28506
28554
|
const isAborted = () => signal.aborted;
|
|
28507
28555
|
const withinTotalBudget = () => errorReconnects < MAX_TOTAL_RECONNECTS;
|
|
28508
|
-
const retryAfterError = async (error) => {
|
|
28556
|
+
const retryAfterError = async (error, progressed) => {
|
|
28557
|
+
if (progressed)
|
|
28558
|
+
errorReconnects = 0;
|
|
28509
28559
|
attempt += 1;
|
|
28510
|
-
if (attempt
|
|
28560
|
+
if (attempt === 1)
|
|
28561
|
+
outageStartedAt = now();
|
|
28562
|
+
const outageOver = retry.windowMs !== undefined && attempt > 1 && now() - outageStartedAt >= retry.windowMs;
|
|
28563
|
+
if (attempt > retry.attempts || outageOver || !withinTotalBudget()) {
|
|
28511
28564
|
if (live !== undefined && !live.live)
|
|
28512
28565
|
sink.notice(unfollowableNotice(live.reason));
|
|
28513
|
-
throw
|
|
28566
|
+
throw await lostStreamError({
|
|
28567
|
+
lastSeq,
|
|
28568
|
+
cause: error,
|
|
28569
|
+
released: onDisconnect === "released",
|
|
28570
|
+
...options.checkSandbox === undefined ? {} : { check: options.checkSandbox },
|
|
28571
|
+
...options.resumeCommand === undefined ? {} : { resume: options.resumeCommand }
|
|
28572
|
+
});
|
|
28514
28573
|
}
|
|
28515
28574
|
reconnects += 1;
|
|
28516
28575
|
errorReconnects += 1;
|
|
28517
28576
|
if (attempt > 1)
|
|
28518
28577
|
sink.notice(retryFailedNotice(attempt - 1, retry.attempts, lastSeq));
|
|
28519
|
-
await sleep(
|
|
28578
|
+
await sleep(outageBackoffMs(retry.baseDelayMs, attempt, retry.maxDelayMs), signal);
|
|
28520
28579
|
};
|
|
28521
28580
|
const alreadyDelivered = (seq) => seq <= floor;
|
|
28522
28581
|
const noteSeq = (seq) => {
|
|
@@ -28539,7 +28598,7 @@ async function runStreamSession(options) {
|
|
|
28539
28598
|
return detached();
|
|
28540
28599
|
if (reconnects === 0 && attempt === 0)
|
|
28541
28600
|
throw error;
|
|
28542
|
-
await retryAfterError(error);
|
|
28601
|
+
await retryAfterError(error, false);
|
|
28543
28602
|
continue;
|
|
28544
28603
|
}
|
|
28545
28604
|
let exited = false;
|
|
@@ -28671,7 +28730,7 @@ async function runStreamSession(options) {
|
|
|
28671
28730
|
} catch (error) {
|
|
28672
28731
|
if (isAborted())
|
|
28673
28732
|
return detached();
|
|
28674
|
-
await retryAfterError(error);
|
|
28733
|
+
await retryAfterError(error, lastSeq > seqAtOpen);
|
|
28675
28734
|
continue;
|
|
28676
28735
|
}
|
|
28677
28736
|
if (exited)
|
|
@@ -28690,9 +28749,6 @@ async function runStreamSession(options) {
|
|
|
28690
28749
|
await sleep(wait, signal);
|
|
28691
28750
|
}
|
|
28692
28751
|
}
|
|
28693
|
-
function backoffMs(base, attempt) {
|
|
28694
|
-
return base * 2 ** (attempt - 1);
|
|
28695
|
-
}
|
|
28696
28752
|
function unfollowableNotice(reason) {
|
|
28697
28753
|
const why = reason === "" ? "" : ` (${reason})`;
|
|
28698
28754
|
return `The backend cannot follow this sandbox from the instance that answered${why}. ` + "Everything already recorded has been replayed above, and this stream will end there rather " + "than continuing live — the sandbox itself is unaffected and may still be running. " + "Retry later, or attach from the instance that owns it.";
|
|
@@ -28701,11 +28757,6 @@ function gapNotice(lastSeq, nextSeq) {
|
|
|
28701
28757
|
const missing = nextSeq - lastSeq - 1;
|
|
28702
28758
|
return `Output gap: ${String(missing)} event(s) between seq ${String(lastSeq)} and ` + `${String(nextSeq)} are unavailable — the server buffer rolled past the resume point. ` + "What follows is not continuous with what came before.";
|
|
28703
28759
|
}
|
|
28704
|
-
function exhausted(lastSeq, cause, onDisconnect) {
|
|
28705
|
-
const detail = cause instanceof Error ? ` (${cause.message})` : "";
|
|
28706
|
-
const guidance = onDisconnect === "released" ? "This was a single-command run: the server deletes the sandbox when the connection drops, so nothing is left running and there is nothing to re-attach to. Run the turn again, or use --no-stream to wait for the whole result in one reply." : `The sandbox is still running — nothing was destroyed. Resume with: mutagent sandbox attach <id> --since ${String(lastSeq)}`;
|
|
28707
|
-
return new MutagentError("STREAM_DISCONNECTED", `Lost the sandbox stream and could not reconnect${detail}.`, guidance);
|
|
28708
|
-
}
|
|
28709
28760
|
function isGapError(payload) {
|
|
28710
28761
|
return payload.code === "gap";
|
|
28711
28762
|
}
|
|
@@ -28900,6 +28951,8 @@ async function runNativeSession(options) {
|
|
|
28900
28951
|
notice
|
|
28901
28952
|
},
|
|
28902
28953
|
filtered: true,
|
|
28954
|
+
resumeCommand: (seq) => activeProduct().cli + " session attach " + options.reference + " --since " + String(seq),
|
|
28955
|
+
...options.check === undefined ? {} : { checkSandbox: options.check },
|
|
28903
28956
|
...options.sessionId === undefined ? {} : { sessionId: options.sessionId },
|
|
28904
28957
|
...options.since === undefined ? {} : { since: options.since },
|
|
28905
28958
|
signal: controller.signal
|
|
@@ -28942,6 +28995,36 @@ async function runNativeSession(options) {
|
|
|
28942
28995
|
}
|
|
28943
28996
|
}
|
|
28944
28997
|
|
|
28998
|
+
// src/lib/session-durability.ts
|
|
28999
|
+
async function checkSession(reference, findRow) {
|
|
29000
|
+
let row;
|
|
29001
|
+
try {
|
|
29002
|
+
row = await findRow(reference);
|
|
29003
|
+
} catch (error) {
|
|
29004
|
+
return { state: "unknown", detail: error instanceof Error ? error.message : String(error) };
|
|
29005
|
+
}
|
|
29006
|
+
if (row === undefined)
|
|
29007
|
+
return { state: "unknown", detail: "the session list does not show it" };
|
|
29008
|
+
switch (row.status) {
|
|
29009
|
+
case "live":
|
|
29010
|
+
case "restored":
|
|
29011
|
+
return row.idleStopHeld === undefined ? { state: "running" } : { state: "running", detail: `kept by the idle stop: ${row.idleStopHeld.reason}` };
|
|
29012
|
+
case "stopped-for-idling":
|
|
29013
|
+
return {
|
|
29014
|
+
state: "not-running",
|
|
29015
|
+
detail: "the session stopped for idling; a send wakes it from its checkpoint"
|
|
29016
|
+
};
|
|
29017
|
+
case "ended":
|
|
29018
|
+
return {
|
|
29019
|
+
state: "not-running",
|
|
29020
|
+
detail: `the session ended${row.exitCode === undefined || row.exitCode === null ? "" : ` (exit code ${String(row.exitCode)})`}`
|
|
29021
|
+
};
|
|
29022
|
+
}
|
|
29023
|
+
}
|
|
29024
|
+
function listedRow(routes) {
|
|
29025
|
+
return async (reference) => (await listHarnessSessions(routes)).sessions.find((row) => row.reference === reference);
|
|
29026
|
+
}
|
|
29027
|
+
|
|
28945
29028
|
// src/commands/cloud-session/run-cloud.ts
|
|
28946
29029
|
function repositoryFields(flags) {
|
|
28947
29030
|
return {
|
|
@@ -29055,7 +29138,8 @@ async function attachLaunched(session, rpc, interruptRequested) {
|
|
|
29055
29138
|
send: (line, signal) => sendSessionInput(routes, reference, line, signal),
|
|
29056
29139
|
closeInput: (signal) => closeSessionInput(routes, reference, signal),
|
|
29057
29140
|
interrupt: (signal) => signalSession(routes, reference, "SIGINT", signal),
|
|
29058
|
-
interruptRequested
|
|
29141
|
+
interruptRequested,
|
|
29142
|
+
check: () => checkSession(reference, listedRow(routes))
|
|
29059
29143
|
}).catch((error) => {
|
|
29060
29144
|
throw markStreamStarted(error);
|
|
29061
29145
|
});
|
|
@@ -30015,6 +30099,23 @@ function formatDuration(ms) {
|
|
|
30015
30099
|
}
|
|
30016
30100
|
|
|
30017
30101
|
// src/lib/sandbox-session.ts
|
|
30102
|
+
async function sandboxCheck(id) {
|
|
30103
|
+
let status;
|
|
30104
|
+
try {
|
|
30105
|
+
({ status } = await inspectSandbox(id));
|
|
30106
|
+
} catch (error) {
|
|
30107
|
+
if (error instanceof MutagentError && error.code === "NOT_FOUND") {
|
|
30108
|
+
return { state: "not-running", detail: "it no longer exists" };
|
|
30109
|
+
}
|
|
30110
|
+
throw error;
|
|
30111
|
+
}
|
|
30112
|
+
if (status === undefined)
|
|
30113
|
+
return { state: "unknown", detail: "the server reported no status" };
|
|
30114
|
+
if (status === "running" || status === "starting" || status === "pending" || status === "paused") {
|
|
30115
|
+
return { state: "running", detail: status };
|
|
30116
|
+
}
|
|
30117
|
+
return { state: "not-running", detail: `it is ${status}` };
|
|
30118
|
+
}
|
|
30018
30119
|
function dim(text) {
|
|
30019
30120
|
return isPlainOutput() ? text : chalk24.dim(text);
|
|
30020
30121
|
}
|
|
@@ -30122,7 +30223,11 @@ async function attachToSandbox(options) {
|
|
|
30122
30223
|
signal: controller.signal,
|
|
30123
30224
|
...options.retry === undefined ? {} : { retry: options.retry },
|
|
30124
30225
|
...options.onDisconnect === undefined ? {} : { onDisconnect: options.onDisconnect },
|
|
30125
|
-
...options.onSandbox === undefined ? {} : { onSandbox: options.onSandbox }
|
|
30226
|
+
...options.onSandbox === undefined ? {} : { onSandbox: options.onSandbox },
|
|
30227
|
+
...options.id === "" ? {} : {
|
|
30228
|
+
checkSandbox: () => sandboxCheck(options.id),
|
|
30229
|
+
resumeCommand: (seq) => reattachHint(options.id, seq)
|
|
30230
|
+
}
|
|
30126
30231
|
});
|
|
30127
30232
|
reportOutcome(options.id, result, sink, options.onDisconnect ?? "survives");
|
|
30128
30233
|
return result;
|
|
@@ -30148,7 +30253,7 @@ function reportOutcome(id, result, sink, onDisconnect) {
|
|
|
30148
30253
|
sink.notice("Run the turn again to retry, or add --keep next time to hold the sandbox open.");
|
|
30149
30254
|
return;
|
|
30150
30255
|
}
|
|
30151
|
-
sink.notice(`Detached
|
|
30256
|
+
sink.notice(`Detached at seq ${String(result.lastSeq)}: this CLI stopped watching and did not stop sandbox ${sandboxId}.`);
|
|
30152
30257
|
sink.notice(`Re-attach with: ${reattachHint(sandboxId, result.lastSeq)}`);
|
|
30153
30258
|
sink.notice(`Destroy it with: mutagent sandbox delete ${sandboxId} --force`);
|
|
30154
30259
|
return;
|
|
@@ -30973,6 +31078,7 @@ function registerSessionAttach(session, p) {
|
|
|
30973
31078
|
"AI Agent Directive:",
|
|
30974
31079
|
" Output is Helix's own text or JSON lines on stdout; CLI messages go to stderr. --json does not change it.",
|
|
30975
31080
|
" Ctrl-C stops watching without signalling the session.",
|
|
31081
|
+
" A dropped stream reconnects at its cursor for up to 5 minutes; if it gives up, it prints the seq to resume from.",
|
|
30976
31082
|
" A reference names one session; after a restore, take the new reference from session list.",
|
|
30977
31083
|
' When a send wakes the session, stderr carries [mutagent] {"event":"woken","reference":"hs1_..."}:',
|
|
30978
31084
|
" this stream ends there; attach to that reference with --since 0."
|
|
@@ -30980,9 +31086,11 @@ function registerSessionAttach(session, p) {
|
|
|
30980
31086
|
`))).action(async (reference, options) => {
|
|
30981
31087
|
try {
|
|
30982
31088
|
const since = parseSince(options.since);
|
|
31089
|
+
const findRow = listedRow(p);
|
|
30983
31090
|
await runNativeSession({
|
|
30984
31091
|
reference,
|
|
30985
31092
|
rpc: false,
|
|
31093
|
+
check: () => checkSession(reference, findRow),
|
|
30986
31094
|
...since === undefined ? {} : { since },
|
|
30987
31095
|
open: (request) => openSessionStream(p, reference, request),
|
|
30988
31096
|
send: (line, signal) => sendSessionInput(p, reference, line, signal)
|
|
@@ -44673,4 +44781,4 @@ if (process.env.CLI_VERSION) {
|
|
|
44673
44781
|
}
|
|
44674
44782
|
runCli(process.argv.slice(2), cliVersion);
|
|
44675
44783
|
|
|
44676
|
-
//# debugId=
|
|
44784
|
+
//# debugId=E0552D781753FD4964756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutagent/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.209",
|
|
4
4
|
"description": "Bun-native CLI for the MutagenT AI platform - auth, provider config, and lifecycle-tool installer for AI-native workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@inquirer/prompts": "^8.7.2",
|
|
48
|
-
"@mutagent/sdk": "0.12.
|
|
48
|
+
"@mutagent/sdk": "0.12.21",
|
|
49
49
|
"@sinclair/typebox": "^0.34.0",
|
|
50
50
|
"chalk": "^5.3.0",
|
|
51
51
|
"commander": "^12.1.0",
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"bugs": {
|
|
95
95
|
"url": "https://docs.mutagent.io/support"
|
|
96
96
|
},
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "14b9f48b84e32ae4f13253bad2d4a365cb03107d"
|
|
98
98
|
}
|