@tryarcanist/cli 0.1.81 → 0.1.82

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 +100 -24
  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
  }
@@ -1402,6 +1423,20 @@ function formatSessionErrorMessage(error, code) {
1402
1423
  }
1403
1424
 
1404
1425
  // src/utils/session-output.ts
1426
+ var VALID_PHASES = /* @__PURE__ */ new Set([
1427
+ "idle",
1428
+ "running",
1429
+ "waiting_for_input",
1430
+ "finalizing",
1431
+ "completed",
1432
+ "blocked",
1433
+ "failed",
1434
+ "stopped",
1435
+ "archived"
1436
+ ]);
1437
+ var VALID_SANDBOX_SUBSTATES = /* @__PURE__ */ new Set(["creating", "reconnecting", "stopping", "none"]);
1438
+ var VALID_STOP_MODES = /* @__PURE__ */ new Set(["user", "resumable", "none"]);
1439
+ var VALID_FINALIZING_STEPS = /* @__PURE__ */ new Set(["post_execution", "publishing", "none"]);
1405
1440
  function formatDate(value) {
1406
1441
  const date = new Date(value);
1407
1442
  if (Number.isNaN(date.getTime())) return value;
@@ -1601,9 +1636,21 @@ function parseSsePayload(payload) {
1601
1636
  for (const message of messages) {
1602
1637
  const data = message.data ? parseJsonObject(message.data) : {};
1603
1638
  if (message.event === "status") {
1639
+ const phaseRaw = data.phase;
1640
+ const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
1604
1641
  const entry = {
1605
- status: typeof data.status === "string" ? data.status : "unknown"
1642
+ phase
1606
1643
  };
1644
+ if (typeof data.sandboxSubstate === "string" && VALID_SANDBOX_SUBSTATES.has(data.sandboxSubstate)) {
1645
+ entry.sandboxSubstate = data.sandboxSubstate;
1646
+ }
1647
+ if (typeof data.stopMode === "string" && VALID_STOP_MODES.has(data.stopMode)) {
1648
+ entry.stopMode = data.stopMode;
1649
+ }
1650
+ if (typeof data.finalizingStep === "string" && VALID_FINALIZING_STEPS.has(data.finalizingStep)) {
1651
+ entry.finalizingStep = data.finalizingStep;
1652
+ }
1653
+ if (typeof data.sessionKind === "string") entry.sessionKind = data.sessionKind;
1607
1654
  if (typeof data.title === "string") entry.title = data.title;
1608
1655
  if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
1609
1656
  entry.spawnDurationMs = data.spawnDurationMs;
@@ -1734,10 +1781,16 @@ function parseNonNegativeInteger(raw, name, defaultValue) {
1734
1781
  return Number(raw);
1735
1782
  }
1736
1783
  function formatStatusLine(status) {
1784
+ const label = status.phase ?? "unknown";
1785
+ const substate = [];
1786
+ if (status.sandboxSubstate && status.sandboxSubstate !== "none") substate.push(status.sandboxSubstate);
1787
+ if (status.stopMode && status.stopMode !== "none") substate.push(status.stopMode);
1788
+ if (status.finalizingStep && status.finalizingStep !== "none") substate.push(status.finalizingStep);
1789
+ const phaseLabel = substate.length > 0 ? `${label}/${substate.join("/")}` : label;
1737
1790
  const details = [];
1738
1791
  if (status.title) details.push(status.title);
1739
1792
  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}`;
1793
+ return details.length > 0 ? `[status] ${phaseLabel} | ${details.join(" | ")}` : `[status] ${phaseLabel}`;
1741
1794
  }
1742
1795
  async function fetchPromptLabels(config, sessionId) {
1743
1796
  const data = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
@@ -1817,7 +1870,7 @@ async function watchCommand(sessionId, options, command) {
1817
1870
  console.log(rendered.line);
1818
1871
  }
1819
1872
  if (receivedFullPage) continue;
1820
- if (parsed.status?.status && WATCH_TERMINAL_STATUSES.has(parsed.status.status)) break;
1873
+ if (parsed.status?.phase && isWatchTerminal(parsed.status.phase, parsed.status.sessionKind)) break;
1821
1874
  await sleep(effectivePollIntervalMs);
1822
1875
  }
1823
1876
  } finally {
@@ -1850,12 +1903,13 @@ function buildPromptFailureError(sessionId, prompt, sessionUrl) {
1850
1903
  hint: `Inspect with: arcanist sessions transcript ${sessionId}`
1851
1904
  });
1852
1905
  }
1853
- function extractSessionStatus(payload) {
1906
+ function extractSessionLifecycle(payload) {
1854
1907
  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";
1908
+ const root = session && typeof session === "object" ? session : payload;
1909
+ const phaseRaw = root.phase;
1910
+ const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
1911
+ const sessionKind = typeof root.sessionKind === "string" ? root.sessionKind : null;
1912
+ return { phase, sessionKind };
1859
1913
  }
1860
1914
  async function fetchCreatedPromptStatus(config, sessionId, promptId) {
1861
1915
  const promptList = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
@@ -1869,7 +1923,8 @@ async function waitForCreatedPromptToSettle(config, sessionId, promptId, pollInt
1869
1923
  return createdPrompt;
1870
1924
  }
1871
1925
  const sessionPayload = await apiFetch(config, `/api/sessions/${sessionId}`);
1872
- if (WATCH_TERMINAL_STATUSES.has(extractSessionStatus(sessionPayload))) {
1926
+ const lifecycle = extractSessionLifecycle(sessionPayload);
1927
+ if (lifecycle.phase && isWatchTerminal(lifecycle.phase, lifecycle.sessionKind)) {
1873
1928
  return fetchCreatedPromptStatus(config, sessionId, promptId);
1874
1929
  }
1875
1930
  await sleep(effectivePollIntervalMs);
@@ -2165,17 +2220,38 @@ async function usageCommand(sessionId, options, command) {
2165
2220
  async function stopCommand(sessionId, options = {}, command) {
2166
2221
  const runtime = getRuntimeOptions(command, options);
2167
2222
  const config = requireConfig(runtime);
2168
- const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
2169
- method: "POST"
2170
- });
2171
- const status = response.status ?? "stopping";
2223
+ let status;
2224
+ try {
2225
+ const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
2226
+ method: "POST"
2227
+ });
2228
+ status = response.status ?? "stopping";
2229
+ } catch (err) {
2230
+ const parsed = parseStopBlocked(err);
2231
+ if (!parsed) throw err;
2232
+ status = parsed.reason;
2233
+ }
2172
2234
  if (isJson(command, options)) {
2173
2235
  writeJson({ sessionId, status });
2174
2236
  return;
2175
2237
  }
2176
- console.log(
2177
- status === "already_stopped" ? `Session ${sessionId} is already stopped.` : `Stop requested for session ${sessionId}.`
2178
- );
2238
+ if (status === "already_stopped") {
2239
+ console.log(`Session ${sessionId} is already stopped.`);
2240
+ } else if (status === "stopping" || status === "stopped") {
2241
+ console.log(`Stop requested for session ${sessionId}.`);
2242
+ } else {
2243
+ console.log(`Session ${sessionId} cannot be stopped from phase=${status}.`);
2244
+ }
2245
+ }
2246
+ function parseStopBlocked(err) {
2247
+ if (!(err instanceof ApiError) || err.status !== 409) return null;
2248
+ try {
2249
+ const body = JSON.parse(err.body);
2250
+ if (body.error !== "session_not_stoppable") return null;
2251
+ return { reason: body.reason ?? "not_stoppable" };
2252
+ } catch {
2253
+ return null;
2254
+ }
2179
2255
  }
2180
2256
 
2181
2257
  // src/commands/test-creds.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {