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

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,12 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
7
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
8
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
10
17
 
11
18
  // packages/cli/src/commands/_preflight.ts
12
19
  import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/project-main-pre-run-sync";
@@ -33,9 +40,14 @@ function readJsonFile(path) {
33
40
  try {
34
41
  return JSON.parse(readFileSync(path, "utf8"));
35
42
  } catch (error) {
36
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
43
+ 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>`." });
37
44
  }
38
45
  }
46
+ function writeJsonFile(path, value) {
47
+ mkdirSync(dirname(path), { recursive: true });
48
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
49
+ `, "utf8");
50
+ }
39
51
  function normalizeConnection(value) {
40
52
  if (!value || typeof value !== "object" || Array.isArray(value))
41
53
  return null;
@@ -76,29 +88,42 @@ function readRepoConnection(projectRoot) {
76
88
  return {
77
89
  selected,
78
90
  project: typeof record.project === "string" ? record.project : undefined,
79
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
91
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
92
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
80
93
  };
81
94
  }
95
+ function writeRepoConnection(projectRoot, state) {
96
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
97
+ }
82
98
  function resolveSelectedConnection(projectRoot, options = {}) {
83
99
  const repo = readRepoConnection(projectRoot);
84
100
  if (!repo)
85
101
  return null;
86
102
  if (repo.selected === "local")
87
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
103
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
88
104
  const global = readGlobalConnections(options);
89
105
  const connection = global.connections[repo.selected];
90
106
  if (!connection) {
91
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
107
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
92
108
  }
93
- return { alias: repo.selected, connection };
109
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
110
+ }
111
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
112
+ const repo = readRepoConnection(projectRoot);
113
+ if (!repo)
114
+ return;
115
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
94
116
  }
95
117
 
96
118
  // packages/cli/src/commands/_server-client.ts
97
- import { spawnSync } from "child_process";
98
119
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
99
120
  import { resolve as resolve2 } from "path";
100
121
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
101
- var cachedGitHubBearerToken;
122
+ var scopedGitHubBearerTokens = new Map;
123
+ var serverPhaseListener = null;
124
+ function reportServerPhase(label) {
125
+ serverPhaseListener?.(label);
126
+ }
102
127
  function cleanToken(value) {
103
128
  const trimmed = value?.trim();
104
129
  return trimmed ? trimmed : null;
@@ -115,49 +140,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
115
140
  }
116
141
  }
117
142
  function readGitHubBearerTokenForRemote(projectRoot) {
118
- if (cachedGitHubBearerToken !== undefined)
119
- return cachedGitHubBearerToken;
143
+ const scopedKey = resolve2(projectRoot);
144
+ if (scopedGitHubBearerTokens.has(scopedKey))
145
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
120
146
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
121
- if (privateSession) {
122
- cachedGitHubBearerToken = privateSession;
123
- return cachedGitHubBearerToken;
124
- }
125
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
126
- if (envToken) {
127
- cachedGitHubBearerToken = envToken;
128
- return cachedGitHubBearerToken;
147
+ if (privateSession)
148
+ return privateSession;
149
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
150
+ }
151
+ function readStoredGitHubAuthToken(projectRoot) {
152
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
153
+ if (!existsSync2(path))
154
+ return null;
155
+ try {
156
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
157
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
158
+ } catch {
159
+ return null;
129
160
  }
130
- const result = spawnSync("gh", ["auth", "token"], {
131
- encoding: "utf8",
132
- timeout: 5000,
133
- stdio: ["ignore", "pipe", "ignore"]
134
- });
135
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
136
- return cachedGitHubBearerToken;
161
+ }
162
+ function readLocalConnectionFallbackToken(projectRoot) {
163
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
137
164
  }
138
165
  async function ensureServerForCli(projectRoot) {
139
166
  try {
140
167
  const selected = resolveSelectedConnection(projectRoot);
141
168
  if (selected?.connection.kind === "remote") {
169
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
170
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
171
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
142
172
  return {
143
173
  baseUrl: selected.connection.baseUrl,
144
- authToken: readGitHubBearerTokenForRemote(projectRoot),
145
- connectionKind: "remote"
174
+ authToken,
175
+ connectionKind: "remote",
176
+ serverProjectRoot
146
177
  };
147
178
  }
179
+ reportServerPhase("Starting local Rig server\u2026");
148
180
  const connection = await ensureLocalRigServerConnection(projectRoot);
149
181
  return {
150
182
  baseUrl: connection.baseUrl,
151
- authToken: connection.authToken,
152
- connectionKind: "local"
183
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
184
+ connectionKind: "local",
185
+ serverProjectRoot: resolve2(projectRoot)
153
186
  };
154
187
  } catch (error) {
155
188
  if (error instanceof Error) {
156
- throw new CliError2(error.message, 1);
189
+ throw new CliError(error.message, 1);
157
190
  }
158
191
  throw error;
159
192
  }
160
193
  }
194
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
195
+ const repo = readRepoConnection(projectRoot);
196
+ const slug = repo?.project?.trim();
197
+ if (!slug)
198
+ return null;
199
+ try {
200
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
201
+ headers: mergeHeaders(undefined, authToken)
202
+ });
203
+ if (!response.ok)
204
+ return null;
205
+ const payload = await response.json();
206
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
207
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
208
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
209
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
210
+ if (path)
211
+ writeRepoServerProjectRoot(projectRoot, path);
212
+ return path;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
161
217
  function mergeHeaders(headers, authToken) {
162
218
  const merged = new Headers(headers);
163
219
  if (authToken) {
@@ -180,12 +236,65 @@ function diagnosticMessage(payload) {
180
236
  });
181
237
  return messages.length > 0 ? messages.join("; ") : null;
182
238
  }
239
+ var serverReachabilityCache = new Map;
240
+ async function probeServerReachability(baseUrl, authToken) {
241
+ try {
242
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
243
+ headers: mergeHeaders(undefined, authToken),
244
+ signal: AbortSignal.timeout(1500)
245
+ });
246
+ return response.ok;
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
252
+ const key = resolve2(projectRoot);
253
+ const cached = serverReachabilityCache.get(key);
254
+ if (cached)
255
+ return cached;
256
+ const probe = probeServerReachability(baseUrl, authToken);
257
+ serverReachabilityCache.set(key, probe);
258
+ return probe;
259
+ }
260
+ function describeSelectedServer(projectRoot, server) {
261
+ try {
262
+ const selected = resolveSelectedConnection(projectRoot);
263
+ if (selected) {
264
+ return {
265
+ alias: selected.alias,
266
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
267
+ };
268
+ }
269
+ } catch {}
270
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
271
+ }
272
+ async function buildServerFailureContext(projectRoot, server) {
273
+ const { alias, target } = describeSelectedServer(projectRoot, server);
274
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
275
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
276
+ return {
277
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
278
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
279
+ };
280
+ }
183
281
  async function requestServerJson(context, pathname, init = {}) {
184
282
  const server = await ensureServerForCli(context.projectRoot);
185
- const response = await fetch(`${server.baseUrl}${pathname}`, {
186
- ...init,
187
- headers: mergeHeaders(init.headers, server.authToken)
188
- });
283
+ const headers = mergeHeaders(init.headers, server.authToken);
284
+ if (server.serverProjectRoot)
285
+ headers.set("x-rig-project-root", server.serverProjectRoot);
286
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
287
+ let response;
288
+ try {
289
+ response = await fetch(`${server.baseUrl}${pathname}`, {
290
+ ...init,
291
+ headers
292
+ });
293
+ } catch (error) {
294
+ const failure = await buildServerFailureContext(context.projectRoot, server);
295
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
296
+ ${failure.contextLine}`, 1, { hint: failure.hint });
297
+ }
189
298
  const text = await response.text();
190
299
  const payload = text.trim().length > 0 ? (() => {
191
300
  try {
@@ -197,82 +306,23 @@ async function requestServerJson(context, pathname, init = {}) {
197
306
  if (!response.ok) {
198
307
  const diagnostics = diagnosticMessage(payload);
199
308
  const detail = diagnostics ?? (text || response.statusText);
200
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
309
+ const failure = await buildServerFailureContext(context.projectRoot, server);
310
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
311
+ ${failure.contextLine}`, 1, { hint: failure.hint });
201
312
  }
202
313
  return payload;
203
314
  }
204
-
205
- // packages/cli/src/commands/_pi-install.ts
206
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
207
- import { homedir as homedir2 } from "os";
208
- import { resolve as resolve3 } from "path";
209
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
210
- async function defaultCommandRunner(command, options = {}) {
211
- const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
212
- const [stdout, stderr, exitCode] = await Promise.all([
213
- new Response(proc.stdout).text(),
214
- new Response(proc.stderr).text(),
215
- proc.exited
216
- ]);
217
- return { exitCode, stdout, stderr };
218
- }
219
- function resolvePiRigExtensionPath(homeDir) {
220
- return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
221
- }
222
- function resolvePiHomeDir(inputHomeDir) {
223
- return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
224
- }
225
- function piListContainsPiRig(output) {
226
- return output.split(/\r?\n/).some((line) => {
227
- const normalized = line.trim();
228
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
229
- });
230
- }
231
- async function safeRun(runner, command, options) {
232
- try {
233
- return await runner(command, options);
234
- } catch (error) {
235
- return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
236
- }
237
- }
238
- async function checkPiRigInstall(input = {}) {
239
- const home = resolvePiHomeDir(input.homeDir);
240
- const extensionPath = resolvePiRigExtensionPath(home);
241
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
242
- return {
243
- extensionPath,
244
- pi: { ok: true, label: "pi", detail: "fake-pi" },
245
- piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
246
- };
247
- }
248
- const exists = input.exists ?? existsSync3;
249
- const runner = input.commandRunner ?? defaultCommandRunner;
250
- const piResult = await safeRun(runner, ["pi", "--version"]);
251
- const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
252
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
253
- ${piListResult.stderr}`);
254
- const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
255
- const hasPiRig = listedPiRig;
256
- return {
257
- extensionPath,
258
- pi: {
259
- ok: piResult.exitCode === 0,
260
- label: "pi",
261
- detail: (piResult.stdout || piResult.stderr).trim() || undefined,
262
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
263
- },
264
- piRig: {
265
- ok: hasPiRig,
266
- label: "pi-rig global extension",
267
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
268
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
269
- }
270
- };
271
- }
272
- async function buildPiSetupChecks(input = {}) {
273
- const status = await checkPiRigInstall(input);
274
- return [status.pi, status.piRig];
275
- }
315
+ var RESUMABLE_RUN_STATUSES = new Set([
316
+ "created",
317
+ "preparing",
318
+ "running",
319
+ "validating",
320
+ "reviewing",
321
+ "stopped",
322
+ "failed",
323
+ "needs-attention",
324
+ "needs_attention"
325
+ ]);
276
326
 
277
327
  // packages/cli/src/commands/_preflight.ts
278
328
  function preflightCheck(id, label, status, detail, remediation) {
@@ -329,6 +379,9 @@ function permissionAllowsPr(payload) {
329
379
  }
330
380
  return null;
331
381
  }
382
+ function isNotFoundError(error) {
383
+ return /\b(404|not found)\b/i.test(message(error));
384
+ }
332
385
  function projectCheckoutReady(payload) {
333
386
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
334
387
  return null;
@@ -361,19 +414,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
361
414
  const checks = [];
362
415
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
363
416
  const taskId = options.taskId?.trim() || null;
417
+ const requiresCurrentRunApi = Boolean(taskId);
418
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
419
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
420
+ let legacyServerCompatibility = false;
364
421
  try {
365
422
  await request("/api/server/status");
366
423
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
367
424
  } catch (error) {
368
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
425
+ if (isNotFoundError(error)) {
426
+ try {
427
+ await request("/health");
428
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
429
+ checks.push(requiresCurrentRunApi && !allowLocalLegacyTaskRunCompatibility ? preflightCheck("server", "Rig server reachable", "fail", "legacy /health endpoint only; current task-run APIs are required", "Upgrade/select the Rig server before launching a task run.") : preflightCheck("server", "Rig server reachable", "pass", allowLocalLegacyTaskRunCompatibility ? "local legacy /health endpoint; submit endpoint will be authoritative" : "legacy /health endpoint"));
430
+ } catch (healthError) {
431
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
432
+ }
433
+ } else {
434
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
435
+ }
369
436
  }
370
437
  const repo = readRepoConnection(context.projectRoot);
371
- checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
438
+ checks.push(repo ? preflightCheck("project-link", "project linked to Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
372
439
  try {
373
440
  const auth = await request("/api/github/auth/status");
374
- checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
441
+ checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
375
442
  } catch (error) {
376
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
443
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
377
444
  }
378
445
  try {
379
446
  const projection = await request("/api/workspace/task-projection");
@@ -401,9 +468,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
401
468
  try {
402
469
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
403
470
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
404
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
471
+ checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
405
472
  } catch (error) {
406
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
473
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
407
474
  }
408
475
  try {
409
476
  const runs = await request("/api/runs?limit=200");
@@ -414,14 +481,7 @@ async function runFastTaskRunPreflight(context, options = {}) {
414
481
  }
415
482
  }
416
483
  if ((options.runtimeAdapter ?? "pi") === "pi") {
417
- const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
418
- ok: false,
419
- label: "pi/pi-rig checks",
420
- hint: message(error)
421
- }]);
422
- for (const pi of piChecks) {
423
- checks.push(preflightCheck(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "fail", pi.detail, pi.hint ?? (pi.ok ? undefined : "Run `rig init --yes` to install/update Pi and enable pi-rig.")));
424
- }
484
+ checks.push(preflightCheck("runtime", "worker Pi SDK session daemon", "pass", selectedServer?.connectionKind === "remote" ? "remote worker-owned runtime" : "bundled server-owned runtime"));
425
485
  } else {
426
486
  checks.push(preflightCheck("runtime", "runtime adapter", "pass", options.runtimeAdapter));
427
487
  }
@@ -429,9 +489,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
429
489
  if (failures.length > 0) {
430
490
  const summary = failures.map((check) => `${check.label}${check.detail ? `: ${check.detail}` : ""}`).join("; ");
431
491
  if (failures.some((check) => check.id === "duplicate-active-run") && taskId) {
432
- throw new CliError2(`Task ${taskId} already has an active Rig run. ${summary}`, 1);
492
+ throw new CliError(`Task ${taskId} already has an active Rig run. ${summary}`, 1, { hint: `Attach to it with \`rig run attach <run-id> --follow\`, or stop it first with \`rig run stop <run-id>\`.` });
433
493
  }
434
- throw new CliError2(`Task run preflight failed: ${summary}`, 1);
494
+ throw new CliError(`Task run preflight failed: ${summary}`, 1, { hint: "Run `rig doctor` to diagnose, then retry `rig task run`." });
435
495
  }
436
496
  return { ok: true, checks };
437
497
  }
@@ -448,7 +508,7 @@ async function runProjectMainSyncPreflight(context, options) {
448
508
  runBootstrap: async () => {
449
509
  const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
450
510
  if (bootstrap.exitCode !== 0) {
451
- throw new CliError2(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
511
+ throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
452
512
  }
453
513
  }
454
514
  });
@@ -5,21 +5,33 @@ import { resolve as resolve3 } from "path";
5
5
 
6
6
  // packages/cli/src/runner.ts
7
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
8
- import { CliError } from "@rig/runtime/control-plane/errors";
8
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
9
9
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
10
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
11
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
12
10
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
13
- 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
+ }
14
21
 
15
22
  // packages/cli/src/commands/_run-driver-helpers.ts
16
23
  import {
17
- appendJsonlRecord,
24
+ appendRunJournalEvent,
25
+ appendRunLogEntry,
26
+ appendRunTimelineEntry,
27
+ patchAuthorityRunRecord,
18
28
  readAuthorityRun as readAuthorityRun2,
19
- resolveAuthorityRunDir as resolveAuthorityRunDir2,
20
- writeJsonFile as writeJsonFile2
29
+ resolveAuthorityRunDir,
30
+ setAuthorityRunStatus,
31
+ writeJsonFile
21
32
  } from "@rig/runtime/control-plane/authority-files";
22
33
  import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
34
+ import { readPriorPrProgressPromptSection } from "@rig/runtime/control-plane/prompt-context";
23
35
  import { buildProviderTaskRunInstructionLines } from "@rig/runtime/control-plane/provider/runtime-instructions";
24
36
  import { loadRigTaskRunSkillBody } from "@rig/runtime/control-plane/provider/rig-task-run-skill";
25
37
 
@@ -29,8 +41,7 @@ import { resolve as resolve2 } from "path";
29
41
  import {
30
42
  readAuthorityRun,
31
43
  readJsonlFile,
32
- resolveAuthorityRunDir,
33
- writeJsonFile
44
+ writeAuthorityRunRecord
34
45
  } from "@rig/runtime/control-plane/authority-files";
35
46
 
36
47
  // packages/cli/src/commands/_paths.ts
@@ -64,16 +75,20 @@ function readLatestBeadRecord(projectRoot, taskId) {
64
75
 
65
76
  // packages/cli/src/commands/_run-driver-helpers.ts
66
77
  function patchAuthorityRun(projectRoot, runId, patch) {
67
- const current = readAuthorityRun2(projectRoot, runId);
68
- if (!current) {
69
- throw new CliError2(`Run not found: ${runId}`, 2);
78
+ const next = patchAuthorityRunRecord(projectRoot, runId, patch);
79
+ if (!next) {
80
+ throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
81
+ }
82
+ return next;
83
+ }
84
+ function setRunStatusOrFail(projectRoot, runId, to, options = {}) {
85
+ const next = setAuthorityRunStatus(projectRoot, runId, to, {
86
+ ...options,
87
+ actor: { kind: "agent" }
88
+ });
89
+ if (!next) {
90
+ throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
70
91
  }
71
- const next = {
72
- ...current,
73
- ...patch,
74
- updatedAt: new Date().toISOString()
75
- };
76
- writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
77
92
  return next;
78
93
  }
79
94
  function touchAuthorityRun(projectRoot, runId) {
@@ -81,21 +96,21 @@ function touchAuthorityRun(projectRoot, runId) {
81
96
  if (!current) {
82
97
  return;
83
98
  }
84
- writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), {
99
+ writeJsonFile(resolve3(resolveAuthorityRunDir(projectRoot, runId), "run.json"), {
85
100
  ...current,
86
101
  updatedAt: new Date().toISOString()
87
102
  });
88
103
  }
89
104
  function appendRunTimeline(projectRoot, runId, value) {
90
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), value);
105
+ appendRunTimelineEntry(projectRoot, runId, value);
91
106
  touchAuthorityRun(projectRoot, runId);
92
107
  }
93
108
  function appendRunLog(projectRoot, runId, value) {
94
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "logs.jsonl"), value);
109
+ appendRunLogEntry(projectRoot, runId, value);
95
110
  touchAuthorityRun(projectRoot, runId);
96
111
  }
97
112
  function appendRunAction(projectRoot, runId, value) {
98
- appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), {
113
+ appendRunTimelineEntry(projectRoot, runId, {
99
114
  id: value.id,
100
115
  type: "action",
101
116
  actionType: value.actionType,
@@ -149,8 +164,43 @@ function startRunAction(projectRoot, runId, input) {
149
164
  }
150
165
  };
151
166
  }
167
+ var runEventSinkProjectRoot = null;
168
+ function configureRunEventSink(projectRoot) {
169
+ runEventSinkProjectRoot = projectRoot;
170
+ }
171
+ var lastDoorbellRangAt = 0;
172
+ function ringServerRunDoorbell(runId) {
173
+ const serverUrl = process.env.RIG_SERVER_URL?.trim();
174
+ if (!serverUrl)
175
+ return;
176
+ const now = Date.now();
177
+ if (now - lastDoorbellRangAt < 250)
178
+ return;
179
+ lastDoorbellRangAt = now;
180
+ const token = process.env.RIG_AUTH_TOKEN || process.env.RIG_GITHUB_TOKEN || "";
181
+ fetch(`${serverUrl.replace(/\/+$/, "")}/api/runs/doorbell`, {
182
+ method: "POST",
183
+ headers: {
184
+ "content-type": "application/json",
185
+ authorization: `Bearer ${token}`
186
+ },
187
+ body: JSON.stringify({ runId }),
188
+ signal: AbortSignal.timeout(1000)
189
+ }).catch(() => {});
190
+ }
152
191
  function emitServerRunEvent(event) {
153
192
  console.log(`__RIG_RUN_EVENT__${JSON.stringify({ ...event, at: new Date().toISOString() })}`);
193
+ if (runEventSinkProjectRoot) {
194
+ try {
195
+ if (event.type === "status" || event.type === "completed" || event.type === "failed") {
196
+ appendRunJournalEvent(runEventSinkProjectRoot, event.runId, {
197
+ type: "timeline-entry",
198
+ payload: { kind: "driver-event", ...event }
199
+ });
200
+ }
201
+ } catch {}
202
+ ringServerRunDoorbell(event.runId);
203
+ }
154
204
  }
155
205
  function buildRunPrompt(input) {
156
206
  const initialPrompt = input.initialPrompt?.trim() || null;
@@ -207,6 +257,7 @@ ${acceptance}` : null,
207
257
  ${sourceContractLines.join(`
208
258
  `)}` : null,
209
259
  scopeText || renderSourceScopeValidation(sourceTask, sourceValidation) || null,
260
+ readPriorPrProgressPromptSection(input.projectRoot, input.taskId),
210
261
  initialPrompt ? `Additional operator guidance:
211
262
  ${initialPrompt}` : null,
212
263
  providerLines.length > 0 ? `Provider-specific runtime tooling:
@@ -280,8 +331,10 @@ function renderSourceScopeValidation(task, validation) {
280
331
  export {
281
332
  touchAuthorityRun,
282
333
  startRunAction,
334
+ setRunStatusOrFail,
283
335
  patchAuthorityRun,
284
336
  emitServerRunEvent,
337
+ configureRunEventSink,
285
338
  buildRunPrompt,
286
339
  appendRunTimeline,
287
340
  appendRunLog,