@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.21

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.
@@ -2464,17 +2464,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
2464
2464
  }
2465
2465
 
2466
2466
  // packages/cli/src/commands/_server-client.ts
2467
- import { spawnSync } from "child_process";
2468
2467
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2469
2468
  import { resolve as resolve8 } from "path";
2470
2469
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
2471
- var cachedGitHubBearerToken;
2470
+ var scopedGitHubBearerTokens = new Map;
2472
2471
  function cleanToken(value) {
2473
2472
  const trimmed = value?.trim();
2474
2473
  return trimmed ? trimmed : null;
2475
2474
  }
2476
- function setGitHubBearerTokenForCurrentProcess(token) {
2477
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
2475
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
2476
+ const scopedKey = resolve8(projectRoot ?? process.cwd());
2477
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
2478
2478
  }
2479
2479
  function readPrivateRemoteSessionToken(projectRoot) {
2480
2480
  const path = resolve8(projectRoot, ".rig", "state", "github-auth.json");
@@ -2488,25 +2488,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
2488
2488
  }
2489
2489
  }
2490
2490
  function readGitHubBearerTokenForRemote(projectRoot) {
2491
- if (cachedGitHubBearerToken !== undefined)
2492
- return cachedGitHubBearerToken;
2491
+ const scopedKey = resolve8(projectRoot);
2492
+ if (scopedGitHubBearerTokens.has(scopedKey))
2493
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
2493
2494
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
2494
- if (privateSession) {
2495
- cachedGitHubBearerToken = privateSession;
2496
- return cachedGitHubBearerToken;
2497
- }
2498
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
2499
- if (envToken) {
2500
- cachedGitHubBearerToken = envToken;
2501
- return cachedGitHubBearerToken;
2502
- }
2503
- const result = spawnSync("gh", ["auth", "token"], {
2504
- encoding: "utf8",
2505
- timeout: 5000,
2506
- stdio: ["ignore", "pipe", "ignore"]
2507
- });
2508
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
2509
- return cachedGitHubBearerToken;
2495
+ if (privateSession)
2496
+ return privateSession;
2497
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
2510
2498
  }
2511
2499
  async function ensureServerForCli(projectRoot) {
2512
2500
  try {
@@ -2644,16 +2632,36 @@ async function registerProjectViaServer(context, input) {
2644
2632
  function sleep(ms) {
2645
2633
  return new Promise((resolve9) => setTimeout(resolve9, ms));
2646
2634
  }
2635
+ function isRetryableProjectRootSwitchError(error) {
2636
+ if (!(error instanceof Error))
2637
+ return false;
2638
+ const message = error.message.toLowerCase();
2639
+ 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");
2640
+ }
2647
2641
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
2648
- const switched = await requestServerJson(context, "/api/server/project-root", {
2649
- method: "POST",
2650
- headers: { "content-type": "application/json" },
2651
- body: JSON.stringify({ projectRoot })
2652
- });
2653
2642
  const timeoutMs = options.timeoutMs ?? 30000;
2654
2643
  const pollMs = options.pollMs ?? 1000;
2655
2644
  const deadline = Date.now() + timeoutMs;
2656
2645
  let lastError;
2646
+ let switched = null;
2647
+ while (Date.now() < deadline) {
2648
+ try {
2649
+ switched = await requestServerJson(context, "/api/server/project-root", {
2650
+ method: "POST",
2651
+ headers: { "content-type": "application/json" },
2652
+ body: JSON.stringify({ projectRoot })
2653
+ });
2654
+ break;
2655
+ } catch (error) {
2656
+ lastError = error;
2657
+ if (!isRetryableProjectRootSwitchError(error))
2658
+ throw error;
2659
+ await sleep(pollMs);
2660
+ }
2661
+ }
2662
+ if (!switched) {
2663
+ throw new CliError2(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
2664
+ }
2657
2665
  while (Date.now() < deadline) {
2658
2666
  try {
2659
2667
  const status = await requestServerJson(context, "/api/server/status");
@@ -2671,6 +2679,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
2671
2679
  }
2672
2680
  throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
2673
2681
  }
2682
+ async function listRunsViaServer(context, options = {}) {
2683
+ const url = new URL("http://rig.local/api/runs");
2684
+ if (options.limit !== undefined)
2685
+ url.searchParams.set("limit", String(options.limit));
2686
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2687
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
2688
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
2689
+ }
2674
2690
  async function getRunDetailsViaServer(context, runId) {
2675
2691
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
2676
2692
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
@@ -2684,6 +2700,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
2684
2700
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2685
2701
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2686
2702
  }
2703
+ async function getRunTimelineViaServer(context, runId, options = {}) {
2704
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
2705
+ if (options.limit !== undefined)
2706
+ url.searchParams.set("limit", String(options.limit));
2707
+ if (options.cursor)
2708
+ url.searchParams.set("cursor", options.cursor);
2709
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2710
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2711
+ }
2712
+ async function ensureTaskLabelsViaServer(context) {
2713
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
2714
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2715
+ }
2716
+ async function listGitHubProjectsViaServer(context, owner) {
2717
+ const url = new URL("http://rig.local/api/github/projects");
2718
+ url.searchParams.set("owner", owner);
2719
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2720
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
2721
+ }
2722
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
2723
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
2724
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2725
+ }
2726
+ async function updateWorkspaceTaskViaServer(context, input) {
2727
+ const payload = await requestServerJson(context, "/api/tasks/update", {
2728
+ method: "POST",
2729
+ headers: { "content-type": "application/json" },
2730
+ body: JSON.stringify(input)
2731
+ });
2732
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2733
+ }
2687
2734
  async function stopRunViaServer(context, runId) {
2688
2735
  const payload = await requestServerJson(context, "/api/runs/stop", {
2689
2736
  method: "POST",
@@ -2736,7 +2783,8 @@ async function submitTaskRunViaServer(context, input) {
2736
2783
  import { existsSync as existsSync5, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2737
2784
  import { homedir as homedir3 } from "os";
2738
2785
  import { resolve as resolve9 } from "path";
2739
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2786
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2787
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2740
2788
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2741
2789
  export { default } from '@rig/pi-rig';
2742
2790
  `;
@@ -2764,7 +2812,7 @@ function resolvePiHomeDir(inputHomeDir) {
2764
2812
  function piListContainsPiRig(output) {
2765
2813
  return output.split(/\r?\n/).some((line) => {
2766
2814
  const normalized = line.trim();
2767
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
2815
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
2768
2816
  });
2769
2817
  }
2770
2818
  async function safeRun(runner, command, options) {
@@ -2880,7 +2928,7 @@ async function ensureRemotePiRigInstalled(input) {
2880
2928
  const payload = await input.requestJson("/api/pi-rig/install", {
2881
2929
  method: "POST",
2882
2930
  headers: { "content-type": "application/json" },
2883
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
2931
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
2884
2932
  });
2885
2933
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2886
2934
  const piOk = record.piOk === true || record.ok === true;
@@ -2963,6 +3011,9 @@ function permissionAllowsPr(payload) {
2963
3011
  }
2964
3012
  return null;
2965
3013
  }
3014
+ function isNotFoundError(error) {
3015
+ return /\b(404|not found)\b/i.test(message(error));
3016
+ }
2966
3017
  function projectCheckoutReady(payload) {
2967
3018
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
2968
3019
  return null;
@@ -2995,19 +3046,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
2995
3046
  const checks = [];
2996
3047
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
2997
3048
  const taskId = options.taskId?.trim() || null;
3049
+ const requiresCurrentRunApi = Boolean(taskId);
3050
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
3051
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
3052
+ let legacyServerCompatibility = false;
2998
3053
  try {
2999
3054
  await request("/api/server/status");
3000
3055
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
3001
3056
  } catch (error) {
3002
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3057
+ if (isNotFoundError(error)) {
3058
+ try {
3059
+ await request("/health");
3060
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
3061
+ 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"));
3062
+ } catch (healthError) {
3063
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
3064
+ }
3065
+ } else {
3066
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3067
+ }
3003
3068
  }
3004
3069
  const repo = readRepoConnection(context.projectRoot);
3005
- 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>`."));
3070
+ 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", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
3006
3071
  try {
3007
3072
  const auth = await request("/api/github/auth/status");
3008
- 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>`."));
3073
+ 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>`."));
3009
3074
  } catch (error) {
3010
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3075
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3011
3076
  }
3012
3077
  try {
3013
3078
  const projection = await request("/api/workspace/task-projection");
@@ -3035,9 +3100,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
3035
3100
  try {
3036
3101
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
3037
3102
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
3038
- 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."));
3103
+ 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."));
3039
3104
  } catch (error) {
3040
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
3105
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
3041
3106
  }
3042
3107
  try {
3043
3108
  const runs = await request("/api/runs?limit=200");
@@ -3240,6 +3305,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3240
3305
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3241
3306
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3242
3307
  const next = {
3308
+ ...existing ?? {},
3243
3309
  runId: input.runId,
3244
3310
  projectRoot,
3245
3311
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -3948,9 +4014,10 @@ async function executeInbox(context, args) {
3948
4014
 
3949
4015
  // packages/cli/src/commands/init.ts
3950
4016
  import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3951
- import { spawnSync as spawnSync2 } from "child_process";
4017
+ import { spawnSync } from "child_process";
3952
4018
  import { resolve as resolve16 } from "path";
3953
4019
  import { buildRigInitConfigSource } from "@rig/core";
4020
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
3954
4021
 
3955
4022
  // packages/cli/src/commands/_snapshot-upload.ts
3956
4023
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -4264,6 +4331,7 @@ function countDoctorFailures(checks) {
4264
4331
 
4265
4332
  // packages/cli/src/commands/init.ts
4266
4333
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4334
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
4267
4335
  var RIG_CONFIG_DEV_DEPENDENCIES = {
4268
4336
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
4269
4337
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -4274,7 +4342,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
4274
4342
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
4275
4343
  }
4276
4344
  function detectOriginRepoSlug(projectRoot) {
4277
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4345
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4278
4346
  if (result.status !== 0)
4279
4347
  return null;
4280
4348
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -4330,11 +4398,14 @@ function applyGitHubProjectConfig(source, options) {
4330
4398
  return source;
4331
4399
  const projectId = JSON.stringify(options.githubProject);
4332
4400
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
4401
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
4402
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
4403
+ `)},` : "";
4333
4404
  return source.replace(` projects: { enabled: false },`, [
4334
4405
  ` projects: {`,
4335
4406
  ` enabled: true,`,
4336
4407
  ` projectId: ${projectId},`,
4337
- ` statusFieldId: ${statusFieldId},`,
4408
+ ` statusFieldId: ${statusFieldId},${statuses}`,
4338
4409
  ` },`
4339
4410
  ].join(`
4340
4411
  `));
@@ -4355,21 +4426,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
4355
4426
  }
4356
4427
  }
4357
4428
  function detectGhLogin() {
4358
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4429
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4359
4430
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
4360
4431
  }
4361
4432
  function readGhAuthToken() {
4362
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4433
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
4363
4434
  if (result.status !== 0 || !result.stdout.trim()) {
4364
4435
  throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
4365
4436
  }
4366
4437
  return result.stdout.trim();
4367
4438
  }
4439
+ function refreshGhProjectScopesAndReadToken() {
4440
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
4441
+ encoding: "utf8",
4442
+ stdio: ["inherit", "pipe", "pipe"]
4443
+ });
4444
+ if (result.status !== 0)
4445
+ return null;
4446
+ try {
4447
+ return readGhAuthToken();
4448
+ } catch {
4449
+ return null;
4450
+ }
4451
+ }
4368
4452
  async function loadClackPrompts() {
4369
4453
  return await import("@clack/prompts");
4370
4454
  }
4455
+ function clackTextOptions(options) {
4456
+ return {
4457
+ message: options.message,
4458
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
4459
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
4460
+ };
4461
+ }
4371
4462
  async function promptRequiredText(prompts, options) {
4372
- const value = await prompts.text(options);
4463
+ const value = await prompts.text(clackTextOptions(options));
4373
4464
  if (prompts.isCancel(value))
4374
4465
  throw new CliError2("Init cancelled.", 1);
4375
4466
  const text2 = String(value ?? "").trim();
@@ -4378,7 +4469,7 @@ async function promptRequiredText(prompts, options) {
4378
4469
  return text2;
4379
4470
  }
4380
4471
  async function promptOptionalText(prompts, options) {
4381
- const value = await prompts.text(options);
4472
+ const value = await prompts.text(clackTextOptions(options));
4382
4473
  if (prompts.isCancel(value))
4383
4474
  throw new CliError2("Init cancelled.", 1);
4384
4475
  return String(value ?? "").trim();
@@ -4389,6 +4480,164 @@ async function promptSelect(prompts, options) {
4389
4480
  throw new CliError2("Init cancelled.", 1);
4390
4481
  return String(value);
4391
4482
  }
4483
+ function repoOwnerFromSlug(repoSlug) {
4484
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
4485
+ }
4486
+ function recordArray(value, key) {
4487
+ if (!value || typeof value !== "object" || Array.isArray(value))
4488
+ return [];
4489
+ const raw = value[key];
4490
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4491
+ }
4492
+ async function listGitHubProjectsForInit(context, owner, token) {
4493
+ if (token?.trim()) {
4494
+ try {
4495
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
4496
+ } catch (directError) {
4497
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
4498
+ if (recordArray(serverPayload, "projects").length > 0)
4499
+ return serverPayload;
4500
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
4501
+ }
4502
+ }
4503
+ return listGitHubProjectsViaServer(context, owner);
4504
+ }
4505
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
4506
+ if (token?.trim()) {
4507
+ try {
4508
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
4509
+ } catch (directError) {
4510
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
4511
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
4512
+ return serverPayload;
4513
+ }
4514
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
4515
+ }
4516
+ }
4517
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
4518
+ }
4519
+ var PROJECT_STATUS_PROMPTS = {
4520
+ running: "Running/In progress",
4521
+ prOpen: "PR open/review",
4522
+ ciFixing: "CI/review fixing",
4523
+ merging: "Merging",
4524
+ done: "Done",
4525
+ needsAttention: "Needs attention"
4526
+ };
4527
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
4528
+ running: "In Progress",
4529
+ prOpen: "In Review",
4530
+ ciFixing: "In Review",
4531
+ merging: "Merging",
4532
+ done: "Done",
4533
+ needsAttention: "Needs Attention"
4534
+ };
4535
+ async function promptManualProjectStatusMapping(prompts) {
4536
+ const statuses = {};
4537
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4538
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
4539
+ const value = await promptOptionalText(prompts, {
4540
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
4541
+ placeholder: defaultLabel
4542
+ });
4543
+ statuses[key] = value || defaultLabel;
4544
+ }
4545
+ return statuses;
4546
+ }
4547
+ function projectScopeError(value) {
4548
+ const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
4549
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
4550
+ }
4551
+ function optionName(option) {
4552
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
4553
+ }
4554
+ function autoProjectStatusValue(options, key, label) {
4555
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
4556
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
4557
+ if (!match)
4558
+ return null;
4559
+ return String(match.id ?? match.name);
4560
+ }
4561
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
4562
+ const projectChoice = await promptSelect(prompts, {
4563
+ message: "GitHub Projects status sync",
4564
+ initialValue: "select",
4565
+ options: [
4566
+ { value: "select", label: "Select accessible ProjectV2" },
4567
+ { value: "off", label: "Off" },
4568
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
4569
+ ]
4570
+ });
4571
+ if (projectChoice === "off")
4572
+ return { githubProject: "off" };
4573
+ if (projectChoice === "manual") {
4574
+ return {
4575
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
4576
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
4577
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
4578
+ };
4579
+ }
4580
+ const owner = repoOwnerFromSlug(repoSlug);
4581
+ if (!owner)
4582
+ throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
4583
+ let activeToken = githubToken?.trim() || null;
4584
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4585
+ let projects = recordArray(projectsPayload, "projects");
4586
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
4587
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
4588
+ const refreshedToken = refreshProjectToken();
4589
+ if (refreshedToken) {
4590
+ activeToken = refreshedToken;
4591
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4592
+ projects = recordArray(projectsPayload, "projects");
4593
+ }
4594
+ }
4595
+ if (projects.length === 0) {
4596
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
4597
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
4598
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
4599
+ }
4600
+ const selectedProjectId = await promptSelect(prompts, {
4601
+ message: "GitHub ProjectV2 project",
4602
+ options: [
4603
+ ...projects.map((project) => ({
4604
+ value: String(project.id),
4605
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
4606
+ hint: typeof project.url === "string" ? project.url : undefined
4607
+ })),
4608
+ { value: "manual", label: "Enter ProjectV2 id manually" }
4609
+ ]
4610
+ });
4611
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
4612
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
4613
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
4614
+ const rawField = fieldPayloadRecord.field;
4615
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
4616
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
4617
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4618
+ if (options.length === 0) {
4619
+ return {
4620
+ githubProject: projectId,
4621
+ githubProjectStatusField: fieldId,
4622
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
4623
+ ...activeToken ? { githubToken: activeToken } : {}
4624
+ };
4625
+ }
4626
+ const statuses = {};
4627
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4628
+ const auto = autoProjectStatusValue(options, key, label);
4629
+ statuses[key] = auto ?? await promptSelect(prompts, {
4630
+ message: `Project status option for ${label}`,
4631
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
4632
+ });
4633
+ }
4634
+ return {
4635
+ githubProject: projectId,
4636
+ githubProjectStatusField: fieldId,
4637
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
4638
+ ...activeToken ? { githubToken: activeToken } : {}
4639
+ };
4640
+ }
4392
4641
  function sleep2(ms) {
4393
4642
  return new Promise((resolve17) => setTimeout(resolve17, ms));
4394
4643
  }
@@ -4402,12 +4651,29 @@ function apiSessionTokenFrom(payload) {
4402
4651
  const token = payload.apiSessionToken;
4403
4652
  return typeof token === "string" && token.trim() ? token.trim() : null;
4404
4653
  }
4654
+ function cleanPayloadString(value) {
4655
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4656
+ }
4657
+ function remoteGitHubAuthMetadata(payload) {
4658
+ if (!payload)
4659
+ return {};
4660
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
4661
+ return {
4662
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
4663
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
4664
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
4665
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
4666
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
4667
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
4668
+ };
4669
+ }
4405
4670
  function writeRemoteGitHubAuthState(projectRoot, input) {
4406
4671
  writeFileSync5(resolve16(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
4407
4672
  authenticated: true,
4408
4673
  source: input.source,
4409
4674
  storedOnServer: true,
4410
4675
  selectedRepo: input.selectedRepo,
4676
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
4411
4677
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
4412
4678
  updatedAt: new Date().toISOString()
4413
4679
  }, null, 2)}
@@ -4500,12 +4766,13 @@ async function runControlPlaneInit(context, options) {
4500
4766
  if (token) {
4501
4767
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
4502
4768
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
4503
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4769
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4504
4770
  if (serverKind === "remote") {
4505
4771
  writeRemoteGitHubAuthState(projectRoot, {
4506
4772
  source: authMethod === "gh" ? "gh" : "init-token",
4507
4773
  selectedRepo: repo.slug,
4508
- apiSessionToken
4774
+ apiSessionToken,
4775
+ authPayload: githubAuth
4509
4776
  });
4510
4777
  }
4511
4778
  } else if (authMethod === "device") {
@@ -4524,9 +4791,9 @@ async function runControlPlaneInit(context, options) {
4524
4791
  if (completed) {
4525
4792
  const apiSessionToken = apiSessionTokenFrom(completed);
4526
4793
  if (apiSessionToken) {
4527
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
4794
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
4528
4795
  if (serverKind === "remote") {
4529
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
4796
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
4530
4797
  }
4531
4798
  }
4532
4799
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -4544,19 +4811,25 @@ async function runControlPlaneInit(context, options) {
4544
4811
  Object.assign(checkout, preparedCheckout);
4545
4812
  }
4546
4813
  }
4814
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
4815
+ if (serverKind === "remote" && checkoutPath && token) {
4816
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
4817
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
4818
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4819
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
4820
+ }
4547
4821
  const registered = await registerProjectViaServer(context, {
4548
4822
  repoSlug: repo.slug,
4549
4823
  checkout
4550
4824
  });
4551
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
4552
4825
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
4553
- if (serverRootSwitch && token) {
4554
- githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
4555
- const apiSessionToken = apiSessionTokenFrom(githubAuth);
4556
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4557
- writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
4558
- }
4559
4826
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
4827
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
4828
+ ok: false,
4829
+ ready: false,
4830
+ labelsReady: false,
4831
+ error: error instanceof Error ? error.message : String(error)
4832
+ }));
4560
4833
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
4561
4834
  remote: true,
4562
4835
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -4587,6 +4860,7 @@ async function runControlPlaneInit(context, options) {
4587
4860
  githubAuth,
4588
4861
  deviceAuth,
4589
4862
  githubAuthWarning: remoteGhTokenWarning,
4863
+ labelSetup,
4590
4864
  pi,
4591
4865
  doctor
4592
4866
  };
@@ -4693,12 +4967,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4693
4967
  });
4694
4968
  const serverChoice = await promptSelect(prompts, {
4695
4969
  message: "Rig server",
4970
+ initialValue: "remote",
4696
4971
  options: [
4697
- { value: "local", label: "Local server", hint: "run on this machine" },
4698
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
4972
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
4973
+ { value: "local", label: "Local server", hint: "run on this machine" }
4699
4974
  ]
4700
4975
  });
4701
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
4976
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
4702
4977
  let remoteCheckout;
4703
4978
  if (serverChoice === "remote") {
4704
4979
  const checkout = await promptSelect(prompts, {
@@ -4730,38 +5005,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4730
5005
  { value: "skip", label: "Skip for now" }
4731
5006
  ]
4732
5007
  });
5008
+ let remoteGhTokenConfirmed = false;
4733
5009
  if (serverChoice === "remote" && authMethod === "gh") {
4734
5010
  if (!prompts.confirm)
4735
5011
  throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
4736
5012
  const confirmed = await prompts.confirm({
4737
5013
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
4738
- initialValue: false
5014
+ initialValue: true
4739
5015
  });
4740
5016
  if (prompts.isCancel(confirmed) || confirmed !== true) {
4741
5017
  throw new CliError2("Remote gh-token import cancelled.", 1);
4742
5018
  }
5019
+ remoteGhTokenConfirmed = true;
4743
5020
  }
4744
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
4745
- const projectChoice = await promptSelect(prompts, {
4746
- message: "GitHub Projects status sync",
4747
- options: [
4748
- { value: "off", label: "Off" },
4749
- { value: "configure", label: "Configure ProjectV2 status field" }
4750
- ]
4751
- });
4752
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
4753
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
5021
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
5022
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
5023
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
4754
5024
  const result = await runControlPlaneInit(context, {
4755
5025
  server: serverChoice,
4756
5026
  remoteUrl,
4757
5027
  repoSlug,
4758
- githubToken,
5028
+ githubToken: effectiveGithubToken,
4759
5029
  githubAuthMethod: authMethod,
4760
- githubProject,
4761
- githubProjectStatusField,
5030
+ githubProject: projectConfig.githubProject,
5031
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
5032
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
4762
5033
  remoteCheckout,
4763
5034
  repair,
4764
- privateStateOnly
5035
+ privateStateOnly,
5036
+ yes: remoteGhTokenConfirmed || undefined
4765
5037
  });
4766
5038
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
4767
5039
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -4857,7 +5129,7 @@ Usage: rig connect <list|add|use|status>`, 1);
4857
5129
  }
4858
5130
 
4859
5131
  // packages/cli/src/commands/github.ts
4860
- import { spawnSync as spawnSync3 } from "child_process";
5132
+ import { spawnSync as spawnSync2 } from "child_process";
4861
5133
  function printPayload(context, payload, fallback) {
4862
5134
  if (context.outputMode === "json")
4863
5135
  console.log(JSON.stringify(payload, null, 2));
@@ -4865,7 +5137,7 @@ function printPayload(context, payload, fallback) {
4865
5137
  console.log(fallback);
4866
5138
  }
4867
5139
  function readGhToken() {
4868
- const result = spawnSync3("gh", ["auth", "token"], { encoding: "utf8" });
5140
+ const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4869
5141
  if (result.status !== 0) {
4870
5142
  const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
4871
5143
  throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
@@ -5897,14 +6169,10 @@ async function executeRemote(context, args) {
5897
6169
  }
5898
6170
 
5899
6171
  // packages/cli/src/commands/run.ts
5900
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
5901
- import { resolve as resolve19 } from "path";
5902
6172
  import { createInterface as createInterface2 } from "readline/promises";
5903
6173
  import {
5904
6174
  listAuthorityRuns as listAuthorityRuns3,
5905
- readAuthorityRun as readAuthorityRun4,
5906
- readJsonlFile as readJsonlFile4,
5907
- resolveAuthorityRunDir as resolveAuthorityRunDir5
6175
+ readAuthorityRun as readAuthorityRun4
5908
6176
  } from "@rig/runtime/control-plane/authority-files";
5909
6177
  import {
5910
6178
  cleanupRunState,
@@ -5912,6 +6180,7 @@ import {
5912
6180
  listOpenEpics,
5913
6181
  resolveDefaultEpic,
5914
6182
  runResume,
6183
+ runRestart,
5915
6184
  runStatus,
5916
6185
  runStop,
5917
6186
  startRun,
@@ -5919,9 +6188,9 @@ import {
5919
6188
  } from "@rig/runtime/control-plane/native/run-ops";
5920
6189
  import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
5921
6190
 
5922
- // packages/cli/src/commands/_operator-view.ts
6191
+ // packages/cli/src/commands/_operator-surface.ts
5923
6192
  import { createInterface } from "readline";
5924
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6193
+ import { createInterface as createPromptInterface } from "readline/promises";
5925
6194
  var CANONICAL_STAGES = [
5926
6195
  "Connect",
5927
6196
  "GitHub/task sync",
@@ -5936,18 +6205,188 @@ var CANONICAL_STAGES = [
5936
6205
  "Merge",
5937
6206
  "Complete"
5938
6207
  ];
6208
+ function logDetail(log3) {
6209
+ return typeof log3.detail === "string" ? log3.detail.trim() : "";
6210
+ }
6211
+ function parseProviderProtocolLog(title, detail) {
6212
+ if (title.trim().toLowerCase() !== "agent output")
6213
+ return null;
6214
+ if (!detail.startsWith("{") || !detail.endsWith("}"))
6215
+ return null;
6216
+ try {
6217
+ const record = JSON.parse(detail);
6218
+ if (!record || typeof record !== "object" || Array.isArray(record))
6219
+ return null;
6220
+ const type = record.type;
6221
+ return typeof type === "string" && [
6222
+ "assistant",
6223
+ "message_start",
6224
+ "message_update",
6225
+ "message_end",
6226
+ "stream_event",
6227
+ "tool_result",
6228
+ "tool_execution_start",
6229
+ "tool_execution_update",
6230
+ "tool_execution_end",
6231
+ "turn_start",
6232
+ "turn_end"
6233
+ ].includes(type) ? record : null;
6234
+ } catch {
6235
+ return null;
6236
+ }
6237
+ }
6238
+ function renderProviderProtocolLog(record) {
6239
+ const type = typeof record.type === "string" ? record.type : "";
6240
+ if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
6241
+ const toolName = String(record.toolName ?? record.name ?? "tool");
6242
+ const status = type === "tool_execution_start" ? "started" : type === "tool_execution_end" ? record.isError === true || record.result && typeof record.result === "object" && !Array.isArray(record.result) && record.result.isError === true ? "failed" : "completed" : "running";
6243
+ return `[Pi tool] ${toolName} ${status}`;
6244
+ }
6245
+ return null;
6246
+ }
6247
+ function entryId(entry, fallback) {
6248
+ return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
6249
+ }
5939
6250
  function renderOperatorSnapshot(snapshot) {
5940
6251
  const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
5941
6252
  const runId = String(run.runId ?? run.id ?? "run");
5942
6253
  const status = String(run.status ?? "unknown");
5943
6254
  const logs = snapshot.logs ?? [];
6255
+ const latestByStage = new Map;
6256
+ for (const log3 of logs) {
6257
+ const title = String(log3.title ?? "").toLowerCase();
6258
+ const stageName = String(log3.stage ?? "").toLowerCase();
6259
+ const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6260
+ if (stage)
6261
+ latestByStage.set(stage, log3);
6262
+ }
5944
6263
  const stageLines = CANONICAL_STAGES.flatMap((stage) => {
5945
- const match = logs.find((log3) => String(log3.title ?? "").toLowerCase() === stage.toLowerCase() || String(log3.stage ?? "").toLowerCase() === stage.toLowerCase());
5946
- return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
6264
+ const match = latestByStage.get(stage);
6265
+ return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
5947
6266
  });
5948
6267
  return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
5949
6268
  `);
5950
6269
  }
6270
+ function createPiRunStreamRenderer(output = process.stdout) {
6271
+ let lastSnapshot = "";
6272
+ const assistantTextById = new Map;
6273
+ const seenTimeline = new Set;
6274
+ const seenLogs = new Set;
6275
+ const writeLine = (line) => output.write(`${line}
6276
+ `);
6277
+ return {
6278
+ renderSnapshot(snapshot) {
6279
+ const rendered = renderOperatorSnapshot(snapshot);
6280
+ if (rendered && rendered !== lastSnapshot) {
6281
+ writeLine(rendered);
6282
+ lastSnapshot = rendered;
6283
+ }
6284
+ },
6285
+ renderTimeline(entries) {
6286
+ for (const [index, entry] of entries.entries()) {
6287
+ const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
6288
+ if (entry.type === "assistant_message" && typeof entry.text === "string") {
6289
+ const text2 = entry.text;
6290
+ const previousText = assistantTextById.get(id) ?? "";
6291
+ if (!previousText && text2.trim()) {
6292
+ writeLine("[Pi assistant]");
6293
+ }
6294
+ if (text2.startsWith(previousText)) {
6295
+ const delta = text2.slice(previousText.length);
6296
+ if (delta)
6297
+ output.write(delta);
6298
+ } else if (text2.trim() && text2 !== previousText) {
6299
+ if (previousText)
6300
+ writeLine(`
6301
+ [Pi assistant]`);
6302
+ output.write(text2);
6303
+ }
6304
+ assistantTextById.set(id, text2);
6305
+ continue;
6306
+ }
6307
+ if (seenTimeline.has(id))
6308
+ continue;
6309
+ seenTimeline.add(id);
6310
+ if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
6311
+ writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
6312
+ continue;
6313
+ }
6314
+ if (entry.type === "timeline_warning") {
6315
+ writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
6316
+ }
6317
+ }
6318
+ },
6319
+ renderLogs(entries) {
6320
+ for (const [index, entry] of entries.entries()) {
6321
+ const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
6322
+ if (seenLogs.has(id))
6323
+ continue;
6324
+ seenLogs.add(id);
6325
+ const title = String(entry.title ?? "");
6326
+ if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
6327
+ continue;
6328
+ const detail = logDetail(entry);
6329
+ if (!detail)
6330
+ continue;
6331
+ const protocolRecord = parseProviderProtocolLog(title, detail);
6332
+ if (protocolRecord) {
6333
+ const protocolLine = renderProviderProtocolLog(protocolRecord);
6334
+ if (protocolLine)
6335
+ writeLine(protocolLine);
6336
+ continue;
6337
+ }
6338
+ writeLine(`[${title || "Rig log"}] ${detail}`);
6339
+ }
6340
+ }
6341
+ };
6342
+ }
6343
+ function createOperatorSurface(options = {}) {
6344
+ const input = options.input ?? process.stdin;
6345
+ const output = options.output ?? process.stdout;
6346
+ const errorOutput = options.errorOutput ?? process.stderr;
6347
+ const renderer = createPiRunStreamRenderer(output);
6348
+ const writeLine = (line) => output.write(`${line}
6349
+ `);
6350
+ return {
6351
+ mode: "pi-compatible-text",
6352
+ ...renderer,
6353
+ info: writeLine,
6354
+ error: (message2) => errorOutput.write(`${message2}
6355
+ `),
6356
+ attachCommandInput(handler) {
6357
+ if (options.interactive === false || !input.isTTY)
6358
+ return null;
6359
+ const rl = createInterface({ input, output: process.stdout, terminal: false });
6360
+ rl.on("line", (line) => {
6361
+ Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6362
+ });
6363
+ return { close: () => rl.close() };
6364
+ }
6365
+ };
6366
+ }
6367
+ function taskId(task) {
6368
+ return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6369
+ }
6370
+ function taskTitle(task) {
6371
+ return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6372
+ }
6373
+ function taskStatus(task) {
6374
+ return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6375
+ }
6376
+ function renderTaskPickerRows(tasks) {
6377
+ return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6378
+ }
6379
+ async function promptForTaskSelection(question) {
6380
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
6381
+ try {
6382
+ return await rl.question(question);
6383
+ } finally {
6384
+ rl.close();
6385
+ }
6386
+ }
6387
+
6388
+ // packages/cli/src/commands/_operator-view.ts
6389
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
5951
6390
  function runStatusFromPayload(payload) {
5952
6391
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
5953
6392
  return String(run.status ?? "unknown").toLowerCase();
@@ -5969,11 +6408,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
5969
6408
  await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
5970
6409
  return { action: "continue", message: "Steering message queued." };
5971
6410
  }
5972
- async function readOperatorSnapshot(context, runId) {
6411
+ async function readOperatorSnapshot(context, runId, options = {}) {
5973
6412
  const run = await getRunDetailsViaServer(context, runId);
5974
6413
  const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
5975
- const entries = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
5976
- return { run, logs: entries, rendered: renderOperatorSnapshot({ run, logs: entries }) };
6414
+ const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
6415
+ entries: [{
6416
+ id: `timeline-unavailable:${runId}`,
6417
+ type: "timeline_warning",
6418
+ detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
6419
+ createdAt: new Date().toISOString()
6420
+ }],
6421
+ nextCursor: options.timelineCursor ?? null
6422
+ }));
6423
+ const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
6424
+ const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6425
+ const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6426
+ return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
5977
6427
  }
5978
6428
  async function attachRunOperatorView(context, input) {
5979
6429
  let steered = false;
@@ -5981,45 +6431,266 @@ async function attachRunOperatorView(context, input) {
5981
6431
  await steerRunViaServer(context, input.runId, input.message.trim());
5982
6432
  steered = true;
5983
6433
  }
6434
+ const surface = createOperatorSurface({ interactive: input.interactive !== false });
5984
6435
  let snapshot = await readOperatorSnapshot(context, input.runId);
5985
6436
  if (context.outputMode === "text") {
5986
- console.log(snapshot.rendered);
6437
+ surface.renderSnapshot(snapshot);
6438
+ surface.renderTimeline(snapshot.timeline);
6439
+ surface.renderLogs(snapshot.logs);
5987
6440
  if (steered)
5988
- console.log("Steering message queued.");
6441
+ surface.info("Steering message queued.");
5989
6442
  }
5990
6443
  let detached = false;
5991
- let rl = null;
6444
+ let commandInput = null;
5992
6445
  if (input.follow && !input.once && context.outputMode === "text") {
5993
6446
  if (input.interactive !== false && process.stdin.isTTY) {
5994
- console.log("Controls: /user <message>, /stop, /detach");
5995
- rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
5996
- rl.on("line", (line) => {
5997
- applyOperatorCommand(context, { runId: input.runId, line }).then((result) => {
5998
- if (result.message)
5999
- console.log(result.message);
6000
- if (result.action === "detach" || result.action === "stopped") {
6001
- detached = true;
6002
- rl?.close();
6003
- }
6004
- }).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6447
+ surface.info("Controls: /user <message>, /stop, /detach");
6448
+ commandInput = surface.attachCommandInput(async (line) => {
6449
+ const result = await applyOperatorCommand(context, { runId: input.runId, line });
6450
+ if (result.message)
6451
+ surface.info(result.message);
6452
+ if (result.action === "detach" || result.action === "stopped") {
6453
+ detached = true;
6454
+ commandInput?.close();
6455
+ }
6005
6456
  });
6006
6457
  }
6007
- let lastRendered = snapshot.rendered;
6008
6458
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6459
+ let timelineCursor = snapshot.timelineCursor;
6009
6460
  while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
6010
6461
  await Bun.sleep(pollMs);
6011
- snapshot = await readOperatorSnapshot(context, input.runId);
6012
- if (snapshot.rendered !== lastRendered) {
6013
- console.log(snapshot.rendered);
6014
- lastRendered = snapshot.rendered;
6015
- }
6462
+ snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6463
+ timelineCursor = snapshot.timelineCursor;
6464
+ surface.renderSnapshot(snapshot);
6465
+ surface.renderTimeline(snapshot.timeline);
6466
+ surface.renderLogs(snapshot.logs);
6016
6467
  }
6017
- rl?.close();
6468
+ commandInput?.close();
6018
6469
  }
6019
6470
  return { ...snapshot, steered, detached };
6020
6471
  }
6021
6472
 
6473
+ // packages/cli/src/commands/_cli-format.ts
6474
+ import pc3 from "picocolors";
6475
+ function stringField(record, key, fallback = "") {
6476
+ const value = record[key];
6477
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
6478
+ }
6479
+ function arrayField(record, key) {
6480
+ const value = record[key];
6481
+ return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
6482
+ }
6483
+ function rawObject(record) {
6484
+ const raw = record.raw;
6485
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
6486
+ }
6487
+ function truncate(value, width) {
6488
+ if (value.length <= width)
6489
+ return value;
6490
+ if (width <= 1)
6491
+ return "\u2026";
6492
+ return `${value.slice(0, width - 1)}\u2026`;
6493
+ }
6494
+ function pad(value, width) {
6495
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
6496
+ }
6497
+ function statusColor(status) {
6498
+ const normalized = status.toLowerCase();
6499
+ if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
6500
+ return pc3.green;
6501
+ if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
6502
+ return pc3.red;
6503
+ if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
6504
+ return pc3.cyan;
6505
+ if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
6506
+ return pc3.yellow;
6507
+ return pc3.dim;
6508
+ }
6509
+ function formatTaskList(tasks, options = {}) {
6510
+ if (tasks.length === 0)
6511
+ return pc3.dim("No matching tasks.");
6512
+ if (options.raw)
6513
+ return tasks.map((task) => JSON.stringify(task)).join(`
6514
+ `);
6515
+ const rows = tasks.map((task) => {
6516
+ const raw = rawObject(task);
6517
+ const id = stringField(task, "id", "<unknown>");
6518
+ const status = stringField(task, "status", "unknown");
6519
+ const title = stringField(task, "title", "Untitled task");
6520
+ const source = stringField(task, "source", stringField(raw, "source", ""));
6521
+ const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
6522
+ return { id, status, title, source, labels };
6523
+ });
6524
+ const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
6525
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6526
+ const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6527
+ const body = rows.map((row) => {
6528
+ const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
6529
+ const source = row.source ? pc3.dim(` ${row.source}`) : "";
6530
+ return [
6531
+ pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
6532
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6533
+ `${row.title}${labels}${source}`
6534
+ ].join(" ");
6535
+ });
6536
+ return [pc3.bold("Rig tasks"), header, ...body].join(`
6537
+ `);
6538
+ }
6539
+ function formatRunList(runs, options = {}) {
6540
+ if (runs.length === 0) {
6541
+ return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
6542
+ }
6543
+ const rows = runs.map((run) => {
6544
+ const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
6545
+ const status = stringField(run, "status", "unknown");
6546
+ const taskId2 = stringField(run, "taskId", "");
6547
+ const title = stringField(run, "title", taskId2 || "(untitled)");
6548
+ const runtime = stringField(run, "runtimeAdapter", "");
6549
+ return { runId, status, title, runtime };
6550
+ });
6551
+ const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
6552
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6553
+ const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6554
+ const body = rows.map((row) => [
6555
+ pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
6556
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6557
+ `${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
6558
+ ].join(" "));
6559
+ return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
6560
+ `);
6561
+ }
6562
+ function formatSubmittedRun(input) {
6563
+ const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
6564
+ if (input.task) {
6565
+ const id = stringField(input.task, "id", "<unknown>");
6566
+ const status = stringField(input.task, "status", "unknown");
6567
+ const title = stringField(input.task, "title", "Untitled task");
6568
+ lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
6569
+ }
6570
+ return lines.join(`
6571
+ `);
6572
+ }
6573
+
6574
+ // packages/cli/src/commands/_pi-session.ts
6575
+ import { spawn as spawn2 } from "child_process";
6576
+ function buildPiRigSessionEnv(input) {
6577
+ return {
6578
+ RIG_PROJECT_ROOT: input.projectRoot,
6579
+ PROJECT_RIG_ROOT: input.projectRoot,
6580
+ RIG_RUN_ID: input.runId,
6581
+ RIG_SERVER_RUN_ID: input.runId,
6582
+ RIG_RUNTIME_ADAPTER: "pi",
6583
+ RIG_SERVER_URL: input.serverUrl,
6584
+ RIG_SERVER_BASE_URL: input.serverUrl,
6585
+ RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6586
+ RIG_PI_OPERATOR_SESSION: "1",
6587
+ ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6588
+ ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6589
+ };
6590
+ }
6591
+ function shellBinary(name) {
6592
+ const explicit = process.env.RIG_PI_BINARY?.trim();
6593
+ if (explicit)
6594
+ return explicit;
6595
+ return Bun.which(name) || name;
6596
+ }
6597
+ function buildPiRigSessionCommand(input) {
6598
+ const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6599
+ const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6600
+ const initialCommand = `/rig attach ${input.runId}`;
6601
+ return [
6602
+ shellBinary("pi"),
6603
+ "--no-extensions",
6604
+ "--extension",
6605
+ extensionSource,
6606
+ initialCommand
6607
+ ];
6608
+ }
6609
+ async function launchPiRigSession(context, input) {
6610
+ if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6611
+ return { launched: false, exitCode: null, command: [] };
6612
+ }
6613
+ if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6614
+ return { launched: false, exitCode: null, command: [] };
6615
+ }
6616
+ const server = await ensureServerForCli(context.projectRoot);
6617
+ const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6618
+ const env = {
6619
+ ...process.env,
6620
+ ...buildPiRigSessionEnv({
6621
+ projectRoot: context.projectRoot,
6622
+ runId: input.runId,
6623
+ taskId: input.taskId,
6624
+ serverUrl: server.baseUrl,
6625
+ authToken: server.authToken
6626
+ })
6627
+ };
6628
+ process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6629
+ `);
6630
+ process.stdout.write(`Pi command: ${formatCommand(command)}
6631
+ `);
6632
+ const launchedAt = Date.now();
6633
+ const child = spawn2(command[0], command.slice(1), {
6634
+ cwd: context.projectRoot,
6635
+ env,
6636
+ stdio: "inherit"
6637
+ });
6638
+ const launchError = await new Promise((resolve19) => {
6639
+ child.once("error", (error) => {
6640
+ resolve19({ error: error.message });
6641
+ });
6642
+ child.once("close", (code) => resolve19({ code }));
6643
+ });
6644
+ if ("error" in launchError) {
6645
+ process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6646
+ `);
6647
+ return { launched: false, exitCode: null, command, error: launchError.error };
6648
+ }
6649
+ const exitCode = launchError.code;
6650
+ const elapsedMs = Date.now() - launchedAt;
6651
+ if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6652
+ const error = `Pi exited during startup with code ${exitCode}.`;
6653
+ process.stderr.write(`${error} Falling back to Rig attach view.
6654
+ `);
6655
+ return { launched: false, exitCode, command, error };
6656
+ }
6657
+ return { launched: true, exitCode, command };
6658
+ }
6659
+
6022
6660
  // packages/cli/src/commands/run.ts
6661
+ function normalizeRemoteRunDetails(payload) {
6662
+ const run = payload.run;
6663
+ if (!run || typeof run !== "object" || Array.isArray(run))
6664
+ return null;
6665
+ return {
6666
+ ...run,
6667
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6668
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6669
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6670
+ };
6671
+ }
6672
+ var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
6673
+ function isRemoteConnectionSelected(projectRoot) {
6674
+ return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
6675
+ }
6676
+ async function listRunsForSelectedConnection(context, options = {}) {
6677
+ if (isRemoteConnectionSelected(context.projectRoot)) {
6678
+ return { runs: await listRunsViaServer(context, options), source: "server" };
6679
+ }
6680
+ return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
6681
+ }
6682
+ function runStringField(run, key, fallback = "") {
6683
+ const value = run[key];
6684
+ return typeof value === "string" && value.trim() ? value : fallback;
6685
+ }
6686
+ function runDisplayTitle(run) {
6687
+ return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
6688
+ }
6689
+ function buildServerRunStatus(runs) {
6690
+ const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6691
+ const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6692
+ return { activeRuns, recentRuns, runs };
6693
+ }
6023
6694
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6024
6695
  if (noEpicPrompt) {
6025
6696
  return false;
@@ -6085,17 +6756,11 @@ async function executeRun(context, args) {
6085
6756
  switch (command) {
6086
6757
  case "list": {
6087
6758
  requireNoExtraArgs(rest, "bun run rig run list");
6088
- const runs = listAuthorityRuns3(context.projectRoot);
6759
+ const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
6089
6760
  if (context.outputMode === "text") {
6090
- if (runs.length === 0) {
6091
- console.log("No runs recorded in .rig/runs.");
6092
- } else {
6093
- for (const run of runs) {
6094
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
6095
- }
6096
- }
6761
+ console.log(formatRunList(runs, { source }));
6097
6762
  }
6098
- return { ok: true, group: "run", command, details: { runs } };
6763
+ return { ok: true, group: "run", command, details: { runs, source } };
6099
6764
  }
6100
6765
  case "delete": {
6101
6766
  let pending = rest;
@@ -6158,7 +6823,7 @@ async function executeRun(context, args) {
6158
6823
  if (!run.value) {
6159
6824
  throw new CliError2("run show requires --run <id>.");
6160
6825
  }
6161
- const record = readAuthorityRun4(context.projectRoot, run.value);
6826
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6162
6827
  if (!record) {
6163
6828
  throw new CliError2(`Run not found: ${run.value}`, 2);
6164
6829
  }
@@ -6177,34 +6842,24 @@ async function executeRun(context, args) {
6177
6842
  if (!run.value) {
6178
6843
  throw new CliError2("run timeline requires --run <id>.");
6179
6844
  }
6180
- const timelinePath = resolve19(resolveAuthorityRunDir5(context.projectRoot, run.value), "timeline.jsonl");
6181
- const printEvents = () => {
6182
- const events2 = readJsonlFile4(timelinePath);
6183
- if (context.outputMode === "text") {
6184
- for (const event of events2) {
6185
- console.log(JSON.stringify(event));
6186
- }
6187
- }
6188
- return events2;
6189
- };
6190
- const events = printEvents();
6845
+ const renderer = createPiRunStreamRenderer();
6846
+ let cursor = null;
6847
+ const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
6848
+ const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6849
+ cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
6850
+ if (context.outputMode === "text") {
6851
+ renderer.renderTimeline(events);
6852
+ }
6191
6853
  if (follow.value && context.outputMode === "text") {
6192
- let lastLength = existsSync11(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
6193
6854
  while (true) {
6194
6855
  await Bun.sleep(1000);
6195
- if (!existsSync11(timelinePath))
6196
- continue;
6197
- const next = readFileSync9(timelinePath, "utf8");
6198
- if (next.length <= lastLength)
6199
- continue;
6200
- const delta = next.slice(lastLength);
6201
- lastLength = next.length;
6202
- for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
6203
- console.log(line);
6204
- }
6856
+ const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
6857
+ const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6858
+ cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
6859
+ renderer.renderTimeline(nextEvents);
6205
6860
  }
6206
6861
  }
6207
- return { ok: true, group: "run", command, details: { runId: run.value, events } };
6862
+ return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
6208
6863
  }
6209
6864
  case "attach": {
6210
6865
  let pending = rest;
@@ -6225,14 +6880,26 @@ async function executeRun(context, args) {
6225
6880
  if (!runId) {
6226
6881
  throw new CliError2("run attach requires a run id.", 2);
6227
6882
  }
6883
+ let steered = false;
6884
+ const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
6885
+ if (shouldTryPiAttach && messageOption.value?.trim()) {
6886
+ await steerRunViaServer(context, runId, messageOption.value.trim());
6887
+ steered = true;
6888
+ }
6889
+ if (shouldTryPiAttach) {
6890
+ const piSession = await launchPiRigSession(context, { runId });
6891
+ if (piSession.launched) {
6892
+ return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
6893
+ }
6894
+ }
6228
6895
  const attached = await attachRunOperatorView(context, {
6229
6896
  runId,
6230
- message: messageOption.value ?? null,
6897
+ message: shouldTryPiAttach ? null : messageOption.value ?? null,
6231
6898
  once: once.value,
6232
6899
  follow: follow.value,
6233
6900
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
6234
6901
  });
6235
- return { ok: true, group: "run", command, details: attached };
6902
+ return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
6236
6903
  }
6237
6904
  case "status": {
6238
6905
  requireNoExtraArgs(rest, "bun run rig run status");
@@ -6242,17 +6909,19 @@ async function executeRun(context, args) {
6242
6909
  }
6243
6910
  return { ok: true, group: "run", command };
6244
6911
  }
6245
- const summary = runStatus(context.projectRoot, runtimeContext);
6912
+ const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
6913
+ const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
6914
+ const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
6246
6915
  if (context.outputMode === "text") {
6247
- console.log(`Active runs: ${summary.activeRuns.length}`);
6248
- for (const run of summary.activeRuns) {
6249
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
6916
+ console.log(`Active runs: ${activeRuns.length}`);
6917
+ for (const run of activeRuns) {
6918
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6250
6919
  }
6251
- if (summary.recentRuns.length > 0) {
6920
+ if (recentRuns.length > 0) {
6252
6921
  console.log("");
6253
6922
  console.log("Recent runs:");
6254
- for (const run of summary.recentRuns) {
6255
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
6923
+ for (const run of recentRuns) {
6924
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6256
6925
  }
6257
6926
  }
6258
6927
  }
@@ -6339,6 +7008,20 @@ async function executeRun(context, args) {
6339
7008
  }
6340
7009
  return { ok: true, group: "run", command, details: resumed };
6341
7010
  }
7011
+ case "restart": {
7012
+ requireNoExtraArgs(rest, "bun run rig run restart");
7013
+ if (context.dryRun) {
7014
+ if (context.outputMode === "text") {
7015
+ console.log("[dry-run] rig run restart");
7016
+ }
7017
+ return { ok: true, group: "run", command };
7018
+ }
7019
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
7020
+ if (context.outputMode === "text") {
7021
+ console.log(`Restarted run: ${restarted.runId}`);
7022
+ }
7023
+ return { ok: true, group: "run", command, details: restarted };
7024
+ }
6342
7025
  case "stop": {
6343
7026
  const runOption = takeOption(rest, "--run");
6344
7027
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -6396,7 +7079,7 @@ async function executeServer(context, args, options) {
6396
7079
  const authTokenResult = takeOption(pending, "--auth-token");
6397
7080
  pending = authTokenResult.rest;
6398
7081
  requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
6399
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "start"];
7082
+ const commandParts = ["rig-server", "start"];
6400
7083
  if (hostResult.value) {
6401
7084
  commandParts.push("--host", hostResult.value);
6402
7085
  }
@@ -6419,7 +7102,7 @@ async function executeServer(context, args, options) {
6419
7102
  const eventResult = takeOption(pending, "--event");
6420
7103
  pending = eventResult.rest;
6421
7104
  requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
6422
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "notify-test"];
7105
+ const commandParts = ["rig-server", "notify-test"];
6423
7106
  if (eventResult.value) {
6424
7107
  commandParts.push("--event", eventResult.value);
6425
7108
  }
@@ -6480,10 +7163,10 @@ async function executeServer(context, args, options) {
6480
7163
  }
6481
7164
 
6482
7165
  // packages/cli/src/commands/task.ts
6483
- import { readFileSync as readFileSync10 } from "fs";
6484
- import { spawnSync as spawnSync4 } from "child_process";
6485
- import { createInterface as createInterface4 } from "readline/promises";
6486
- import { resolve as resolve20 } from "path";
7166
+ import { readFileSync as readFileSync9 } from "fs";
7167
+ import { spawnSync as spawnSync3 } from "child_process";
7168
+ import { resolve as resolve19 } from "path";
7169
+ import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
6487
7170
  import {
6488
7171
  taskArtifactDir,
6489
7172
  taskArtifacts,
@@ -6501,19 +7184,10 @@ import {
6501
7184
  } from "@rig/runtime/control-plane/native/task-ops";
6502
7185
 
6503
7186
  // packages/cli/src/commands/_task-picker.ts
6504
- import { createInterface as createInterface3 } from "readline/promises";
6505
- function taskId(task) {
7187
+ import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
7188
+ function taskId2(task) {
6506
7189
  return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6507
7190
  }
6508
- function taskTitle(task) {
6509
- return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6510
- }
6511
- function taskStatus(task) {
6512
- return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6513
- }
6514
- function renderTaskPickerRows(tasks) {
6515
- return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6516
- }
6517
7191
  async function selectTaskWithTextPicker(tasks, io = {}) {
6518
7192
  if (tasks.length === 0)
6519
7193
  return null;
@@ -6523,25 +7197,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
6523
7197
  if (!isTty) {
6524
7198
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
6525
7199
  }
6526
- const prompt = io.prompt ?? (async (question) => {
6527
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6528
- try {
6529
- return await rl.question(question);
6530
- } finally {
6531
- rl.close();
7200
+ if (io.prompt || io.renderer) {
7201
+ const prompt = io.prompt ?? promptForTaskSelection;
7202
+ const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
7203
+ `) };
7204
+ renderer.writeLine("Select Rig task:");
7205
+ for (const row of renderTaskPickerRows(tasks))
7206
+ renderer.writeLine(` ${row}`);
7207
+ const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
7208
+ if (!answer2)
7209
+ return null;
7210
+ if (/^\d+$/.test(answer2)) {
7211
+ const index2 = Number.parseInt(answer2, 10) - 1;
7212
+ return tasks[index2] ?? null;
6532
7213
  }
7214
+ return tasks.find((task) => taskId2(task) === answer2) ?? null;
7215
+ }
7216
+ const options = tasks.map((task, index2) => ({
7217
+ value: `${index2}`,
7218
+ label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
7219
+ hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
7220
+ }));
7221
+ const answer = await select2({
7222
+ message: "Select Rig task",
7223
+ options
6533
7224
  });
6534
- console.log("Select Rig task:");
6535
- for (const row of renderTaskPickerRows(tasks))
6536
- console.log(` ${row}`);
6537
- const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
6538
- if (!answer)
7225
+ if (isCancel2(answer)) {
7226
+ cancel2("No task selected.");
6539
7227
  return null;
6540
- if (/^\d+$/.test(answer)) {
6541
- const index = Number.parseInt(answer, 10) - 1;
6542
- return tasks[index] ?? null;
6543
7228
  }
6544
- return tasks.find((task) => taskId(task) === answer) ?? null;
7229
+ const index = Number.parseInt(String(answer), 10);
7230
+ return Number.isFinite(index) ? tasks[index] ?? null : null;
6545
7231
  }
6546
7232
 
6547
7233
  // packages/cli/src/commands/task.ts
@@ -6619,7 +7305,7 @@ function normalizePrMode(value) {
6619
7305
  throw new CliError2("--pr must be auto, ask, or off.", 2);
6620
7306
  }
6621
7307
  function detectLocalDirtyState(projectRoot) {
6622
- const result = spawnSync4("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
7308
+ const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
6623
7309
  if (result.status !== 0)
6624
7310
  return { dirty: false, modified: 0, untracked: 0, lines: [] };
6625
7311
  const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -6653,13 +7339,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
6653
7339
  if (explicit)
6654
7340
  return { mode: explicit, state };
6655
7341
  if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
6656
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
6657
- try {
6658
- const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
6659
- return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
6660
- } finally {
6661
- rl.close();
7342
+ const answer = await confirm2({
7343
+ message: "Include current uncommitted changes in run baseline?",
7344
+ initialValue: false
7345
+ });
7346
+ if (isCancel3(answer)) {
7347
+ cancel3("Run cancelled.");
7348
+ throw new CliError2("Run cancelled by user.", 1);
6662
7349
  }
7350
+ return { mode: answer ? "dirty-snapshot" : "head", state };
6663
7351
  }
6664
7352
  return { mode: "head", state };
6665
7353
  }
@@ -6694,10 +7382,7 @@ function summarizeTask(task, options = {}) {
6694
7382
  };
6695
7383
  }
6696
7384
  function printTaskSummary(task) {
6697
- const id = readTaskId(task) ?? "<unknown>";
6698
- const title = readTaskString(task, "title") ?? "Untitled task";
6699
- const status = readTaskString(task, "status") ?? "unknown";
6700
- console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
7385
+ console.log(formatTaskList([task]));
6701
7386
  }
6702
7387
  async function validatorRegistryForTaskCommands(projectRoot) {
6703
7388
  return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
@@ -6715,16 +7400,8 @@ async function executeTask(context, args, options) {
6715
7400
  requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
6716
7401
  const tasks = await listWorkspaceTasksViaServer(context, filters);
6717
7402
  if (context.outputMode === "text") {
6718
- if (tasks.length === 0) {
6719
- console.log("No matching tasks.");
6720
- } else {
6721
- for (const task of tasks) {
6722
- if (rawResult.value)
6723
- console.log(JSON.stringify(summarizeTask(task, { raw: true })));
6724
- else
6725
- printTaskSummary(task);
6726
- }
6727
- }
7403
+ const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
7404
+ console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
6728
7405
  }
6729
7406
  return {
6730
7407
  ok: true,
@@ -6738,12 +7415,12 @@ async function executeTask(context, args, options) {
6738
7415
  const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
6739
7416
  const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
6740
7417
  requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
6741
- const taskId2 = normalizeTaskRunTaskId(taskOption.value ?? positional);
6742
- if (!taskId2)
7418
+ const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
7419
+ if (!taskId3)
6743
7420
  throw new CliError2("task show requires a task id.", 2);
6744
- const task = await getWorkspaceTaskViaServer(context, taskId2);
7421
+ const task = await getWorkspaceTaskViaServer(context, taskId3);
6745
7422
  if (!task)
6746
- throw new CliError2(`Task not found: ${taskId2}`, 3);
7423
+ throw new CliError2(`Task not found: ${taskId3}`, 3);
6747
7424
  const summary = summarizeTask(task, { raw: true });
6748
7425
  if (context.outputMode === "text")
6749
7426
  console.log(JSON.stringify(summary, null, 2));
@@ -6814,7 +7491,7 @@ async function executeTask(context, args, options) {
6814
7491
  const fileFlag = takeOption(rest.slice(1), "--file");
6815
7492
  let content;
6816
7493
  if (fileFlag.value) {
6817
- content = readFileSync10(resolve20(context.projectRoot, fileFlag.value), "utf-8");
7494
+ content = readFileSync9(resolve19(context.projectRoot, fileFlag.value), "utf-8");
6818
7495
  } else {
6819
7496
  content = await readStdin();
6820
7497
  }
@@ -6943,16 +7620,23 @@ async function executeTask(context, args, options) {
6943
7620
  });
6944
7621
  let attachDetails = null;
6945
7622
  if (!detachResult.value && context.outputMode === "text") {
6946
- console.log(`Run submitted: ${submitted.runId}`);
6947
- if (selectedTask) {
6948
- printTaskSummary(selectedTask);
7623
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7624
+ if (runtimeAdapter === "pi") {
7625
+ const piSession = await launchPiRigSession(context, {
7626
+ runId: submitted.runId,
7627
+ taskId: selectedTaskId,
7628
+ title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7629
+ runtimeAdapter
7630
+ });
7631
+ attachDetails = { mode: "pi", ...piSession };
7632
+ if (!piSession.launched) {
7633
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7634
+ }
7635
+ } else {
7636
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
6949
7637
  }
6950
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
6951
7638
  } else if (context.outputMode === "text") {
6952
- console.log(`Run submitted: ${submitted.runId}`);
6953
- if (selectedTask) {
6954
- printTaskSummary(selectedTask);
6955
- }
7639
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
6956
7640
  }
6957
7641
  return {
6958
7642
  ok: true,
@@ -7035,9 +7719,9 @@ async function executeTask(context, args, options) {
7035
7719
  }
7036
7720
 
7037
7721
  // packages/cli/src/commands/task-run-driver.ts
7038
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync11, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7039
- import { resolve as resolve21 } from "path";
7040
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
7722
+ import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7723
+ import { resolve as resolve20 } from "path";
7724
+ import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
7041
7725
  import { createInterface as createLineInterface } from "readline";
7042
7726
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7043
7727
  import {
@@ -7062,15 +7746,31 @@ import {
7062
7746
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
7063
7747
  import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
7064
7748
  import {
7065
- buildTaskRunLifecycleComment,
7066
- updateConfiguredTaskSourceTask
7749
+ buildTaskRunLifecycleComment
7067
7750
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
7068
7751
  import {
7069
7752
  closeIssueAfterMergedPr,
7070
7753
  commitRunChanges,
7071
7754
  runPrAutomation
7072
7755
  } from "@rig/runtime/control-plane/native/pr-automation";
7756
+ function looksLikeGitHubToken(value) {
7757
+ const token = value?.trim();
7758
+ if (!token)
7759
+ return false;
7760
+ return /^(gh[opusr]_|github_pat_)/.test(token);
7761
+ }
7762
+ function githubBridgeEnv(token) {
7763
+ const clean = token?.trim();
7764
+ if (!clean)
7765
+ return {};
7766
+ return {
7767
+ RIG_GITHUB_TOKEN: clean,
7768
+ GITHUB_TOKEN: clean,
7769
+ GH_TOKEN: clean
7770
+ };
7771
+ }
7073
7772
  function buildPiRigBridgeEnv(input) {
7773
+ const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
7074
7774
  return {
7075
7775
  RIG_PROJECT_ROOT: input.projectRoot,
7076
7776
  PROJECT_RIG_ROOT: input.projectRoot,
@@ -7080,11 +7780,11 @@ function buildPiRigBridgeEnv(input) {
7080
7780
  RIG_RUNTIME_ADAPTER: "pi",
7081
7781
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7082
7782
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7083
- ...input.githubToken ? { RIG_GITHUB_TOKEN: input.githubToken } : {}
7783
+ ...githubBridgeEnv(githubToken)
7084
7784
  };
7085
7785
  }
7086
7786
  function runGitSync(cwd, args, input) {
7087
- const result = spawnSync5("git", [...args], {
7787
+ const result = spawnSync4("git", [...args], {
7088
7788
  cwd,
7089
7789
  input,
7090
7790
  encoding: "utf8",
@@ -7102,12 +7802,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
7102
7802
  return 0;
7103
7803
  let copied = 0;
7104
7804
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
7105
- const sourcePath = resolve21(sourceRoot, relativePath);
7106
- const targetPath = resolve21(targetRoot, relativePath);
7805
+ const sourcePath = resolve20(sourceRoot, relativePath);
7806
+ const targetPath = resolve20(targetRoot, relativePath);
7107
7807
  try {
7108
7808
  if (!statSync2(sourcePath).isFile())
7109
7809
  continue;
7110
- mkdirSync7(resolve21(targetPath, ".."), { recursive: true });
7810
+ mkdirSync7(resolve20(targetPath, ".."), { recursive: true });
7111
7811
  copyFileSync3(sourcePath, targetPath);
7112
7812
  copied += 1;
7113
7813
  } catch {}
@@ -7146,7 +7846,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
7146
7846
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
7147
7847
  return {
7148
7848
  RIG_BASELINE_MODE: "dirty-snapshot",
7149
- RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7849
+ RIG_DIRTY_BASELINE_READY_FILE: resolve20(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7150
7850
  };
7151
7851
  }
7152
7852
  function positiveInt(value, fallback) {
@@ -7154,7 +7854,7 @@ function positiveInt(value, fallback) {
7154
7854
  }
7155
7855
  function resolveTaskRunAutomationLimits(config, env = process.env) {
7156
7856
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
7157
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
7857
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
7158
7858
  return {
7159
7859
  maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
7160
7860
  maxPrFixIterations: configuredPrFixIterations
@@ -7249,7 +7949,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
7249
7949
  }
7250
7950
  function createCommandRunner(binary) {
7251
7951
  return async (args, options) => {
7252
- const child = spawn2(binary, [...args], {
7952
+ const child = spawn3(binary, [...args], {
7253
7953
  cwd: options?.cwd,
7254
7954
  stdio: ["ignore", "pipe", "pipe"]
7255
7955
  });
@@ -7257,9 +7957,9 @@ function createCommandRunner(binary) {
7257
7957
  const stderrChunks = [];
7258
7958
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7259
7959
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7260
- return await new Promise((resolve22) => {
7261
- child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
7262
- child.once("close", (code) => resolve22({
7960
+ return await new Promise((resolve21) => {
7961
+ child.once("error", (error) => resolve21({ exitCode: 1, stderr: error.message }));
7962
+ child.once("close", (code) => resolve21({
7263
7963
  exitCode: code ?? 1,
7264
7964
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
7265
7965
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -7279,12 +7979,13 @@ async function resolvePostValidationBranch(input) {
7279
7979
  return input.configuredBranch;
7280
7980
  }
7281
7981
  async function runTaskRunPostValidationLifecycle(input) {
7282
- const taskId2 = input.taskId?.trim();
7283
- if (!taskId2) {
7982
+ const taskId3 = input.taskId?.trim();
7983
+ if (!taskId3) {
7284
7984
  return { status: "skipped" };
7285
7985
  }
7286
- const config = input.config ?? {};
7287
- const prMode = config.pr?.mode ?? "auto";
7986
+ const configInput = input.config ?? null;
7987
+ const config = configInput ?? {};
7988
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
7288
7989
  if (prMode === "off" || prMode === "ask") {
7289
7990
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
7290
7991
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -7300,7 +8001,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7300
8001
  gitCommand
7301
8002
  });
7302
8003
  const prAutomation = input.prAutomation ?? runPrAutomation;
7303
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
8004
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
7304
8005
  const stage = input.appendStage ?? (() => {
7305
8006
  return;
7306
8007
  });
@@ -7318,7 +8019,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7318
8019
  stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
7319
8020
  const commit = await commitRunChanges({
7320
8021
  cwd: workspace,
7321
- message: `rig: complete task ${taskId2}`,
8022
+ message: `rig: complete task ${taskId3}`,
7322
8023
  command: gitCommand
7323
8024
  });
7324
8025
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
@@ -7326,13 +8027,15 @@ async function runTaskRunPostValidationLifecycle(input) {
7326
8027
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
7327
8028
  const pr = await prAutomation({
7328
8029
  projectRoot: workspace,
7329
- taskId: taskId2,
8030
+ taskId: taskId3,
7330
8031
  runId: input.runId,
7331
8032
  branch,
7332
8033
  config,
7333
8034
  sourceTask: input.sourceTask,
7334
8035
  uploadedSnapshot: input.uploadedSnapshot,
8036
+ artifactRoot: resolve20(input.projectRoot, "artifacts", taskId3),
7335
8037
  command: ghCommand,
8038
+ gitCommand,
7336
8039
  steerPi,
7337
8040
  lifecycle: {
7338
8041
  onPrOpened: async ({ prUrl }) => {
@@ -7340,7 +8043,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7340
8043
  try {
7341
8044
  if (shouldWriteDriverIssueUpdate(config, "under_review")) {
7342
8045
  await updateTaskSource(input.projectRoot, {
7343
- taskId: taskId2,
8046
+ taskId: taskId3,
7344
8047
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7345
8048
  update: {
7346
8049
  status: "under_review",
@@ -7370,7 +8073,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7370
8073
  `), "reviewing", "error");
7371
8074
  if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
7372
8075
  await updateTaskSource(input.projectRoot, {
7373
- taskId: taskId2,
8076
+ taskId: taskId3,
7374
8077
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7375
8078
  update: {
7376
8079
  status: "ci_fixing",
@@ -7381,8 +8084,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7381
8084
  runtimeWorkspace: input.runtimeWorkspace ?? null
7382
8085
  })
7383
8086
  }
7384
- }).catch(() => {
7385
- return;
7386
8087
  });
7387
8088
  }
7388
8089
  },
@@ -7390,7 +8091,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7390
8091
  stage("Merge", prUrl, "running", "tool");
7391
8092
  if (shouldWriteDriverIssueUpdate(config, "merging")) {
7392
8093
  await updateTaskSource(input.projectRoot, {
7393
- taskId: taskId2,
8094
+ taskId: taskId3,
7394
8095
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7395
8096
  update: {
7396
8097
  status: "merging",
@@ -7401,8 +8102,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7401
8102
  runtimeWorkspace: input.runtimeWorkspace ?? null
7402
8103
  })
7403
8104
  }
7404
- }).catch(() => {
7405
- return;
7406
8105
  });
7407
8106
  }
7408
8107
  },
@@ -7419,7 +8118,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7419
8118
  stage("Needs attention", detail, "needs_attention", "error");
7420
8119
  if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
7421
8120
  await updateTaskSource(input.projectRoot, {
7422
- taskId: taskId2,
8121
+ taskId: taskId3,
7423
8122
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7424
8123
  update: {
7425
8124
  status: "needs_attention",
@@ -7431,8 +8130,17 @@ async function runTaskRunPostValidationLifecycle(input) {
7431
8130
  errorText: detail
7432
8131
  })
7433
8132
  }
7434
- }).catch(() => {
7435
- return;
8133
+ }).catch((error) => {
8134
+ try {
8135
+ appendRunLog(input.projectRoot, input.runId, {
8136
+ id: `log:${input.runId}:task-source-needs-attention-update`,
8137
+ title: "Task source needs-attention update failed",
8138
+ detail: error instanceof Error ? error.message : String(error),
8139
+ tone: "error",
8140
+ status: "needs_attention",
8141
+ createdAt: new Date().toISOString()
8142
+ });
8143
+ } catch {}
7436
8144
  });
7437
8145
  }
7438
8146
  return { status: "needs_attention", pr };
@@ -7440,7 +8148,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7440
8148
  if (shouldWriteDriverIssueUpdate(config, "closed")) {
7441
8149
  await closeIssueAfterMergedPr({
7442
8150
  projectRoot: input.projectRoot,
7443
- taskId: taskId2,
8151
+ taskId: taskId3,
7444
8152
  runId: input.runId,
7445
8153
  prUrl: pr.prUrl,
7446
8154
  sourceTask: input.sourceTask,
@@ -7450,12 +8158,12 @@ async function runTaskRunPostValidationLifecycle(input) {
7450
8158
  stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
7451
8159
  return { status: "completed", pr };
7452
8160
  }
7453
- function summarizeValidationFailure(projectRoot, taskId2) {
7454
- if (!taskId2) {
8161
+ function summarizeValidationFailure(projectRoot, taskId3) {
8162
+ if (!taskId3) {
7455
8163
  return null;
7456
8164
  }
7457
- for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId2)) {
7458
- const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
8165
+ for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
8166
+ const summary = readJsonFile3(resolve20(artifactDir, "validation-summary.json"), null);
7459
8167
  if (!summary || summary.status !== "fail") {
7460
8168
  continue;
7461
8169
  }
@@ -7536,9 +8244,9 @@ function readTaskRunAcceptedArtifactState(input) {
7536
8244
  if (!input.taskId || !input.workspaceDir) {
7537
8245
  return { accepted: false, reason: null };
7538
8246
  }
7539
- const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
7540
- const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
7541
- const taskResultPath = resolve21(artifactDir, "task-result.json");
8247
+ const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
8248
+ const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
8249
+ const taskResultPath = resolve20(artifactDir, "task-result.json");
7542
8250
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7543
8251
  if (reviewStatus !== "APPROVED") {
7544
8252
  return { accepted: false, reason: null };
@@ -7575,12 +8283,12 @@ function resolveTaskRunRetryContext(input) {
7575
8283
  if (!input.taskId || !input.workspaceDir) {
7576
8284
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
7577
8285
  }
7578
- const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
7579
- const reviewStatePath = resolve21(artifactDir, "review-state.json");
7580
- const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
7581
- const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
7582
- const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
7583
- const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
8286
+ const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
8287
+ const reviewStatePath = resolve20(artifactDir, "review-state.json");
8288
+ const reviewFeedbackPath = resolve20(artifactDir, "review-feedback.md");
8289
+ const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
8290
+ const failedApproachesPath = resolve20(input.workspaceDir, ".rig", "state", "failed_approaches.md");
8291
+ const validationSummaryPath = resolve20(artifactDir, "validation-summary.json");
7584
8292
  const reviewState = readJsonFile3(reviewStatePath, null);
7585
8293
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7586
8294
  const reviewRejected = isTaskRunReviewRejected(reviewState);
@@ -7634,12 +8342,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
7634
8342
  }
7635
8343
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
7636
8344
  }
8345
+ function appendAssistantTimelineFromRecord(input) {
8346
+ let nextAssistantText = input.assistantText;
8347
+ if (input.record.type === "message_update") {
8348
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
8349
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
8350
+ nextAssistantText += assistantMessageEvent.delta;
8351
+ }
8352
+ } else if (input.record.type === "stream_event") {
8353
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
8354
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
8355
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
8356
+ nextAssistantText += delta.text;
8357
+ }
8358
+ } else if (input.record.type === "assistant") {
8359
+ const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
8360
+ const content = Array.isArray(message2.content) ? message2.content : [];
8361
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
8362
+ if (fullText.length > nextAssistantText.length)
8363
+ nextAssistantText = fullText;
8364
+ }
8365
+ if (nextAssistantText !== input.assistantText) {
8366
+ appendRunTimeline(input.projectRoot, input.runId, {
8367
+ id: input.messageId,
8368
+ type: "assistant_message",
8369
+ text: nextAssistantText,
8370
+ state: "streaming",
8371
+ createdAt: new Date().toISOString()
8372
+ });
8373
+ }
8374
+ return nextAssistantText;
8375
+ }
8376
+ function appendPiToolTimelineFromRecord(input) {
8377
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8378
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
8379
+ return false;
8380
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
8381
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
8382
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
8383
+ appendRunTimeline(input.projectRoot, input.runId, {
8384
+ id: `tool:${toolCallId}:${type}`,
8385
+ type,
8386
+ toolName,
8387
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
8388
+ createdAt: new Date().toISOString()
8389
+ });
8390
+ return true;
8391
+ }
8392
+ function isNonRenderablePiProtocolRecord(record) {
8393
+ const type = typeof record.type === "string" ? record.type : "";
8394
+ return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
8395
+ }
8396
+ function appendToolTimelineFromLog(input) {
8397
+ const title = typeof input.log.title === "string" ? input.log.title : "";
8398
+ if (title !== "Tool activity")
8399
+ return;
8400
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
8401
+ const toolName = typeof payload.toolName === "string" && payload.toolName.trim() ? payload.toolName.trim() : typeof payload.tool_name === "string" && payload.tool_name.trim() ? payload.tool_name.trim() : null;
8402
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
8403
+ appendRunTimeline(input.projectRoot, input.runId, {
8404
+ id: `tool:${logId}`,
8405
+ type: "tool_execution_update",
8406
+ toolName,
8407
+ status: typeof input.log.status === "string" ? input.log.status : "running",
8408
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
8409
+ payload,
8410
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
8411
+ });
8412
+ }
7637
8413
  function readTaskRunReviewStatus(reviewStatusPath) {
7638
- if (!existsSync12(reviewStatusPath)) {
8414
+ if (!existsSync11(reviewStatusPath)) {
7639
8415
  return null;
7640
8416
  }
7641
8417
  try {
7642
- const status = readFileSync11(reviewStatusPath, "utf8").trim().toUpperCase();
8418
+ const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
7643
8419
  return status === "APPROVED" || status === "REJECTED" ? status : null;
7644
8420
  } catch {
7645
8421
  return null;
@@ -7657,14 +8433,45 @@ function isTaskRunReviewRejected(reviewState) {
7657
8433
  function runSourceTaskIdentity(sourceTask) {
7658
8434
  return sourceTask;
7659
8435
  }
7660
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
7661
- if (!taskId2)
8436
+ function sourceTaskIssueNodeId(sourceTask) {
8437
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
8438
+ return null;
8439
+ const record = sourceTask;
8440
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
8441
+ if (direct?.trim())
8442
+ return direct.trim();
8443
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
8444
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
8445
+ }
8446
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
8447
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
8448
+ id: input.taskId,
8449
+ ...input.update.status ? { status: input.update.status } : {},
8450
+ ...input.update.comment ? { comment: input.update.comment } : {},
8451
+ ...input.update.title ? { title: input.update.title } : {},
8452
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
8453
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
8454
+ });
8455
+ if (serverResult.ok === false) {
8456
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
8457
+ }
8458
+ return {
8459
+ updated: serverResult.ok !== false,
8460
+ taskId: input.taskId,
8461
+ status: input.update.status,
8462
+ source: "server",
8463
+ sourceKind: "server",
8464
+ projectSync: serverResult.projectSync
8465
+ };
8466
+ };
8467
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
8468
+ if (!taskId3)
7662
8469
  return;
7663
8470
  const config = await loadTaskRunAutomationConfig(projectRoot);
7664
8471
  if (!shouldWriteDriverIssueUpdate(config, status))
7665
8472
  return;
7666
8473
  const result = await updateTaskSource(projectRoot, {
7667
- taskId: taskId2,
8474
+ taskId: taskId3,
7668
8475
  sourceTask: runSourceTaskIdentity(sourceTask),
7669
8476
  update: {
7670
8477
  status,
@@ -7683,14 +8490,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
7683
8490
  throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
7684
8491
  }
7685
8492
  }
7686
- function readRunSourceTaskContract(projectRoot, runId, taskId2) {
8493
+ function readRunSourceTaskContract(projectRoot, runId, taskId3) {
7687
8494
  const run = readAuthorityRun5(projectRoot, runId);
7688
8495
  const raw = run?.sourceTask;
7689
8496
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7690
8497
  return null;
7691
8498
  }
7692
8499
  const record = raw;
7693
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId2?.trim();
8500
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
7694
8501
  if (!id) {
7695
8502
  return null;
7696
8503
  }
@@ -7722,6 +8529,9 @@ function stringArrayField(record, key) {
7722
8529
  }
7723
8530
  async function executeRigOwnedTaskRun(context, input) {
7724
8531
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
8532
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
8533
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
8534
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7725
8535
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7726
8536
  let prompt = buildRunPrompt({
7727
8537
  projectRoot: context.projectRoot,
@@ -7777,14 +8587,14 @@ async function executeRigOwnedTaskRun(context, input) {
7777
8587
  taskId: runtimeTaskId,
7778
8588
  createdAt: startedAt,
7779
8589
  runtimeAdapter: input.runtimeAdapter,
7780
- status: "created"
8590
+ status: resumeMode ? "preparing" : "created"
7781
8591
  });
7782
8592
  patchAuthorityRun(context.projectRoot, input.runId, {
7783
8593
  status: "preparing",
7784
8594
  startedAt,
7785
8595
  completedAt: null,
7786
8596
  errorText: null,
7787
- artifactRoot: null,
8597
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
7788
8598
  runtimeAdapter: input.runtimeAdapter,
7789
8599
  runtimeMode: input.runtimeMode,
7790
8600
  interactionMode: input.interactionMode,
@@ -7800,9 +8610,9 @@ async function executeRigOwnedTaskRun(context, input) {
7800
8610
  detail: input.taskId ?? input.title ?? runtimeTaskId
7801
8611
  });
7802
8612
  appendRunLog(context.projectRoot, input.runId, {
7803
- id: `log:${input.runId}:start`,
7804
- title: "Rig task run started",
7805
- detail: input.taskId ?? input.title ?? runtimeTaskId,
8613
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
8614
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
8615
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
7806
8616
  tone: "info",
7807
8617
  status: "preparing",
7808
8618
  createdAt: startedAt
@@ -7823,15 +8633,15 @@ async function executeRigOwnedTaskRun(context, input) {
7823
8633
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
7824
8634
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
7825
8635
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
7826
- const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
8636
+ const planningArtifactPath = resolve20("artifacts", runtimeTaskId, "implementation-plan.md");
7827
8637
  const persistedPlanning = {
7828
8638
  ...planningClassification,
7829
8639
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
7830
8640
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
7831
8641
  classifiedAt: new Date().toISOString()
7832
8642
  };
7833
- mkdirSync7(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
7834
- writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8643
+ mkdirSync7(resolve20(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8644
+ writeFileSync6(resolve20(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
7835
8645
  `, "utf8");
7836
8646
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
7837
8647
  prompt = `${prompt}
@@ -7880,11 +8690,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7880
8690
  let reviewAction;
7881
8691
  let verificationStarted = false;
7882
8692
  let reviewStarted = false;
7883
- let latestRuntimeWorkspace = null;
7884
- let latestSessionDir = null;
7885
- let latestLogsDir = null;
8693
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
8694
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve20(existingRunRecord.sessionPath, "..") : null;
8695
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
7886
8696
  let latestProviderCommand = null;
7887
- let latestRuntimeBranch = null;
8697
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
7888
8698
  let snapshotSidecarPromise = null;
7889
8699
  let dirtyBaselineApplied = false;
7890
8700
  const childEnv = {
@@ -7902,7 +8712,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7902
8712
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
7903
8713
  RIG_SERVER_RUN_ID: input.runId
7904
8714
  },
7905
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
8715
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
8716
+ ...resumeMode ? {
8717
+ RIG_RUN_RESUME: "1",
8718
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
8719
+ } : {}
7906
8720
  };
7907
8721
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
7908
8722
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -7960,10 +8774,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7960
8774
  patchAuthorityRun(context.projectRoot, input.runId, {
7961
8775
  status: "running",
7962
8776
  worktreePath: latestRuntimeWorkspace,
7963
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8777
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve20(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
7964
8778
  logRoot: latestLogsDir,
7965
- sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
7966
- sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
8779
+ sessionPath: latestSessionDir ? resolve20(latestSessionDir, "session.json") : null,
8780
+ sessionLogPath: latestLogsDir ? resolve20(latestLogsDir, "agent-stdout.log") : null,
7967
8781
  branch: runtimeId
7968
8782
  });
7969
8783
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -7971,7 +8785,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7971
8785
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
7972
8786
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
7973
8787
  if (readyFile) {
7974
- mkdirSync7(resolve21(readyFile, ".."), { recursive: true });
8788
+ mkdirSync7(resolve20(readyFile, ".."), { recursive: true });
7975
8789
  writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
7976
8790
  `, "utf8");
7977
8791
  }
@@ -8104,6 +8918,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8104
8918
  try {
8105
8919
  const record = JSON.parse(trimmed);
8106
8920
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
8921
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
8922
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8923
+ return;
8924
+ }
8107
8925
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
8108
8926
  runId: input.runId,
8109
8927
  record,
@@ -8120,7 +8938,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8120
8938
  if (providerLogs.length > 0) {
8121
8939
  for (const providerLog of providerLogs) {
8122
8940
  appendRunLog(context.projectRoot, input.runId, providerLog);
8941
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
8123
8942
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
8943
+ if (providerLog.title === "Tool activity")
8944
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8124
8945
  }
8125
8946
  }
8126
8947
  if (input.runtimeAdapter === "codex") {
@@ -8172,6 +8993,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8172
8993
  return;
8173
8994
  }
8174
8995
  }
8996
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
8997
+ return;
8998
+ }
8175
8999
  if (record.type === "assistant") {
8176
9000
  const message2 = record.message && typeof record.message === "object" ? record.message : record;
8177
9001
  const content = Array.isArray(message2.content) ? message2.content : [];
@@ -8208,9 +9032,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8208
9032
  let reviewFailureDetail = null;
8209
9033
  const stderrLines = [];
8210
9034
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8211
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
9035
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
9036
+ appendRunLog(context.projectRoot, input.runId, {
9037
+ id: `log:${input.runId}:resume-closeout-phase`,
9038
+ title: "Resume continuing from closeout phase",
9039
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
9040
+ tone: "info",
9041
+ status: resumePreviousStatus,
9042
+ createdAt: new Date().toISOString()
9043
+ });
9044
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
9045
+ exit = { code: 0, signal: null };
9046
+ } else if (resumeMode && latestRuntimeWorkspace) {
9047
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
9048
+ taskId: input.taskId ?? runtimeTaskId,
9049
+ workspaceDir: latestRuntimeWorkspace
9050
+ });
9051
+ if (acceptedArtifactState.accepted) {
9052
+ appendRunLog(context.projectRoot, input.runId, {
9053
+ id: `log:${input.runId}:resume-accepted-artifacts`,
9054
+ title: "Resume found accepted artifacts; continuing closeout",
9055
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
9056
+ tone: "info",
9057
+ status: "validating",
9058
+ createdAt: new Date().toISOString()
9059
+ });
9060
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
9061
+ exit = { code: 0, signal: null };
9062
+ }
9063
+ }
9064
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8212
9065
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8213
- const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9066
+ const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8214
9067
  cwd: context.projectRoot,
8215
9068
  env: childEnv,
8216
9069
  stdio: ["pipe", "pipe", "pipe"]
@@ -8249,7 +9102,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8249
9102
  let acceptedArtifactObservedAt = null;
8250
9103
  let acceptedArtifactPollTimer = null;
8251
9104
  let acceptedArtifactKillTimer = null;
8252
- const attemptExit = await new Promise((resolve22) => {
9105
+ const attemptExit = await new Promise((resolve21) => {
8253
9106
  let settled = false;
8254
9107
  const settle = (result) => {
8255
9108
  if (settled)
@@ -8257,7 +9110,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8257
9110
  settled = true;
8258
9111
  if (acceptedArtifactPollTimer)
8259
9112
  clearInterval(acceptedArtifactPollTimer);
8260
- resolve22(result);
9113
+ resolve21(result);
8261
9114
  };
8262
9115
  const pollAcceptedArtifacts = () => {
8263
9116
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -8330,7 +9183,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8330
9183
  });
8331
9184
  for (const pendingLog of pendingLogs) {
8332
9185
  appendRunLog(context.projectRoot, input.runId, pendingLog);
9186
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
8333
9187
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9188
+ if (pendingLog.title === "Tool activity")
9189
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8334
9190
  }
8335
9191
  process.off("SIGTERM", forwardSigterm);
8336
9192
  if (attemptExit.error) {
@@ -8456,8 +9312,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8456
9312
  }
8457
9313
  if (planningClassification.planningRequired) {
8458
9314
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
8459
- const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
8460
- if (!existsSync12(expectedPlanPath)) {
9315
+ const expectedPlanPath = resolve20(planWorkspace, planningArtifactPath);
9316
+ if (!existsSync11(expectedPlanPath)) {
8461
9317
  const failedAt = new Date().toISOString();
8462
9318
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
8463
9319
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -8477,6 +9333,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8477
9333
  throw new CliError2(failureDetail, 1);
8478
9334
  }
8479
9335
  }
9336
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
9337
+ appendPiStageLog({
9338
+ projectRoot: context.projectRoot,
9339
+ runId: input.runId,
9340
+ stage: "Validate",
9341
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
9342
+ status: "completed"
9343
+ });
9344
+ if (verificationAction && !reviewStarted) {
9345
+ verificationAction.complete("Completion verification checks finished.");
9346
+ }
9347
+ if (!reviewAction) {
9348
+ promoteToReviewing("Server-owned closeout is queued.");
9349
+ }
9350
+ if (reviewAction) {
9351
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
9352
+ }
9353
+ const requestedAt = new Date().toISOString();
9354
+ patchAuthorityRun(context.projectRoot, input.runId, {
9355
+ status: "reviewing",
9356
+ completedAt: null,
9357
+ errorText: null,
9358
+ serverCloseout: {
9359
+ status: "pending",
9360
+ phase: "queued",
9361
+ requestedAt,
9362
+ updatedAt: requestedAt,
9363
+ runtimeWorkspace: latestRuntimeWorkspace,
9364
+ branch: latestRuntimeBranch,
9365
+ taskId: input.taskId ?? runtimeTaskId
9366
+ }
9367
+ });
9368
+ appendRunLog(context.projectRoot, input.runId, {
9369
+ id: `log:${input.runId}:server-closeout-requested`,
9370
+ title: "Server-owned closeout requested",
9371
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
9372
+ tone: "info",
9373
+ status: "reviewing",
9374
+ createdAt: requestedAt,
9375
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
9376
+ });
9377
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
9378
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
9379
+ await context.emitEvent("command.finished", {
9380
+ command: [
9381
+ "rig",
9382
+ "server",
9383
+ "task-run",
9384
+ ...input.taskId ? ["--task", input.taskId] : [],
9385
+ ...input.title ? ["--title", input.title] : []
9386
+ ],
9387
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
9388
+ exitCode: 0,
9389
+ durationMs: 0,
9390
+ startedAt,
9391
+ finishedAt: requestedAt
9392
+ });
9393
+ return;
9394
+ }
8480
9395
  const runPiPrFeedbackFix = async (message2) => {
8481
9396
  appendPiStageLog({
8482
9397
  projectRoot: context.projectRoot,
@@ -8495,7 +9410,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8495
9410
  });
8496
9411
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
8497
9412
  const feedbackCommand = buildHostAgentCommand(message2);
8498
- const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9413
+ const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
8499
9414
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
8500
9415
  env: childEnv,
8501
9416
  stdio: ["pipe", "pipe", "pipe"]
@@ -8504,11 +9419,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8504
9419
  child.stdin.write(message2);
8505
9420
  }
8506
9421
  child.stdin.end();
9422
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
9423
+ let feedbackAssistantText = "";
9424
+ const feedbackPendingToolUses = new Map;
8507
9425
  const stdout = createLineInterface({ input: child.stdout });
8508
9426
  stdout.on("line", (line) => {
8509
9427
  const trimmed = line.trim();
8510
9428
  if (!trimmed)
8511
9429
  return;
9430
+ try {
9431
+ const record = JSON.parse(trimmed);
9432
+ const providerLogs = buildClaudeLogsFromRecord({
9433
+ runId: input.runId,
9434
+ record,
9435
+ createdAtFallback: new Date().toISOString(),
9436
+ status: "reviewing",
9437
+ pendingToolUses: feedbackPendingToolUses
9438
+ });
9439
+ for (const providerLog of providerLogs) {
9440
+ appendRunLog(context.projectRoot, input.runId, providerLog);
9441
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
9442
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9443
+ if (providerLog.title === "Tool activity")
9444
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9445
+ }
9446
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
9447
+ projectRoot: context.projectRoot,
9448
+ runId: input.runId,
9449
+ messageId: feedbackAssistantMessageId,
9450
+ record,
9451
+ assistantText: feedbackAssistantText
9452
+ });
9453
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
9454
+ if (hadAssistantDelta) {
9455
+ feedbackAssistantText = nextFeedbackAssistantText;
9456
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9457
+ }
9458
+ if (providerLogs.length > 0 || hadAssistantDelta)
9459
+ return;
9460
+ } catch {}
8512
9461
  appendRunLog(context.projectRoot, input.runId, {
8513
9462
  id: nextRunLogId(),
8514
9463
  title: "Pi PR feedback fix output",
@@ -8534,10 +9483,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8534
9483
  });
8535
9484
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
8536
9485
  });
8537
- const exitCode = await new Promise((resolve22) => {
8538
- child.once("error", () => resolve22(1));
8539
- child.once("close", (code) => resolve22(code ?? 1));
9486
+ const exitCode = await new Promise((resolve21) => {
9487
+ child.once("error", () => resolve21(1));
9488
+ child.once("close", (code) => resolve21(code ?? 1));
8540
9489
  });
9490
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
9491
+ runId: input.runId,
9492
+ status: exitCode === 0 ? "completed" : "failed",
9493
+ pendingToolUses: feedbackPendingToolUses
9494
+ })) {
9495
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
9496
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
9497
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9498
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9499
+ }
9500
+ if (feedbackAssistantText.trim()) {
9501
+ appendRunTimeline(context.projectRoot, input.runId, {
9502
+ id: feedbackAssistantMessageId,
9503
+ type: "assistant_message",
9504
+ text: feedbackAssistantText,
9505
+ state: "completed",
9506
+ createdAt: new Date().toISOString(),
9507
+ completedAt: new Date().toISOString()
9508
+ });
9509
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9510
+ }
8541
9511
  if (exitCode !== 0) {
8542
9512
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
8543
9513
  }
@@ -8655,8 +9625,8 @@ async function executeTest(context, args) {
8655
9625
  }
8656
9626
 
8657
9627
  // packages/cli/src/commands/setup.ts
8658
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
8659
- import { resolve as resolve22 } from "path";
9628
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
9629
+ import { resolve as resolve21 } from "path";
8660
9630
  import { createPluginHost } from "@rig/core";
8661
9631
  import {
8662
9632
  isSupportedBunVersion as isSupportedBunVersion2,
@@ -8719,8 +9689,8 @@ function runSetupInit(projectRoot) {
8719
9689
  mkdirSync8(stateDir, { recursive: true });
8720
9690
  mkdirSync8(logsDir, { recursive: true });
8721
9691
  mkdirSync8(artifactsDir, { recursive: true });
8722
- const failuresPath = resolve22(stateDir, "failed_approaches.md");
8723
- if (!existsSync13(failuresPath)) {
9692
+ const failuresPath = resolve21(stateDir, "failed_approaches.md");
9693
+ if (!existsSync12(failuresPath)) {
8724
9694
  writeFileSync7(failuresPath, `# Failed Approaches
8725
9695
 
8726
9696
  `, "utf-8");
@@ -8738,18 +9708,18 @@ async function runSetupCheck(projectRoot) {
8738
9708
  }
8739
9709
  async function runSetupPreflight(projectRoot) {
8740
9710
  await runSetupCheck(projectRoot);
8741
- const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
8742
- if (existsSync13(validationRoot)) {
9711
+ const validationRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
9712
+ if (existsSync12(validationRoot)) {
8743
9713
  const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
8744
9714
  for (const validator of validators) {
8745
- const script = resolve22(validationRoot, validator.name, "validate.sh");
8746
- if (existsSync13(script)) {
9715
+ const script = resolve21(validationRoot, validator.name, "validate.sh");
9716
+ if (existsSync12(script)) {
8747
9717
  console.log(`OK: validator script ${script}`);
8748
9718
  }
8749
9719
  }
8750
9720
  }
8751
- const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
8752
- if (existsSync13(hooksRoot)) {
9721
+ const hooksRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
9722
+ if (existsSync12(hooksRoot)) {
8753
9723
  const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
8754
9724
  for (const hook of hooks) {
8755
9725
  console.log(`OK: hook ${hook}`);