@h-rig/cli 0.0.6-alpha.67 → 0.0.6-alpha.69

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.
@@ -66,8 +66,6 @@ import {
66
66
  deleteRunState,
67
67
  listOpenEpics,
68
68
  resolveDefaultEpic,
69
- runResume,
70
- runRestart,
71
69
  runStatus,
72
70
  runStop,
73
71
  startRun,
@@ -194,6 +192,15 @@ function isRemoteConnectionSelected(projectRoot) {
194
192
 
195
193
  // packages/cli/src/commands/_server-client.ts
196
194
  var scopedGitHubBearerTokens = new Map;
195
+ var serverPhaseListener = null;
196
+ function setServerPhaseListener(listener) {
197
+ const previous = serverPhaseListener;
198
+ serverPhaseListener = listener;
199
+ return previous;
200
+ }
201
+ function reportServerPhase(label) {
202
+ serverPhaseListener?.(label);
203
+ }
197
204
  function cleanToken(value) {
198
205
  const trimmed = value?.trim();
199
206
  return trimmed ? trimmed : null;
@@ -236,6 +243,7 @@ async function ensureServerForCli(projectRoot) {
236
243
  try {
237
244
  const selected = resolveSelectedConnection(projectRoot);
238
245
  if (selected?.connection.kind === "remote") {
246
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
239
247
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
240
248
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
241
249
  return {
@@ -245,6 +253,7 @@ async function ensureServerForCli(projectRoot) {
245
253
  serverProjectRoot
246
254
  };
247
255
  }
256
+ reportServerPhase("Starting local Rig server\u2026");
248
257
  const connection = await ensureLocalRigServerConnection(projectRoot);
249
258
  return {
250
259
  baseUrl: connection.baseUrl,
@@ -351,6 +360,7 @@ async function requestServerJson(context, pathname, init = {}) {
351
360
  const headers = mergeHeaders(init.headers, server.authToken);
352
361
  if (server.serverProjectRoot)
353
362
  headers.set("x-rig-project-root", server.serverProjectRoot);
363
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
354
364
  let response;
355
365
  try {
356
366
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -409,6 +419,38 @@ async function getRunTimelineViaServer(context, runId, options = {}) {
409
419
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
410
420
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
411
421
  }
422
+ var RESUMABLE_RUN_STATUSES = new Set([
423
+ "created",
424
+ "preparing",
425
+ "running",
426
+ "validating",
427
+ "reviewing",
428
+ "stopped",
429
+ "failed",
430
+ "needs-attention",
431
+ "needs_attention"
432
+ ]);
433
+ async function resumeRunViaServer(context, runId, options) {
434
+ let targetRunId = runId?.trim() || null;
435
+ if (!targetRunId) {
436
+ const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
437
+ targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
438
+ }
439
+ if (!targetRunId) {
440
+ throw new CliError(options.restart ? "No run is available to restart." : "No resumable run is available.", 2, { hint: "List runs with `rig run list`, then pass an explicit id: `rig run resume <run-id>`." });
441
+ }
442
+ const payload = await requestServerJson(context, "/api/runs/resume", {
443
+ method: "POST",
444
+ headers: { "content-type": "application/json" },
445
+ body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
446
+ });
447
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
448
+ if (record.ok === false) {
449
+ const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
450
+ throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
451
+ }
452
+ return { ok: true, runId: targetRunId, ...record };
453
+ }
412
454
  async function stopRunViaServer(context, runId) {
413
455
  const payload = await requestServerJson(context, "/api/runs/stop", {
414
456
  method: "POST",
@@ -811,7 +853,12 @@ async function attachRunBundledPiFrontend(context, input) {
811
853
  };
812
854
  let detached = false;
813
855
  try {
814
- await runPiMain([], {
856
+ await runPiMain([
857
+ "--no-extensions",
858
+ "--no-skills",
859
+ "--no-prompt-templates",
860
+ "--no-context-files"
861
+ ], {
815
862
  extensionFactories: [piRigExtensionFactory]
816
863
  });
817
864
  detached = true;
@@ -834,6 +881,127 @@ async function attachRunBundledPiFrontend(context, input) {
834
881
  };
835
882
  }
836
883
 
884
+ // packages/cli/src/commands/_async-ui.ts
885
+ import pc from "picocolors";
886
+
887
+ // packages/cli/src/commands/_spinner.ts
888
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
889
+ function createTtySpinner(input) {
890
+ const output = input.output ?? process.stdout;
891
+ const isTty = output.isTTY === true;
892
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
893
+ let label = input.label;
894
+ let frame = 0;
895
+ let paused = false;
896
+ let stopped = false;
897
+ let lastPrintedLabel = "";
898
+ const render = () => {
899
+ if (stopped || paused)
900
+ return;
901
+ if (!isTty) {
902
+ if (label !== lastPrintedLabel) {
903
+ output.write(`${label}
904
+ `);
905
+ lastPrintedLabel = label;
906
+ }
907
+ return;
908
+ }
909
+ frame = (frame + 1) % frames.length;
910
+ const glyph = frames[frame] ?? frames[0] ?? "";
911
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
912
+ };
913
+ const clearLine = () => {
914
+ if (isTty)
915
+ output.write("\r\x1B[2K");
916
+ };
917
+ render();
918
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
919
+ return {
920
+ setLabel(next) {
921
+ label = next;
922
+ render();
923
+ },
924
+ pause() {
925
+ paused = true;
926
+ clearLine();
927
+ },
928
+ resume() {
929
+ if (stopped)
930
+ return;
931
+ paused = false;
932
+ render();
933
+ },
934
+ stop(finalLine) {
935
+ if (stopped)
936
+ return;
937
+ stopped = true;
938
+ if (timer)
939
+ clearInterval(timer);
940
+ clearLine();
941
+ if (finalLine)
942
+ output.write(`${finalLine}
943
+ `);
944
+ }
945
+ };
946
+ }
947
+
948
+ // packages/cli/src/commands/_async-ui.ts
949
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
950
+ var DONE_SYMBOL = pc.green("\u25C7");
951
+ var FAIL_SYMBOL = pc.red("\u25A0");
952
+ var activeUpdate = null;
953
+ async function withSpinner(label, work, options = {}) {
954
+ if (options.outputMode === "json") {
955
+ return work(() => {});
956
+ }
957
+ if (activeUpdate) {
958
+ const outer = activeUpdate;
959
+ outer(label);
960
+ return work(outer);
961
+ }
962
+ const output = options.output ?? process.stderr;
963
+ const isTty = output.isTTY === true;
964
+ let lastLabel = label;
965
+ if (!isTty) {
966
+ output.write(`${label}
967
+ `);
968
+ const update2 = (next) => {
969
+ lastLabel = next;
970
+ };
971
+ activeUpdate = update2;
972
+ const previousListener2 = setServerPhaseListener(update2);
973
+ try {
974
+ return await work(update2);
975
+ } finally {
976
+ activeUpdate = null;
977
+ setServerPhaseListener(previousListener2);
978
+ }
979
+ }
980
+ const spinner = createTtySpinner({
981
+ label,
982
+ output,
983
+ frames: CLACK_SPINNER_FRAMES,
984
+ styleFrame: (frame) => pc.magenta(frame)
985
+ });
986
+ const update = (next) => {
987
+ lastLabel = next;
988
+ spinner.setLabel(next);
989
+ };
990
+ activeUpdate = update;
991
+ const previousListener = setServerPhaseListener(update);
992
+ try {
993
+ const result = await work(update);
994
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
995
+ return result;
996
+ } catch (error) {
997
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
998
+ throw error;
999
+ } finally {
1000
+ activeUpdate = null;
1001
+ setServerPhaseListener(previousListener);
1002
+ }
1003
+ }
1004
+
837
1005
  // packages/cli/src/commands/_operator-view.ts
838
1006
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
839
1007
  function runStatusFromPayload(payload) {
@@ -876,8 +1044,9 @@ async function readOperatorSnapshot(context, runId, options = {}) {
876
1044
  }
877
1045
  async function attachRunOperatorView(context, input) {
878
1046
  let steered = false;
879
- if (input.message?.trim()) {
880
- await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
1047
+ const attachMessage = input.message?.trim();
1048
+ if (attachMessage) {
1049
+ await withSpinner("Queueing steering message\u2026", () => sendRunPiPromptViaServer(context, input.runId, attachMessage, "steer").catch(() => steerRunViaServer(context, input.runId, attachMessage)), { outputMode: context.outputMode });
881
1050
  steered = true;
882
1051
  }
883
1052
  if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
@@ -887,7 +1056,7 @@ async function attachRunOperatorView(context, input) {
887
1056
  });
888
1057
  }
889
1058
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
890
- let snapshot = await readOperatorSnapshot(context, input.runId);
1059
+ let snapshot = await withSpinner(`Connecting to run ${input.runId}\u2026`, () => readOperatorSnapshot(context, input.runId), { outputMode: context.outputMode });
891
1060
  if (context.outputMode === "text") {
892
1061
  surface.renderSnapshot(snapshot);
893
1062
  surface.renderTimeline(snapshot.timeline);
@@ -927,7 +1096,7 @@ async function attachRunOperatorView(context, input) {
927
1096
 
928
1097
  // packages/cli/src/commands/_cli-format.ts
929
1098
  import { log, note } from "@clack/prompts";
930
- import pc from "picocolors";
1099
+ import pc2 from "picocolors";
931
1100
  function stringField(record, key, fallback = "") {
932
1101
  const value = record[key];
933
1102
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
@@ -949,14 +1118,14 @@ function pad(value, width) {
949
1118
  function statusColor(status) {
950
1119
  const normalized = status.toLowerCase();
951
1120
  if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
952
- return pc.green;
1121
+ return pc2.green;
953
1122
  if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
954
- return pc.red;
1123
+ return pc2.red;
955
1124
  if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
956
- return pc.cyan;
1125
+ return pc2.cyan;
957
1126
  if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
958
- return pc.yellow;
959
- return pc.dim;
1127
+ return pc2.yellow;
1128
+ return pc2.dim;
960
1129
  }
961
1130
  function compactDate(value) {
962
1131
  if (!value.trim())
@@ -1001,23 +1170,23 @@ function formatStatusPill(status) {
1001
1170
  return statusColor(label)(`\u25CF ${label}`);
1002
1171
  }
1003
1172
  function formatSection(title, subtitle) {
1004
- return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
1173
+ return `${pc2.bold(pc2.cyan("\u25C6"))} ${pc2.bold(title)}${subtitle ? pc2.dim(` \u2014 ${subtitle}`) : ""}`;
1005
1174
  }
1006
1175
  function formatSuccessCard(title, rows = []) {
1007
- const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(12))} ${value}`);
1176
+ const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc2.dim("\u2502")} ${pc2.dim(key.padEnd(12))} ${value}`);
1008
1177
  return [formatSection(title), ...body].join(`
1009
1178
  `);
1010
1179
  }
1011
1180
  function formatNextSteps(steps) {
1012
1181
  if (steps.length === 0)
1013
1182
  return [];
1014
- return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
1183
+ return [pc2.bold("Next"), ...steps.map((step) => `${pc2.dim("\u203A")} ${step}`)];
1015
1184
  }
1016
1185
  function formatRunList(runs, options = {}) {
1017
1186
  if (runs.length === 0) {
1018
1187
  return [
1019
1188
  formatSection("Runs", "none recorded"),
1020
- options.source === "server" ? pc.dim("No runs recorded on the selected Rig server.") : pc.dim("No runs recorded in .rig/runs."),
1189
+ options.source === "server" ? pc2.dim("No runs recorded on the selected Rig server.") : pc2.dim("No runs recorded in .rig/runs."),
1021
1190
  "",
1022
1191
  ...formatNextSteps(["Start one: `rig task run --next`", "Check server: `rig server status`"])
1023
1192
  ].join(`
@@ -1033,11 +1202,11 @@ function formatRunList(runs, options = {}) {
1033
1202
  });
1034
1203
  const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
1035
1204
  const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
1036
- const header = `${pc.bold(pad("RUN", idWidth))} ${pc.bold(pad("STATUS", statusWidth))} ${pc.bold("TITLE")}`;
1205
+ const header = `${pc2.bold(pad("RUN", idWidth))} ${pc2.bold(pad("STATUS", statusWidth))} ${pc2.bold("TITLE")}`;
1037
1206
  const body = rows.map((row) => [
1038
- pc.bold(pad(truncate(row.runId, idWidth), idWidth)),
1207
+ pc2.bold(pad(truncate(row.runId, idWidth), idWidth)),
1039
1208
  statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
1040
- `${row.title}${row.runtime ? pc.dim(` ${row.runtime}`) : ""}`
1209
+ `${row.title}${row.runtime ? pc2.dim(` ${row.runtime}`) : ""}`
1041
1210
  ].join(" "));
1042
1211
  return [formatSection("Runs", options.source === "server" ? "selected server" : "local state"), header, ...body, "", ...formatNextSteps(["Follow live: `rig run attach <run-id> --follow`", "Details: `rig run show <run-id>`"])].join(`
1043
1212
  `);
@@ -1062,7 +1231,7 @@ function formatRunCard(run, options = {}) {
1062
1231
  const approvals = Array.isArray(merged.approvals) ? merged.approvals.length : null;
1063
1232
  const inputs = Array.isArray(merged.userInputs) ? merged.userInputs.length : null;
1064
1233
  const rows = [
1065
- ["run", pc.bold(runId)],
1234
+ ["run", pc2.bold(runId)],
1066
1235
  ["status", formatStatusPill(status)],
1067
1236
  ["task", taskId],
1068
1237
  ["title", title],
@@ -1088,17 +1257,17 @@ function formatRunStatus(summary, options = {}) {
1088
1257
  const activeRuns = summary.activeRuns ?? [];
1089
1258
  const recentRuns = summary.recentRuns ?? [];
1090
1259
  const lines = [formatSection("Run status", options.source === "server" ? "selected server" : "local state")];
1091
- lines.push("", pc.bold(`Active runs (${activeRuns.length})`));
1260
+ lines.push("", pc2.bold(`Active runs (${activeRuns.length})`));
1092
1261
  if (activeRuns.length === 0) {
1093
- lines.push(pc.dim("No active runs."));
1262
+ lines.push(pc2.dim("No active runs."));
1094
1263
  } else {
1095
1264
  for (const run of activeRuns) {
1096
1265
  lines.push(formatRunSummaryLine(run));
1097
1266
  }
1098
1267
  }
1099
- lines.push("", pc.bold(`Recent runs (${recentRuns.length})`));
1268
+ lines.push("", pc2.bold(`Recent runs (${recentRuns.length})`));
1100
1269
  if (recentRuns.length === 0) {
1101
- lines.push(pc.dim("No recent terminal runs."));
1270
+ lines.push(pc2.dim("No recent terminal runs."));
1102
1271
  } else {
1103
1272
  for (const run of recentRuns.slice(0, 10)) {
1104
1273
  lines.push(formatRunSummaryLine(run));
@@ -1116,7 +1285,7 @@ function formatRunSummaryLine(run) {
1116
1285
  const title = runTitleOf(record);
1117
1286
  const runtime = firstString(record, ["runtimeAdapter", "runtime", "adapter"]);
1118
1287
  const descriptor = [taskId, title].filter(Boolean).join(" \xB7 ");
1119
- return `${pc.dim("\u2502")} ${pc.bold(runId)} ${formatStatusPill(status)} ${descriptor}${runtime ? pc.dim(` ${runtime}`) : ""}`;
1288
+ return `${pc2.dim("\u2502")} ${pc2.bold(runId)} ${formatStatusPill(status)} ${descriptor}${runtime ? pc2.dim(` ${runtime}`) : ""}`;
1120
1289
  }
1121
1290
 
1122
1291
  // packages/cli/src/commands/inbox.ts
@@ -1250,7 +1419,7 @@ async function executeRun(context, args) {
1250
1419
  switch (command) {
1251
1420
  case "list": {
1252
1421
  requireNoExtraArgs(rest, "rig run list");
1253
- const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
1422
+ const { runs, source } = isRemoteConnectionSelected(context.projectRoot) ? await withSpinner("Reading runs from server\u2026", () => listRunsForSelectedConnection(context, { limit: 100 }), { outputMode: context.outputMode }) : await listRunsForSelectedConnection(context, { limit: 100 });
1254
1423
  if (context.outputMode === "text") {
1255
1424
  printFormattedOutput(formatRunList(runs, { source }));
1256
1425
  await printPendingInboxFooter(context);
@@ -1323,7 +1492,7 @@ async function executeRun(context, args) {
1323
1492
  if (!runId) {
1324
1493
  throw new CliError("run show requires a run id.", 1, { hint: "Run `rig run list` to find run ids, then `rig run show <run-id>`." });
1325
1494
  }
1326
- const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
1495
+ const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await withSpinner(`Reading run ${runId} from server\u2026`, () => getRunDetailsViaServer(context, runId).catch(() => ({})), { outputMode: context.outputMode }));
1327
1496
  if (!record) {
1328
1497
  throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
1329
1498
  }
@@ -1344,7 +1513,8 @@ async function executeRun(context, args) {
1344
1513
  }
1345
1514
  const renderer = createPiRunStreamRenderer();
1346
1515
  let cursor = null;
1347
- const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
1516
+ const timelineRunId = run.value;
1517
+ const page = await withSpinner(`Reading timeline for ${timelineRunId}\u2026`, () => getRunTimelineViaServer(context, timelineRunId, { limit: 500 }), { outputMode: context.outputMode });
1348
1518
  const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1349
1519
  cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
1350
1520
  if (context.outputMode === "text") {
@@ -1421,8 +1591,9 @@ async function executeRun(context, args) {
1421
1591
  throw new CliError("run attach requires a run id.", 2, { hint: "Run `rig run list` to find run ids, then `rig run attach <run-id> --follow`." });
1422
1592
  }
1423
1593
  let steered = false;
1424
- if (messageOption.value?.trim()) {
1425
- await steerRunViaServer(context, runId, messageOption.value.trim());
1594
+ const steerMessage = messageOption.value?.trim();
1595
+ if (steerMessage) {
1596
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, steerMessage), { outputMode: context.outputMode });
1426
1597
  steered = true;
1427
1598
  }
1428
1599
  const attached = await attachRunOperatorView(context, {
@@ -1442,7 +1613,7 @@ async function executeRun(context, args) {
1442
1613
  }
1443
1614
  return { ok: true, group: "run", command };
1444
1615
  }
1445
- const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
1616
+ const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await withSpinner("Reading run status from server\u2026", () => listRunsViaServer(context, { limit: 100 }), { outputMode: context.outputMode })) : runStatus(context.projectRoot, runtimeContext);
1446
1617
  const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
1447
1618
  const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
1448
1619
  if (context.outputMode === "text") {
@@ -1531,7 +1702,7 @@ async function executeRun(context, args) {
1531
1702
  }
1532
1703
  return { ok: true, group: "run", command };
1533
1704
  }
1534
- const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
1705
+ const resumed = await withSpinner(targetRunId ? `Resuming run ${targetRunId}\u2026` : "Resuming latest resumable run\u2026", () => resumeRunViaServer(context, targetRunId, { restart: false }), { outputMode: context.outputMode });
1535
1706
  if (context.outputMode === "text") {
1536
1707
  console.log(`Resumed run: ${resumed.runId}`);
1537
1708
  }
@@ -1550,7 +1721,7 @@ async function executeRun(context, args) {
1550
1721
  }
1551
1722
  return { ok: true, group: "run", command };
1552
1723
  }
1553
- const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
1724
+ const restarted = await withSpinner(targetRunId ? `Restarting run ${targetRunId}\u2026` : "Restarting latest run\u2026", () => resumeRunViaServer(context, targetRunId, { restart: true }), { outputMode: context.outputMode });
1554
1725
  if (context.outputMode === "text") {
1555
1726
  console.log(`Restarted run: ${restarted.runId}`);
1556
1727
  }
@@ -1577,7 +1748,8 @@ async function executeRun(context, args) {
1577
1748
  }
1578
1749
  return { ok: true, group: "run", command, details: { runId, dryRun: true } };
1579
1750
  }
1580
- await steerRunViaServer(context, runId, message.trim());
1751
+ const trimmedMessage = message.trim();
1752
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, trimmedMessage), { outputMode: context.outputMode });
1581
1753
  if (context.outputMode === "text") {
1582
1754
  console.log(`Steering message queued for ${runId}.`);
1583
1755
  }
@@ -1598,7 +1770,7 @@ async function executeRun(context, args) {
1598
1770
  };
1599
1771
  }
1600
1772
  if (runId) {
1601
- const stopped = await stopRunViaServer(context, runId);
1773
+ const stopped = await withSpinner(`Requesting stop for ${runId}\u2026`, () => stopRunViaServer(context, runId), { outputMode: context.outputMode });
1602
1774
  if (context.outputMode === "text")
1603
1775
  console.log(`Stop requested: ${runId}`);
1604
1776
  return { ok: true, group: "run", command, details: stopped };
@@ -361,6 +361,10 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
361
361
  import { resolve as resolve2 } from "path";
362
362
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
363
363
  var scopedGitHubBearerTokens = new Map;
364
+ var serverPhaseListener = null;
365
+ function reportServerPhase(label) {
366
+ serverPhaseListener?.(label);
367
+ }
364
368
  function cleanToken(value) {
365
369
  const trimmed = value?.trim();
366
370
  return trimmed ? trimmed : null;
@@ -403,6 +407,7 @@ async function ensureServerForCli(projectRoot) {
403
407
  try {
404
408
  const selected = resolveSelectedConnection(projectRoot);
405
409
  if (selected?.connection.kind === "remote") {
410
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
406
411
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
407
412
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
408
413
  return {
@@ -412,6 +417,7 @@ async function ensureServerForCli(projectRoot) {
412
417
  serverProjectRoot
413
418
  };
414
419
  }
420
+ reportServerPhase("Starting local Rig server\u2026");
415
421
  const connection = await ensureLocalRigServerConnection(projectRoot);
416
422
  return {
417
423
  baseUrl: connection.baseUrl,
@@ -518,6 +524,7 @@ async function requestServerJson(context, pathname, init = {}) {
518
524
  const headers = mergeHeaders(init.headers, server.authToken);
519
525
  if (server.serverProjectRoot)
520
526
  headers.set("x-rig-project-root", server.serverProjectRoot);
527
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
521
528
  let response;
522
529
  try {
523
530
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -546,6 +553,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
546
553
  }
547
554
  return payload;
548
555
  }
556
+ var RESUMABLE_RUN_STATUSES = new Set([
557
+ "created",
558
+ "preparing",
559
+ "running",
560
+ "validating",
561
+ "reviewing",
562
+ "stopped",
563
+ "failed",
564
+ "needs-attention",
565
+ "needs_attention"
566
+ ]);
549
567
  async function submitTaskRunViaServer(context, input) {
550
568
  const isTaskRun = Boolean(input.taskId);
551
569
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";