@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.
Files changed (2) hide show
  1. package/dist/index.js +116 -32
  2. 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
- // src/constants/watch.ts
392
- var MIN_WATCH_POLL_INTERVAL_MS = 1;
393
- var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
394
- var WATCH_REPLAY_PAGE_SIZE = 200;
395
- var WATCH_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
396
- "idle",
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
- status: typeof data.status === "string" ? data.status : "unknown"
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] ${status.status} | ${details.join(" | ")}` : `[status] ${status.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?.status && WATCH_TERMINAL_STATUSES.has(parsed.status.status)) break;
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 extractSessionStatus(payload) {
1904
+ function extractSessionLifecycle(payload) {
1854
1905
  const session = payload.session;
1855
- if (session && typeof session === "object" && typeof session.status === "string") {
1856
- return String(session.status);
1857
- }
1858
- return typeof payload.status === "string" ? payload.status : "unknown";
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
- if (WATCH_TERMINAL_STATUSES.has(extractSessionStatus(sessionPayload))) {
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 (options.beforeSequence) {
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: options.afterSequence, limit: options.limit },
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 (options.afterSequence) query.set("after_sequence", options.afterSequence);
2116
- if (options.beforeSequence) query.set("before_sequence", options.beforeSequence);
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
- const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
2169
- method: "POST"
2170
- });
2171
- const status = response.status ?? "stopping";
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
- console.log(
2177
- status === "already_stopped" ? `Session ${sessionId} is already stopped.` : `Stop requested for session ${sessionId}.`
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {