@tryarcanist/cli 0.1.80 → 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 +139 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -200,7 +200,7 @@ function validateApiUrl(url) {
200
200
  // src/runtime.ts
201
201
  import { randomUUID } from "crypto";
202
202
  import { createInterface } from "readline/promises";
203
- var ANSI_CONTROL_SEQUENCE = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
203
+ var ANSI_CONTROL_SEQUENCE = /\u001b\[[0-9:;<=>?]*[ -/]*[@-~]/g;
204
204
  function getRuntimeOptions(command, options = {}) {
205
205
  const globals = command?.optsWithGlobals?.();
206
206
  const merged = { ...globals, ...options };
@@ -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";
@@ -434,6 +446,27 @@ function validateUploadedName(name, kind) {
434
446
  function trimNamedItems(items) {
435
447
  return items.map((item) => ({ ...item, name: item.name.trim() }));
436
448
  }
449
+ function validateUploadItems({
450
+ items,
451
+ existingNames = [],
452
+ kind,
453
+ max,
454
+ label,
455
+ normalizeItems,
456
+ validateItem
457
+ }) {
458
+ const normalized = normalizeItems ? normalizeItems(items) : [...items];
459
+ const deduped = deduplicateByName(normalized, existingNames);
460
+ const capacityError = checkUploadCapacity(existingNames.length, deduped.length, max, label);
461
+ if (capacityError) return fail(capacityError);
462
+ for (const item of deduped) {
463
+ const nameError = validateUploadedName(item.name, kind);
464
+ if (nameError) return fail(nameError);
465
+ const itemError = validateItem(item);
466
+ if (itemError) return fail(itemError);
467
+ }
468
+ return ok(deduped);
469
+ }
437
470
  function hasNullBytes(content) {
438
471
  return content.slice(0, 8192).includes("\0");
439
472
  }
@@ -455,22 +488,24 @@ function deduplicateByName(items, existingNames = []) {
455
488
  return deduped;
456
489
  }
457
490
  function validateUploadedFilePayload(files, existingNames = []) {
458
- const trimmed = trimNamedItems(files);
459
- const deduped = deduplicateByName(trimmed, existingNames);
460
- const capacityError = checkUploadCapacity(existingNames.length, deduped.length, MAX_UPLOADED_FILES, "uploaded files");
461
- if (capacityError) return fail(capacityError);
462
- for (const file of deduped) {
463
- const nameError = validateUploadedName(file.name, "file");
464
- if (nameError) return fail(nameError);
465
- const byteLength = new TextEncoder().encode(file.content).byteLength;
466
- if (byteLength > MAX_UPLOADED_FILE_SIZE_BYTES) {
467
- return fail(`Uploaded file too large: ${file.name} (${byteLength} bytes, max ${MAX_UPLOADED_FILE_SIZE_BYTES})`);
468
- }
469
- if (hasNullBytes(file.content)) {
470
- return fail(`Binary files are not supported: ${file.name}`);
491
+ return validateUploadItems({
492
+ items: files,
493
+ existingNames,
494
+ kind: "file",
495
+ max: MAX_UPLOADED_FILES,
496
+ label: "uploaded files",
497
+ normalizeItems: trimNamedItems,
498
+ validateItem: (file) => {
499
+ const byteLength = new TextEncoder().encode(file.content).byteLength;
500
+ if (byteLength > MAX_UPLOADED_FILE_SIZE_BYTES) {
501
+ return `Uploaded file too large: ${file.name} (${byteLength} bytes, max ${MAX_UPLOADED_FILE_SIZE_BYTES})`;
502
+ }
503
+ if (hasNullBytes(file.content)) {
504
+ return `Binary files are not supported: ${file.name}`;
505
+ }
506
+ return null;
471
507
  }
472
- }
473
- return ok(deduped);
508
+ });
474
509
  }
475
510
 
476
511
  // src/uploads.ts
@@ -938,6 +973,15 @@ function mergeMemoryRefs(ids, rawRefs) {
938
973
  const ref = { id };
939
974
  if (typeof record.path === "string" && record.path.trim()) ref.path = record.path.trim();
940
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
+ }
941
985
  byId.set(id, ref);
942
986
  }
943
987
  }
@@ -1379,6 +1423,20 @@ function formatSessionErrorMessage(error, code) {
1379
1423
  }
1380
1424
 
1381
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"]);
1382
1440
  function formatDate(value) {
1383
1441
  const date = new Date(value);
1384
1442
  if (Number.isNaN(date.getTime())) return value;
@@ -1578,9 +1636,21 @@ function parseSsePayload(payload) {
1578
1636
  for (const message of messages) {
1579
1637
  const data = message.data ? parseJsonObject(message.data) : {};
1580
1638
  if (message.event === "status") {
1639
+ const phaseRaw = data.phase;
1640
+ const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
1581
1641
  const entry = {
1582
- status: typeof data.status === "string" ? data.status : "unknown"
1642
+ phase
1583
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;
1584
1654
  if (typeof data.title === "string") entry.title = data.title;
1585
1655
  if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
1586
1656
  entry.spawnDurationMs = data.spawnDurationMs;
@@ -1711,10 +1781,16 @@ function parseNonNegativeInteger(raw, name, defaultValue) {
1711
1781
  return Number(raw);
1712
1782
  }
1713
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;
1714
1790
  const details = [];
1715
1791
  if (status.title) details.push(status.title);
1716
1792
  if (status.spawnDurationMs != null) details.push(`spawn ${(status.spawnDurationMs / 1e3).toFixed(1)}s`);
1717
- return details.length > 0 ? `[status] ${status.status} | ${details.join(" | ")}` : `[status] ${status.status}`;
1793
+ return details.length > 0 ? `[status] ${phaseLabel} | ${details.join(" | ")}` : `[status] ${phaseLabel}`;
1718
1794
  }
1719
1795
  async function fetchPromptLabels(config, sessionId) {
1720
1796
  const data = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
@@ -1794,7 +1870,7 @@ async function watchCommand(sessionId, options, command) {
1794
1870
  console.log(rendered.line);
1795
1871
  }
1796
1872
  if (receivedFullPage) continue;
1797
- 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;
1798
1874
  await sleep(effectivePollIntervalMs);
1799
1875
  }
1800
1876
  } finally {
@@ -1827,12 +1903,13 @@ function buildPromptFailureError(sessionId, prompt, sessionUrl) {
1827
1903
  hint: `Inspect with: arcanist sessions transcript ${sessionId}`
1828
1904
  });
1829
1905
  }
1830
- function extractSessionStatus(payload) {
1906
+ function extractSessionLifecycle(payload) {
1831
1907
  const session = payload.session;
1832
- if (session && typeof session === "object" && typeof session.status === "string") {
1833
- return String(session.status);
1834
- }
1835
- 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 };
1836
1913
  }
1837
1914
  async function fetchCreatedPromptStatus(config, sessionId, promptId) {
1838
1915
  const promptList = await apiFetch(config, `/api/sessions/${sessionId}/prompts`);
@@ -1846,7 +1923,8 @@ async function waitForCreatedPromptToSettle(config, sessionId, promptId, pollInt
1846
1923
  return createdPrompt;
1847
1924
  }
1848
1925
  const sessionPayload = await apiFetch(config, `/api/sessions/${sessionId}`);
1849
- if (WATCH_TERMINAL_STATUSES.has(extractSessionStatus(sessionPayload))) {
1926
+ const lifecycle = extractSessionLifecycle(sessionPayload);
1927
+ if (lifecycle.phase && isWatchTerminal(lifecycle.phase, lifecycle.sessionKind)) {
1850
1928
  return fetchCreatedPromptStatus(config, sessionId, promptId);
1851
1929
  }
1852
1930
  await sleep(effectivePollIntervalMs);
@@ -2142,17 +2220,38 @@ async function usageCommand(sessionId, options, command) {
2142
2220
  async function stopCommand(sessionId, options = {}, command) {
2143
2221
  const runtime = getRuntimeOptions(command, options);
2144
2222
  const config = requireConfig(runtime);
2145
- const response = await apiFetch(config, `/api/sessions/${sessionId}/stop`, {
2146
- method: "POST"
2147
- });
2148
- 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
+ }
2149
2234
  if (isJson(command, options)) {
2150
2235
  writeJson({ sessionId, status });
2151
2236
  return;
2152
2237
  }
2153
- console.log(
2154
- status === "already_stopped" ? `Session ${sessionId} is already stopped.` : `Stop requested for session ${sessionId}.`
2155
- );
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
+ }
2156
2255
  }
2157
2256
 
2158
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.80",
3
+ "version": "0.1.82",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {