@tryarcanist/cli 0.1.81 → 0.1.83
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/index.js +116 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -388,19 +388,31 @@ function sleep(ms) {
|
|
|
388
388
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
//
|
|
392
|
-
var
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
391
|
+
// ../../shared/session/phase.ts
|
|
392
|
+
var NON_REPO_SESSION_KINDS = /* @__PURE__ */ new Set(["terminal_bench", "swe_bench_pro", "swe_polybench"]);
|
|
393
|
+
function isRepoSession(sessionKind) {
|
|
394
|
+
return !sessionKind || !NON_REPO_SESSION_KINDS.has(sessionKind);
|
|
395
|
+
}
|
|
396
|
+
var TERMINAL_PHASES_ARRAY = [
|
|
397
397
|
"completed",
|
|
398
398
|
"blocked",
|
|
399
399
|
"failed",
|
|
400
400
|
"stopped",
|
|
401
|
-
"stopped_resumable",
|
|
402
401
|
"archived"
|
|
403
|
-
]
|
|
402
|
+
];
|
|
403
|
+
var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
|
|
404
|
+
function isTerminalPhase(phase, sessionKind) {
|
|
405
|
+
if (TERMINAL_PHASES.has(phase)) return true;
|
|
406
|
+
return phase === "idle" && !isRepoSession(sessionKind);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/constants/watch.ts
|
|
410
|
+
var MIN_WATCH_POLL_INTERVAL_MS = 1;
|
|
411
|
+
var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
|
|
412
|
+
var WATCH_REPLAY_PAGE_SIZE = 200;
|
|
413
|
+
function isWatchTerminal(phase, sessionKind) {
|
|
414
|
+
return isTerminalPhase(phase, sessionKind);
|
|
415
|
+
}
|
|
404
416
|
|
|
405
417
|
// src/uploads.ts
|
|
406
418
|
import { readFile } from "fs/promises";
|
|
@@ -961,6 +973,15 @@ function mergeMemoryRefs(ids, rawRefs) {
|
|
|
961
973
|
const ref = { id };
|
|
962
974
|
if (typeof record.path === "string" && record.path.trim()) ref.path = record.path.trim();
|
|
963
975
|
if (typeof record.title === "string" && record.title.trim()) ref.title = record.title.trim();
|
|
976
|
+
if (typeof record.selectionRank === "number") ref.selectionRank = record.selectionRank;
|
|
977
|
+
if (typeof record.selectionScore === "number") ref.selectionScore = record.selectionScore;
|
|
978
|
+
if (typeof record.reason === "string" && record.reason.trim()) ref.reason = record.reason.trim();
|
|
979
|
+
if (typeof record.expectedEffect === "string" && record.expectedEffect.trim()) {
|
|
980
|
+
ref.expectedEffect = record.expectedEffect.trim();
|
|
981
|
+
}
|
|
982
|
+
if (typeof record.observedEffect === "string" && record.observedEffect.trim()) {
|
|
983
|
+
ref.observedEffect = record.observedEffect.trim();
|
|
984
|
+
}
|
|
964
985
|
byId.set(id, ref);
|
|
965
986
|
}
|
|
966
987
|
}
|
|
@@ -1333,7 +1354,6 @@ var ERROR_CODES = [
|
|
|
1333
1354
|
"rate_limit",
|
|
1334
1355
|
"api_error",
|
|
1335
1356
|
"config_error",
|
|
1336
|
-
"doom_loop",
|
|
1337
1357
|
"failed_edits",
|
|
1338
1358
|
"empty_completion",
|
|
1339
1359
|
"followup_not_started",
|
|
@@ -1366,7 +1386,6 @@ var ERROR_CODE_LABELS = {
|
|
|
1366
1386
|
rate_limit: "Provider rate limit",
|
|
1367
1387
|
api_error: "Provider API error",
|
|
1368
1388
|
config_error: "Configuration error",
|
|
1369
|
-
doom_loop: "Repeated failure loop",
|
|
1370
1389
|
failed_edits: "Edit failure",
|
|
1371
1390
|
empty_completion: "Empty completion",
|
|
1372
1391
|
followup_not_started: "Follow-up did not start",
|
|
@@ -1402,6 +1421,20 @@ function formatSessionErrorMessage(error, code) {
|
|
|
1402
1421
|
}
|
|
1403
1422
|
|
|
1404
1423
|
// src/utils/session-output.ts
|
|
1424
|
+
var VALID_PHASES = /* @__PURE__ */ new Set([
|
|
1425
|
+
"idle",
|
|
1426
|
+
"running",
|
|
1427
|
+
"waiting_for_input",
|
|
1428
|
+
"finalizing",
|
|
1429
|
+
"completed",
|
|
1430
|
+
"blocked",
|
|
1431
|
+
"failed",
|
|
1432
|
+
"stopped",
|
|
1433
|
+
"archived"
|
|
1434
|
+
]);
|
|
1435
|
+
var VALID_SANDBOX_SUBSTATES = /* @__PURE__ */ new Set(["creating", "reconnecting", "stopping", "none"]);
|
|
1436
|
+
var VALID_STOP_MODES = /* @__PURE__ */ new Set(["user", "resumable", "none"]);
|
|
1437
|
+
var VALID_FINALIZING_STEPS = /* @__PURE__ */ new Set(["post_execution", "publishing", "none"]);
|
|
1405
1438
|
function formatDate(value) {
|
|
1406
1439
|
const date = new Date(value);
|
|
1407
1440
|
if (Number.isNaN(date.getTime())) return value;
|
|
@@ -1601,9 +1634,21 @@ function parseSsePayload(payload) {
|
|
|
1601
1634
|
for (const message of messages) {
|
|
1602
1635
|
const data = message.data ? parseJsonObject(message.data) : {};
|
|
1603
1636
|
if (message.event === "status") {
|
|
1637
|
+
const phaseRaw = data.phase;
|
|
1638
|
+
const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
|
|
1604
1639
|
const entry = {
|
|
1605
|
-
|
|
1640
|
+
phase
|
|
1606
1641
|
};
|
|
1642
|
+
if (typeof data.sandboxSubstate === "string" && VALID_SANDBOX_SUBSTATES.has(data.sandboxSubstate)) {
|
|
1643
|
+
entry.sandboxSubstate = data.sandboxSubstate;
|
|
1644
|
+
}
|
|
1645
|
+
if (typeof data.stopMode === "string" && VALID_STOP_MODES.has(data.stopMode)) {
|
|
1646
|
+
entry.stopMode = data.stopMode;
|
|
1647
|
+
}
|
|
1648
|
+
if (typeof data.finalizingStep === "string" && VALID_FINALIZING_STEPS.has(data.finalizingStep)) {
|
|
1649
|
+
entry.finalizingStep = data.finalizingStep;
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof data.sessionKind === "string") entry.sessionKind = data.sessionKind;
|
|
1607
1652
|
if (typeof data.title === "string") entry.title = data.title;
|
|
1608
1653
|
if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
|
|
1609
1654
|
entry.spawnDurationMs = data.spawnDurationMs;
|
|
@@ -1734,10 +1779,16 @@ function parseNonNegativeInteger(raw, name, defaultValue) {
|
|
|
1734
1779
|
return Number(raw);
|
|
1735
1780
|
}
|
|
1736
1781
|
function formatStatusLine(status) {
|
|
1782
|
+
const label = status.phase ?? "unknown";
|
|
1783
|
+
const substate = [];
|
|
1784
|
+
if (status.sandboxSubstate && status.sandboxSubstate !== "none") substate.push(status.sandboxSubstate);
|
|
1785
|
+
if (status.stopMode && status.stopMode !== "none") substate.push(status.stopMode);
|
|
1786
|
+
if (status.finalizingStep && status.finalizingStep !== "none") substate.push(status.finalizingStep);
|
|
1787
|
+
const phaseLabel = substate.length > 0 ? `${label}/${substate.join("/")}` : label;
|
|
1737
1788
|
const details = [];
|
|
1738
1789
|
if (status.title) details.push(status.title);
|
|
1739
1790
|
if (status.spawnDurationMs != null) details.push(`spawn ${(status.spawnDurationMs / 1e3).toFixed(1)}s`);
|
|
1740
|
-
return details.length > 0 ? `[status] ${
|
|
1791
|
+
return details.length > 0 ? `[status] ${phaseLabel} | ${details.join(" | ")}` : `[status] ${phaseLabel}`;
|
|
1741
1792
|
}
|
|
1742
1793
|
async function fetchPromptLabels(config, sessionId) {
|
|
1743
1794
|
const data = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
|
|
@@ -1817,7 +1868,7 @@ async function watchCommand(sessionId, options, command) {
|
|
|
1817
1868
|
console.log(rendered.line);
|
|
1818
1869
|
}
|
|
1819
1870
|
if (receivedFullPage) continue;
|
|
1820
|
-
if (parsed.status?.
|
|
1871
|
+
if (parsed.status?.phase && isWatchTerminal(parsed.status.phase, parsed.status.sessionKind)) break;
|
|
1821
1872
|
await sleep(effectivePollIntervalMs);
|
|
1822
1873
|
}
|
|
1823
1874
|
} finally {
|
|
@@ -1850,12 +1901,13 @@ function buildPromptFailureError(sessionId, prompt, sessionUrl) {
|
|
|
1850
1901
|
hint: `Inspect with: arcanist sessions transcript ${sessionId}`
|
|
1851
1902
|
});
|
|
1852
1903
|
}
|
|
1853
|
-
function
|
|
1904
|
+
function extractSessionLifecycle(payload) {
|
|
1854
1905
|
const session = payload.session;
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1906
|
+
const root = session && typeof session === "object" ? session : payload;
|
|
1907
|
+
const phaseRaw = root.phase;
|
|
1908
|
+
const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
|
|
1909
|
+
const sessionKind = typeof root.sessionKind === "string" ? root.sessionKind : null;
|
|
1910
|
+
return { phase, sessionKind };
|
|
1859
1911
|
}
|
|
1860
1912
|
async function fetchCreatedPromptStatus(config, sessionId, promptId) {
|
|
1861
1913
|
const promptList = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
|
|
@@ -1869,7 +1921,8 @@ async function waitForCreatedPromptToSettle(config, sessionId, promptId, pollInt
|
|
|
1869
1921
|
return createdPrompt;
|
|
1870
1922
|
}
|
|
1871
1923
|
const sessionPayload = await apiFetch(config, `/api/sessions/${sessionId}`);
|
|
1872
|
-
|
|
1924
|
+
const lifecycle = extractSessionLifecycle(sessionPayload);
|
|
1925
|
+
if (lifecycle.phase && isWatchTerminal(lifecycle.phase, lifecycle.sessionKind)) {
|
|
1873
1926
|
return fetchCreatedPromptStatus(config, sessionId, promptId);
|
|
1874
1927
|
}
|
|
1875
1928
|
await sleep(effectivePollIntervalMs);
|
|
@@ -2092,9 +2145,11 @@ async function getSessionCommand(sessionId, options, command) {
|
|
|
2092
2145
|
if (session.title) console.log(`Title: ${String(session.title)}`);
|
|
2093
2146
|
}
|
|
2094
2147
|
async function sessionEventsCommand(sessionId, options, command) {
|
|
2148
|
+
const afterSequence = resolveSequenceOption(options.afterSequence, options.after, "--after-sequence", "--after");
|
|
2149
|
+
const beforeSequence = resolveSequenceOption(options.beforeSequence, options.before, "--before-sequence", "--before");
|
|
2095
2150
|
if (options.follow) {
|
|
2096
|
-
if (
|
|
2097
|
-
throw new CliError("user", "--before-sequence cannot be used with --follow.");
|
|
2151
|
+
if (beforeSequence) {
|
|
2152
|
+
throw new CliError("user", "--before-sequence/--before cannot be used with --follow.");
|
|
2098
2153
|
}
|
|
2099
2154
|
if (options.promptId) {
|
|
2100
2155
|
throw new CliError(
|
|
@@ -2104,7 +2159,7 @@ async function sessionEventsCommand(sessionId, options, command) {
|
|
|
2104
2159
|
}
|
|
2105
2160
|
await watchCommand(
|
|
2106
2161
|
sessionId,
|
|
2107
|
-
{ ...options, pollInterval: options.pollInterval, afterSequence
|
|
2162
|
+
{ ...options, pollInterval: options.pollInterval, afterSequence, limit: options.limit },
|
|
2108
2163
|
command
|
|
2109
2164
|
);
|
|
2110
2165
|
return;
|
|
@@ -2112,8 +2167,8 @@ async function sessionEventsCommand(sessionId, options, command) {
|
|
|
2112
2167
|
const runtime = getRuntimeOptions(command, options);
|
|
2113
2168
|
const config = requireConfig(runtime);
|
|
2114
2169
|
const query = new URLSearchParams();
|
|
2115
|
-
if (
|
|
2116
|
-
if (
|
|
2170
|
+
if (afterSequence) query.set("after_sequence", afterSequence);
|
|
2171
|
+
if (beforeSequence) query.set("before_sequence", beforeSequence);
|
|
2117
2172
|
if (options.promptId) query.set("prompt_id", options.promptId);
|
|
2118
2173
|
if (options.limit) query.set("limit", options.limit);
|
|
2119
2174
|
const payload = await apiFetch(config, `/api/sessions/${sessionId}/events/history${query.size ? `?${query.toString()}` : ""}`);
|
|
@@ -2146,6 +2201,12 @@ async function sessionEventsCommand(sessionId, options, command) {
|
|
|
2146
2201
|
}
|
|
2147
2202
|
if (textOpen) process.stdout.write("\n");
|
|
2148
2203
|
}
|
|
2204
|
+
function resolveSequenceOption(canonicalValue, aliasValue, canonicalFlag, aliasFlag) {
|
|
2205
|
+
if (canonicalValue !== void 0 && aliasValue !== void 0) {
|
|
2206
|
+
throw new CliError("user", `${aliasFlag} cannot be combined with ${canonicalFlag}.`);
|
|
2207
|
+
}
|
|
2208
|
+
return canonicalValue ?? aliasValue;
|
|
2209
|
+
}
|
|
2149
2210
|
async function usageCommand(sessionId, options, command) {
|
|
2150
2211
|
const runtime = getRuntimeOptions(command, options);
|
|
2151
2212
|
const config = requireConfig(runtime);
|
|
@@ -2165,17 +2226,38 @@ async function usageCommand(sessionId, options, command) {
|
|
|
2165
2226
|
async function stopCommand(sessionId, options = {}, command) {
|
|
2166
2227
|
const runtime = getRuntimeOptions(command, options);
|
|
2167
2228
|
const config = requireConfig(runtime);
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2229
|
+
let status;
|
|
2230
|
+
try {
|
|
2231
|
+
const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
|
|
2232
|
+
method: "POST"
|
|
2233
|
+
});
|
|
2234
|
+
status = response.status ?? "stopping";
|
|
2235
|
+
} catch (err) {
|
|
2236
|
+
const parsed = parseStopBlocked(err);
|
|
2237
|
+
if (!parsed) throw err;
|
|
2238
|
+
status = parsed.reason;
|
|
2239
|
+
}
|
|
2172
2240
|
if (isJson(command, options)) {
|
|
2173
2241
|
writeJson({ sessionId, status });
|
|
2174
2242
|
return;
|
|
2175
2243
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
)
|
|
2244
|
+
if (status === "already_stopped") {
|
|
2245
|
+
console.log(`Session ${sessionId} is already stopped.`);
|
|
2246
|
+
} else if (status === "stopping" || status === "stopped") {
|
|
2247
|
+
console.log(`Stop requested for session ${sessionId}.`);
|
|
2248
|
+
} else {
|
|
2249
|
+
console.log(`Session ${sessionId} cannot be stopped from phase=${status}.`);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
function parseStopBlocked(err) {
|
|
2253
|
+
if (!(err instanceof ApiError) || err.status !== 409) return null;
|
|
2254
|
+
try {
|
|
2255
|
+
const body = JSON.parse(err.body);
|
|
2256
|
+
if (body.error !== "session_not_stoppable") return null;
|
|
2257
|
+
return { reason: body.reason ?? "not_stoppable" };
|
|
2258
|
+
} catch {
|
|
2259
|
+
return null;
|
|
2260
|
+
}
|
|
2179
2261
|
}
|
|
2180
2262
|
|
|
2181
2263
|
// src/commands/test-creds.ts
|
|
@@ -2455,11 +2537,13 @@ Examples:
|
|
|
2455
2537
|
arcanist sessions search "mcp debugging" --tag codex --json
|
|
2456
2538
|
`
|
|
2457
2539
|
).action((query, options, command) => searchSessionsCommand(query, options, command));
|
|
2458
|
-
sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--before-sequence <n>", "Return events before this sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until the session is idle").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText(
|
|
2540
|
+
sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--after <n>", "Alias for --after-sequence").option("--before-sequence <n>", "Return events before this sequence").option("--before <n>", "Alias for --before-sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until the session is idle").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText(
|
|
2459
2541
|
"after",
|
|
2460
2542
|
`
|
|
2461
2543
|
Examples:
|
|
2462
2544
|
arcanist sessions events <session-id> --json
|
|
2545
|
+
arcanist sessions events <session-id> --after-sequence 540 --limit 250 --json
|
|
2546
|
+
arcanist sessions events <session-id> --after 540 --limit 250 --json
|
|
2463
2547
|
arcanist sessions events <session-id> --follow --json
|
|
2464
2548
|
`
|
|
2465
2549
|
).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
|