@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70

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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +4507 -1506
  3. package/dist/src/commands/_async-ui.js +152 -0
  4. package/dist/src/commands/_authority-runs.js +2 -3
  5. package/dist/src/commands/_cli-format.js +369 -0
  6. package/dist/src/commands/_connection-state.js +30 -11
  7. package/dist/src/commands/_doctor-checks.js +177 -43
  8. package/dist/src/commands/_help-catalog.js +485 -0
  9. package/dist/src/commands/_json-output.js +56 -0
  10. package/dist/src/commands/_operator-surface.js +220 -0
  11. package/dist/src/commands/_operator-view.js +595 -72
  12. package/dist/src/commands/_parsers.js +18 -11
  13. package/dist/src/commands/_pi-frontend.js +411 -0
  14. package/dist/src/commands/_pi-install.js +4 -3
  15. package/dist/src/commands/_policy.js +12 -5
  16. package/dist/src/commands/_preflight.js +187 -127
  17. package/dist/src/commands/_run-driver-helpers.js +75 -22
  18. package/dist/src/commands/_run-replay.js +142 -0
  19. package/dist/src/commands/_server-client.js +343 -60
  20. package/dist/src/commands/_snapshot-upload.js +160 -38
  21. package/dist/src/commands/_spinner.js +65 -0
  22. package/dist/src/commands/_task-picker.js +44 -16
  23. package/dist/src/commands/agent.js +39 -20
  24. package/dist/src/commands/browser.js +28 -21
  25. package/dist/src/commands/connect.js +146 -33
  26. package/dist/src/commands/dist.js +19 -12
  27. package/dist/src/commands/doctor.js +304 -44
  28. package/dist/src/commands/github.js +301 -52
  29. package/dist/src/commands/inbox.js +679 -72
  30. package/dist/src/commands/init.js +622 -118
  31. package/dist/src/commands/inspect.js +515 -32
  32. package/dist/src/commands/inspector.js +20 -13
  33. package/dist/src/commands/pi.js +177 -0
  34. package/dist/src/commands/plugin.js +95 -27
  35. package/dist/src/commands/profile-and-review.js +26 -19
  36. package/dist/src/commands/queue.js +32 -12
  37. package/dist/src/commands/remote.js +43 -36
  38. package/dist/src/commands/repo-git-harness.js +22 -15
  39. package/dist/src/commands/run.js +1162 -158
  40. package/dist/src/commands/server.js +373 -56
  41. package/dist/src/commands/setup.js +316 -62
  42. package/dist/src/commands/stats.js +1030 -0
  43. package/dist/src/commands/task-report-bug.js +29 -22
  44. package/dist/src/commands/task-run-driver.js +862 -129
  45. package/dist/src/commands/task.js +1423 -311
  46. package/dist/src/commands/test.js +15 -8
  47. package/dist/src/commands/workspace.js +18 -11
  48. package/dist/src/commands.js +4446 -1499
  49. package/dist/src/index.js +4502 -1504
  50. package/dist/src/launcher.js +77 -13
  51. package/dist/src/report-bug.js +3 -3
  52. package/dist/src/runner.js +16 -22
  53. package/package.json +10 -5
@@ -1,17 +1,23 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_server-client.ts
3
- import { spawnSync } from "child_process";
4
3
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5
4
  import { resolve as resolve2 } from "path";
6
5
 
7
6
  // packages/cli/src/runner.ts
8
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
- import { CliError } from "@rig/runtime/control-plane/errors";
8
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
10
9
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
11
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
12
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
13
10
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
14
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
11
+
12
+ class CliError extends RuntimeCliError {
13
+ hint;
14
+ constructor(message, exitCode = 1, options = {}) {
15
+ super(message, exitCode);
16
+ if (options.hint?.trim()) {
17
+ this.hint = options.hint.trim();
18
+ }
19
+ }
20
+ }
15
21
 
16
22
  // packages/cli/src/commands/_server-client.ts
17
23
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
@@ -38,9 +44,14 @@ function readJsonFile(path) {
38
44
  try {
39
45
  return JSON.parse(readFileSync(path, "utf8"));
40
46
  } catch (error) {
41
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
47
+ throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
42
48
  }
43
49
  }
50
+ function writeJsonFile(path, value) {
51
+ mkdirSync(dirname(path), { recursive: true });
52
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
53
+ `, "utf8");
54
+ }
44
55
  function normalizeConnection(value) {
45
56
  if (!value || typeof value !== "object" || Array.isArray(value))
46
57
  return null;
@@ -81,31 +92,51 @@ function readRepoConnection(projectRoot) {
81
92
  return {
82
93
  selected,
83
94
  project: typeof record.project === "string" ? record.project : undefined,
84
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
95
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
96
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
85
97
  };
86
98
  }
99
+ function writeRepoConnection(projectRoot, state) {
100
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
101
+ }
87
102
  function resolveSelectedConnection(projectRoot, options = {}) {
88
103
  const repo = readRepoConnection(projectRoot);
89
104
  if (!repo)
90
105
  return null;
91
106
  if (repo.selected === "local")
92
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
107
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
93
108
  const global = readGlobalConnections(options);
94
109
  const connection = global.connections[repo.selected];
95
110
  if (!connection) {
96
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
111
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
97
112
  }
98
- return { alias: repo.selected, connection };
113
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
114
+ }
115
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
116
+ const repo = readRepoConnection(projectRoot);
117
+ if (!repo)
118
+ return;
119
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
99
120
  }
100
121
 
101
122
  // packages/cli/src/commands/_server-client.ts
102
- var cachedGitHubBearerToken;
123
+ var scopedGitHubBearerTokens = new Map;
124
+ var serverPhaseListener = null;
125
+ function setServerPhaseListener(listener) {
126
+ const previous = serverPhaseListener;
127
+ serverPhaseListener = listener;
128
+ return previous;
129
+ }
130
+ function reportServerPhase(label) {
131
+ serverPhaseListener?.(label);
132
+ }
103
133
  function cleanToken(value) {
104
134
  const trimmed = value?.trim();
105
135
  return trimmed ? trimmed : null;
106
136
  }
107
- function setGitHubBearerTokenForCurrentProcess(token) {
108
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
137
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
138
+ const scopedKey = resolve2(projectRoot ?? process.cwd());
139
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
109
140
  }
110
141
  function readPrivateRemoteSessionToken(projectRoot) {
111
142
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
@@ -119,49 +150,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
119
150
  }
120
151
  }
121
152
  function readGitHubBearerTokenForRemote(projectRoot) {
122
- if (cachedGitHubBearerToken !== undefined)
123
- return cachedGitHubBearerToken;
153
+ const scopedKey = resolve2(projectRoot);
154
+ if (scopedGitHubBearerTokens.has(scopedKey))
155
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
124
156
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
125
- if (privateSession) {
126
- cachedGitHubBearerToken = privateSession;
127
- return cachedGitHubBearerToken;
128
- }
129
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
130
- if (envToken) {
131
- cachedGitHubBearerToken = envToken;
132
- return cachedGitHubBearerToken;
157
+ if (privateSession)
158
+ return privateSession;
159
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
160
+ }
161
+ function readStoredGitHubAuthToken(projectRoot) {
162
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
163
+ if (!existsSync2(path))
164
+ return null;
165
+ try {
166
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
167
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
168
+ } catch {
169
+ return null;
133
170
  }
134
- const result = spawnSync("gh", ["auth", "token"], {
135
- encoding: "utf8",
136
- timeout: 5000,
137
- stdio: ["ignore", "pipe", "ignore"]
138
- });
139
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
140
- return cachedGitHubBearerToken;
171
+ }
172
+ function readLocalConnectionFallbackToken(projectRoot) {
173
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
141
174
  }
142
175
  async function ensureServerForCli(projectRoot) {
143
176
  try {
144
177
  const selected = resolveSelectedConnection(projectRoot);
145
178
  if (selected?.connection.kind === "remote") {
179
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
180
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
181
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
146
182
  return {
147
183
  baseUrl: selected.connection.baseUrl,
148
- authToken: readGitHubBearerTokenForRemote(projectRoot),
149
- connectionKind: "remote"
184
+ authToken,
185
+ connectionKind: "remote",
186
+ serverProjectRoot
150
187
  };
151
188
  }
189
+ reportServerPhase("Starting local Rig server\u2026");
152
190
  const connection = await ensureLocalRigServerConnection(projectRoot);
153
191
  return {
154
192
  baseUrl: connection.baseUrl,
155
- authToken: connection.authToken,
156
- connectionKind: "local"
193
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
194
+ connectionKind: "local",
195
+ serverProjectRoot: resolve2(projectRoot)
157
196
  };
158
197
  } catch (error) {
159
198
  if (error instanceof Error) {
160
- throw new CliError2(error.message, 1);
199
+ throw new CliError(error.message, 1);
161
200
  }
162
201
  throw error;
163
202
  }
164
203
  }
204
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
205
+ const repo = readRepoConnection(projectRoot);
206
+ const slug = repo?.project?.trim();
207
+ if (!slug)
208
+ return null;
209
+ try {
210
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
211
+ headers: mergeHeaders(undefined, authToken)
212
+ });
213
+ if (!response.ok)
214
+ return null;
215
+ const payload = await response.json();
216
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
217
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
218
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
219
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
220
+ if (path)
221
+ writeRepoServerProjectRoot(projectRoot, path);
222
+ return path;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
165
227
  function appendTaskFilterParams(url, filters) {
166
228
  if (filters.assignee)
167
229
  url.searchParams.set("assignee", filters.assignee);
@@ -194,12 +256,65 @@ function diagnosticMessage(payload) {
194
256
  });
195
257
  return messages.length > 0 ? messages.join("; ") : null;
196
258
  }
259
+ var serverReachabilityCache = new Map;
260
+ async function probeServerReachability(baseUrl, authToken) {
261
+ try {
262
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
263
+ headers: mergeHeaders(undefined, authToken),
264
+ signal: AbortSignal.timeout(1500)
265
+ });
266
+ return response.ok;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
272
+ const key = resolve2(projectRoot);
273
+ const cached = serverReachabilityCache.get(key);
274
+ if (cached)
275
+ return cached;
276
+ const probe = probeServerReachability(baseUrl, authToken);
277
+ serverReachabilityCache.set(key, probe);
278
+ return probe;
279
+ }
280
+ function describeSelectedServer(projectRoot, server) {
281
+ try {
282
+ const selected = resolveSelectedConnection(projectRoot);
283
+ if (selected) {
284
+ return {
285
+ alias: selected.alias,
286
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
287
+ };
288
+ }
289
+ } catch {}
290
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
291
+ }
292
+ async function buildServerFailureContext(projectRoot, server) {
293
+ const { alias, target } = describeSelectedServer(projectRoot, server);
294
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
295
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
296
+ return {
297
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
298
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
299
+ };
300
+ }
197
301
  async function requestServerJson(context, pathname, init = {}) {
198
302
  const server = await ensureServerForCli(context.projectRoot);
199
- const response = await fetch(`${server.baseUrl}${pathname}`, {
200
- ...init,
201
- headers: mergeHeaders(init.headers, server.authToken)
202
- });
303
+ const headers = mergeHeaders(init.headers, server.authToken);
304
+ if (server.serverProjectRoot)
305
+ headers.set("x-rig-project-root", server.serverProjectRoot);
306
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
307
+ let response;
308
+ try {
309
+ response = await fetch(`${server.baseUrl}${pathname}`, {
310
+ ...init,
311
+ headers
312
+ });
313
+ } catch (error) {
314
+ const failure = await buildServerFailureContext(context.projectRoot, server);
315
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
316
+ ${failure.contextLine}`, 1, { hint: failure.hint });
317
+ }
203
318
  const text = await response.text();
204
319
  const payload = text.trim().length > 0 ? (() => {
205
320
  try {
@@ -211,7 +326,9 @@ async function requestServerJson(context, pathname, init = {}) {
211
326
  if (!response.ok) {
212
327
  const diagnostics = diagnosticMessage(payload);
213
328
  const detail = diagnostics ?? (text || response.statusText);
214
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
329
+ const failure = await buildServerFailureContext(context.projectRoot, server);
330
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
331
+ ${failure.contextLine}`, 1, { hint: failure.hint });
215
332
  }
216
333
  return payload;
217
334
  }
@@ -220,7 +337,7 @@ async function listWorkspaceTasksViaServer(context, filters = {}) {
220
337
  appendTaskFilterParams(url, filters);
221
338
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
222
339
  if (!Array.isArray(payload)) {
223
- throw new CliError2("Rig server returned an invalid task list payload.", 1);
340
+ throw new CliError("Rig server returned an invalid task list payload.", 1, { hint: "Check the selected server with `rig server status`; mixed CLI/server versions can mismatch \u2014 try `rig doctor`." });
224
341
  }
225
342
  return payload.flatMap((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? [entry] : []);
226
343
  }
@@ -236,7 +353,7 @@ async function selectNextWorkspaceTaskViaServer(context, filters = {}) {
236
353
  appendTaskFilterParams(url, filters);
237
354
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
238
355
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
239
- throw new CliError2("Rig server returned an invalid next-task payload.", 1);
356
+ throw new CliError("Rig server returned an invalid next-task payload.", 1, { hint: "Check the selected server with `rig server status`; try `rig task list` to see the raw task set." });
240
357
  }
241
358
  const record = payload;
242
359
  const rawTask = record.task;
@@ -275,32 +392,50 @@ async function registerProjectViaServer(context, input) {
275
392
  function sleep(ms) {
276
393
  return new Promise((resolve3) => setTimeout(resolve3, ms));
277
394
  }
395
+ function isRetryableProjectRootSwitchError(error) {
396
+ if (!(error instanceof Error))
397
+ return false;
398
+ const message = error.message.toLowerCase();
399
+ return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
400
+ }
278
401
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
279
- const switched = await requestServerJson(context, "/api/server/project-root", {
280
- method: "POST",
281
- headers: { "content-type": "application/json" },
282
- body: JSON.stringify({ projectRoot })
283
- });
284
402
  const timeoutMs = options.timeoutMs ?? 30000;
285
403
  const pollMs = options.pollMs ?? 1000;
286
404
  const deadline = Date.now() + timeoutMs;
287
405
  let lastError;
406
+ let switched = null;
288
407
  while (Date.now() < deadline) {
289
408
  try {
290
- const status = await requestServerJson(context, "/api/server/status");
291
- if (status && typeof status === "object" && !Array.isArray(status)) {
292
- const record = status;
293
- if (record.projectRoot === projectRoot) {
294
- return { ok: true, switched, status: record };
295
- }
296
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
297
- }
409
+ switched = await requestServerJson(context, "/api/server/project-root", {
410
+ method: "POST",
411
+ headers: { "content-type": "application/json" },
412
+ body: JSON.stringify({ projectRoot })
413
+ });
414
+ break;
298
415
  } catch (error) {
299
416
  lastError = error;
417
+ if (!isRetryableProjectRootSwitchError(error))
418
+ throw error;
419
+ await sleep(pollMs);
300
420
  }
301
- await sleep(pollMs);
302
421
  }
303
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
422
+ if (!switched) {
423
+ throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
424
+ }
425
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
426
+ if (record.ok === true) {
427
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
428
+ return { ok: true, switched: record };
429
+ }
430
+ throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
431
+ }
432
+ async function listRunsViaServer(context, options = {}) {
433
+ const url = new URL("http://rig.local/api/runs");
434
+ if (options.limit !== undefined)
435
+ url.searchParams.set("limit", String(options.limit));
436
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
437
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
438
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
304
439
  }
305
440
  async function getRunDetailsViaServer(context, runId) {
306
441
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
@@ -315,6 +450,69 @@ async function getRunLogsViaServer(context, runId, options = {}) {
315
450
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
316
451
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
317
452
  }
453
+ async function getRunTimelineViaServer(context, runId, options = {}) {
454
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
455
+ if (options.limit !== undefined)
456
+ url.searchParams.set("limit", String(options.limit));
457
+ if (options.cursor)
458
+ url.searchParams.set("cursor", options.cursor);
459
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
460
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
461
+ }
462
+ async function ensureTaskLabelsViaServer(context) {
463
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
464
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
465
+ }
466
+ async function listGitHubProjectsViaServer(context, owner) {
467
+ const url = new URL("http://rig.local/api/github/projects");
468
+ url.searchParams.set("owner", owner);
469
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
470
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
471
+ }
472
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
473
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
474
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
475
+ }
476
+ async function updateWorkspaceTaskViaServer(context, input) {
477
+ const payload = await requestServerJson(context, "/api/tasks/update", {
478
+ method: "POST",
479
+ headers: { "content-type": "application/json" },
480
+ body: JSON.stringify(input)
481
+ });
482
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
483
+ }
484
+ var RESUMABLE_RUN_STATUSES = new Set([
485
+ "created",
486
+ "preparing",
487
+ "running",
488
+ "validating",
489
+ "reviewing",
490
+ "stopped",
491
+ "failed",
492
+ "needs-attention",
493
+ "needs_attention"
494
+ ]);
495
+ async function resumeRunViaServer(context, runId, options) {
496
+ let targetRunId = runId?.trim() || null;
497
+ if (!targetRunId) {
498
+ const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
499
+ targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
500
+ }
501
+ if (!targetRunId) {
502
+ 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>`." });
503
+ }
504
+ const payload = await requestServerJson(context, "/api/runs/resume", {
505
+ method: "POST",
506
+ headers: { "content-type": "application/json" },
507
+ body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
508
+ });
509
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
510
+ if (record.ok === false) {
511
+ const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
512
+ throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
513
+ }
514
+ return { ok: true, runId: targetRunId, ...record };
515
+ }
318
516
  async function stopRunViaServer(context, runId) {
319
517
  const payload = await requestServerJson(context, "/api/runs/stop", {
320
518
  method: "POST",
@@ -331,6 +529,72 @@ async function steerRunViaServer(context, runId, message) {
331
529
  });
332
530
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
333
531
  }
532
+ async function getRunPiSessionViaServer(context, runId) {
533
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
534
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
535
+ }
536
+ async function getRunPiMessagesViaServer(context, runId) {
537
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
538
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
539
+ }
540
+ async function getRunPiStatusViaServer(context, runId) {
541
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
542
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
543
+ }
544
+ async function getRunPiCommandsViaServer(context, runId) {
545
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
546
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
547
+ }
548
+ async function getRunPiCapabilitiesViaServer(context, runId) {
549
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
550
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
551
+ }
552
+ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
553
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
554
+ method: "POST",
555
+ headers: { "content-type": "application/json" },
556
+ body: JSON.stringify({ text, streamingBehavior })
557
+ });
558
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
559
+ }
560
+ async function sendRunPiShellViaServer(context, runId, text) {
561
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
562
+ method: "POST",
563
+ headers: { "content-type": "application/json" },
564
+ body: JSON.stringify({ text })
565
+ });
566
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
567
+ }
568
+ async function runRunPiCommandViaServer(context, runId, text) {
569
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
570
+ method: "POST",
571
+ headers: { "content-type": "application/json" },
572
+ body: JSON.stringify({ text })
573
+ });
574
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
575
+ }
576
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
577
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
578
+ method: "POST",
579
+ headers: { "content-type": "application/json" },
580
+ body: JSON.stringify({ requestId, ...valueOrCancel })
581
+ });
582
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
583
+ }
584
+ async function abortRunPiViaServer(context, runId) {
585
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
586
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
587
+ }
588
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
589
+ const server = await ensureServerForCli(context.projectRoot);
590
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
591
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
592
+ if (server.authToken)
593
+ url.searchParams.set("token", server.authToken);
594
+ if (server.serverProjectRoot)
595
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
596
+ return url.toString();
597
+ }
334
598
  async function submitTaskRunViaServer(context, input) {
335
599
  const isTaskRun = Boolean(input.taskId);
336
600
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -354,29 +618,48 @@ async function submitTaskRunViaServer(context, input) {
354
618
  })
355
619
  });
356
620
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
357
- throw new CliError2("Rig server returned an invalid run submission payload.", 1);
621
+ throw new CliError("Rig server returned an invalid run submission payload.", 1, { hint: "Check `rig server status` and retry; `rig run list` shows whether the run was created anyway." });
358
622
  }
359
623
  const runId = payload.runId;
360
624
  if (typeof runId !== "string" || runId.trim().length === 0) {
361
- throw new CliError2("Rig server returned no runId for the submitted run.", 1);
625
+ throw new CliError("Rig server returned no runId for the submitted run.", 1, { hint: "Check `rig run list` \u2014 the run may still have been created; otherwise retry the submission." });
362
626
  }
363
627
  return { runId };
364
628
  }
365
629
  export {
630
+ updateWorkspaceTaskViaServer,
366
631
  switchServerProjectRootViaServer,
367
632
  submitTaskRunViaServer,
368
633
  stopRunViaServer,
369
634
  steerRunViaServer,
635
+ setServerPhaseListener,
370
636
  setGitHubBearerTokenForCurrentProcess,
637
+ sendRunPiShellViaServer,
638
+ sendRunPiPromptViaServer,
371
639
  selectNextWorkspaceTaskViaServer,
640
+ runRunPiCommandViaServer,
641
+ resumeRunViaServer,
642
+ respondRunPiExtensionUiViaServer,
372
643
  requestServerJson,
373
644
  registerProjectViaServer,
374
645
  prepareRemoteCheckoutViaServer,
375
646
  postGitHubTokenViaServer,
376
647
  listWorkspaceTasksViaServer,
648
+ listRunsViaServer,
649
+ listGitHubProjectsViaServer,
377
650
  getWorkspaceTaskViaServer,
651
+ getRunTimelineViaServer,
652
+ getRunPiStatusViaServer,
653
+ getRunPiSessionViaServer,
654
+ getRunPiMessagesViaServer,
655
+ getRunPiCommandsViaServer,
656
+ getRunPiCapabilitiesViaServer,
378
657
  getRunLogsViaServer,
379
658
  getRunDetailsViaServer,
659
+ getGitHubProjectStatusFieldViaServer,
380
660
  getGitHubAuthStatusViaServer,
381
- ensureServerForCli
661
+ ensureTaskLabelsViaServer,
662
+ ensureServerForCli,
663
+ buildRunPiEventsWebSocketUrl,
664
+ abortRunPiViaServer
382
665
  };