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

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.
package/dist/src/index.js CHANGED
@@ -101,12 +101,13 @@ async function runCommand(context, parts) {
101
101
  const envMode = process.env.RIG_BASH_MODE;
102
102
  const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
103
103
  const controlledPath = `${resolve(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
104
- const controlledBash = await ensureAgentShellBinary(context.projectRoot);
104
+ const usesInfrastructureBinary = parts[0] === "rig-server";
105
+ const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
105
106
  const commandEnv = [
106
107
  "env",
107
108
  `PATH=${controlledPath}`,
108
109
  `PROJECT_RIG_ROOT=${context.projectRoot}`,
109
- `BASH=${controlledBash}`,
110
+ ...controlledBash ? [`BASH=${controlledBash}`] : [],
110
111
  `RIG_BASH_MODE=${effectiveMode}`,
111
112
  `RIG_POLICY_FILE=${resolve(context.projectRoot, "rig/policy/policy.json")}`,
112
113
  ...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
@@ -2666,17 +2667,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
2666
2667
  }
2667
2668
 
2668
2669
  // packages/cli/src/commands/_server-client.ts
2669
- import { spawnSync } from "child_process";
2670
2670
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
2671
2671
  import { resolve as resolve9 } from "path";
2672
2672
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
2673
- var cachedGitHubBearerToken;
2673
+ var scopedGitHubBearerTokens = new Map;
2674
2674
  function cleanToken(value) {
2675
2675
  const trimmed = value?.trim();
2676
2676
  return trimmed ? trimmed : null;
2677
2677
  }
2678
- function setGitHubBearerTokenForCurrentProcess(token) {
2679
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
2678
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
2679
+ const scopedKey = resolve9(projectRoot ?? process.cwd());
2680
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
2680
2681
  }
2681
2682
  function readPrivateRemoteSessionToken(projectRoot) {
2682
2683
  const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
@@ -2690,25 +2691,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
2690
2691
  }
2691
2692
  }
2692
2693
  function readGitHubBearerTokenForRemote(projectRoot) {
2693
- if (cachedGitHubBearerToken !== undefined)
2694
- return cachedGitHubBearerToken;
2694
+ const scopedKey = resolve9(projectRoot);
2695
+ if (scopedGitHubBearerTokens.has(scopedKey))
2696
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
2695
2697
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
2696
- if (privateSession) {
2697
- cachedGitHubBearerToken = privateSession;
2698
- return cachedGitHubBearerToken;
2699
- }
2700
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
2701
- if (envToken) {
2702
- cachedGitHubBearerToken = envToken;
2703
- return cachedGitHubBearerToken;
2704
- }
2705
- const result = spawnSync("gh", ["auth", "token"], {
2706
- encoding: "utf8",
2707
- timeout: 5000,
2708
- stdio: ["ignore", "pipe", "ignore"]
2709
- });
2710
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
2711
- return cachedGitHubBearerToken;
2698
+ if (privateSession)
2699
+ return privateSession;
2700
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
2712
2701
  }
2713
2702
  async function ensureServerForCli(projectRoot) {
2714
2703
  try {
@@ -2846,16 +2835,36 @@ async function registerProjectViaServer(context, input) {
2846
2835
  function sleep(ms) {
2847
2836
  return new Promise((resolve10) => setTimeout(resolve10, ms));
2848
2837
  }
2838
+ function isRetryableProjectRootSwitchError(error) {
2839
+ if (!(error instanceof Error))
2840
+ return false;
2841
+ const message = error.message.toLowerCase();
2842
+ 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");
2843
+ }
2849
2844
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
2850
- const switched = await requestServerJson(context, "/api/server/project-root", {
2851
- method: "POST",
2852
- headers: { "content-type": "application/json" },
2853
- body: JSON.stringify({ projectRoot })
2854
- });
2855
2845
  const timeoutMs = options.timeoutMs ?? 30000;
2856
2846
  const pollMs = options.pollMs ?? 1000;
2857
2847
  const deadline = Date.now() + timeoutMs;
2858
2848
  let lastError;
2849
+ let switched = null;
2850
+ while (Date.now() < deadline) {
2851
+ try {
2852
+ switched = await requestServerJson(context, "/api/server/project-root", {
2853
+ method: "POST",
2854
+ headers: { "content-type": "application/json" },
2855
+ body: JSON.stringify({ projectRoot })
2856
+ });
2857
+ break;
2858
+ } catch (error) {
2859
+ lastError = error;
2860
+ if (!isRetryableProjectRootSwitchError(error))
2861
+ throw error;
2862
+ await sleep(pollMs);
2863
+ }
2864
+ }
2865
+ if (!switched) {
2866
+ 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);
2867
+ }
2859
2868
  while (Date.now() < deadline) {
2860
2869
  try {
2861
2870
  const status = await requestServerJson(context, "/api/server/status");
@@ -2873,6 +2882,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
2873
2882
  }
2874
2883
  throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
2875
2884
  }
2885
+ async function listRunsViaServer(context, options = {}) {
2886
+ const url = new URL("http://rig.local/api/runs");
2887
+ if (options.limit !== undefined)
2888
+ url.searchParams.set("limit", String(options.limit));
2889
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2890
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
2891
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
2892
+ }
2876
2893
  async function getRunDetailsViaServer(context, runId) {
2877
2894
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
2878
2895
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
@@ -2886,6 +2903,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
2886
2903
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2887
2904
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2888
2905
  }
2906
+ async function getRunTimelineViaServer(context, runId, options = {}) {
2907
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
2908
+ if (options.limit !== undefined)
2909
+ url.searchParams.set("limit", String(options.limit));
2910
+ if (options.cursor)
2911
+ url.searchParams.set("cursor", options.cursor);
2912
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2913
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2914
+ }
2915
+ async function ensureTaskLabelsViaServer(context) {
2916
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
2917
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2918
+ }
2919
+ async function listGitHubProjectsViaServer(context, owner) {
2920
+ const url = new URL("http://rig.local/api/github/projects");
2921
+ url.searchParams.set("owner", owner);
2922
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2923
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
2924
+ }
2925
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
2926
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
2927
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2928
+ }
2929
+ async function updateWorkspaceTaskViaServer(context, input) {
2930
+ const payload = await requestServerJson(context, "/api/tasks/update", {
2931
+ method: "POST",
2932
+ headers: { "content-type": "application/json" },
2933
+ body: JSON.stringify(input)
2934
+ });
2935
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2936
+ }
2889
2937
  async function stopRunViaServer(context, runId) {
2890
2938
  const payload = await requestServerJson(context, "/api/runs/stop", {
2891
2939
  method: "POST",
@@ -2938,7 +2986,8 @@ async function submitTaskRunViaServer(context, input) {
2938
2986
  import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2939
2987
  import { homedir as homedir3 } from "os";
2940
2988
  import { resolve as resolve10 } from "path";
2941
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2989
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2990
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2942
2991
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2943
2992
  export { default } from '@rig/pi-rig';
2944
2993
  `;
@@ -2966,7 +3015,7 @@ function resolvePiHomeDir(inputHomeDir) {
2966
3015
  function piListContainsPiRig(output) {
2967
3016
  return output.split(/\r?\n/).some((line) => {
2968
3017
  const normalized = line.trim();
2969
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
3018
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
2970
3019
  });
2971
3020
  }
2972
3021
  async function safeRun(runner, command, options) {
@@ -3082,7 +3131,7 @@ async function ensureRemotePiRigInstalled(input) {
3082
3131
  const payload = await input.requestJson("/api/pi-rig/install", {
3083
3132
  method: "POST",
3084
3133
  headers: { "content-type": "application/json" },
3085
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
3134
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
3086
3135
  });
3087
3136
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
3088
3137
  const piOk = record.piOk === true || record.ok === true;
@@ -3165,6 +3214,9 @@ function permissionAllowsPr(payload) {
3165
3214
  }
3166
3215
  return null;
3167
3216
  }
3217
+ function isNotFoundError(error) {
3218
+ return /\b(404|not found)\b/i.test(message(error));
3219
+ }
3168
3220
  function projectCheckoutReady(payload) {
3169
3221
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
3170
3222
  return null;
@@ -3197,19 +3249,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
3197
3249
  const checks = [];
3198
3250
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
3199
3251
  const taskId = options.taskId?.trim() || null;
3252
+ const requiresCurrentRunApi = Boolean(taskId);
3253
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
3254
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
3255
+ let legacyServerCompatibility = false;
3200
3256
  try {
3201
3257
  await request("/api/server/status");
3202
3258
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
3203
3259
  } catch (error) {
3204
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3260
+ if (isNotFoundError(error)) {
3261
+ try {
3262
+ await request("/health");
3263
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
3264
+ 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"));
3265
+ } catch (healthError) {
3266
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
3267
+ }
3268
+ } else {
3269
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3270
+ }
3205
3271
  }
3206
3272
  const repo = readRepoConnection(context.projectRoot);
3207
- 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>`."));
3273
+ 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>`."));
3208
3274
  try {
3209
3275
  const auth = await request("/api/github/auth/status");
3210
- 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>`."));
3276
+ 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>`."));
3211
3277
  } catch (error) {
3212
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3278
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3213
3279
  }
3214
3280
  try {
3215
3281
  const projection = await request("/api/workspace/task-projection");
@@ -3237,9 +3303,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
3237
3303
  try {
3238
3304
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
3239
3305
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
3240
- 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."));
3306
+ 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."));
3241
3307
  } catch (error) {
3242
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
3308
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
3243
3309
  }
3244
3310
  try {
3245
3311
  const runs = await request("/api/runs?limit=200");
@@ -3442,6 +3508,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3442
3508
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3443
3509
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3444
3510
  const next = {
3511
+ ...existing ?? {},
3445
3512
  runId: input.runId,
3446
3513
  projectRoot,
3447
3514
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -4150,9 +4217,10 @@ async function executeInbox(context, args) {
4150
4217
 
4151
4218
  // packages/cli/src/commands/init.ts
4152
4219
  import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4153
- import { spawnSync as spawnSync2 } from "child_process";
4220
+ import { spawnSync } from "child_process";
4154
4221
  import { resolve as resolve17 } from "path";
4155
4222
  import { buildRigInitConfigSource } from "@rig/core";
4223
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4156
4224
 
4157
4225
  // packages/cli/src/commands/_snapshot-upload.ts
4158
4226
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -4466,6 +4534,7 @@ function countDoctorFailures(checks) {
4466
4534
 
4467
4535
  // packages/cli/src/commands/init.ts
4468
4536
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4537
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
4469
4538
  var RIG_CONFIG_DEV_DEPENDENCIES = {
4470
4539
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
4471
4540
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -4476,7 +4545,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
4476
4545
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
4477
4546
  }
4478
4547
  function detectOriginRepoSlug(projectRoot) {
4479
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4548
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4480
4549
  if (result.status !== 0)
4481
4550
  return null;
4482
4551
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -4532,11 +4601,14 @@ function applyGitHubProjectConfig(source, options) {
4532
4601
  return source;
4533
4602
  const projectId = JSON.stringify(options.githubProject);
4534
4603
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
4604
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
4605
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
4606
+ `)},` : "";
4535
4607
  return source.replace(` projects: { enabled: false },`, [
4536
4608
  ` projects: {`,
4537
4609
  ` enabled: true,`,
4538
4610
  ` projectId: ${projectId},`,
4539
- ` statusFieldId: ${statusFieldId},`,
4611
+ ` statusFieldId: ${statusFieldId},${statuses}`,
4540
4612
  ` },`
4541
4613
  ].join(`
4542
4614
  `));
@@ -4557,21 +4629,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
4557
4629
  }
4558
4630
  }
4559
4631
  function detectGhLogin() {
4560
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4632
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4561
4633
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
4562
4634
  }
4563
4635
  function readGhAuthToken() {
4564
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4636
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
4565
4637
  if (result.status !== 0 || !result.stdout.trim()) {
4566
4638
  throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
4567
4639
  }
4568
4640
  return result.stdout.trim();
4569
4641
  }
4642
+ function refreshGhProjectScopesAndReadToken() {
4643
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
4644
+ encoding: "utf8",
4645
+ stdio: ["inherit", "pipe", "pipe"]
4646
+ });
4647
+ if (result.status !== 0)
4648
+ return null;
4649
+ try {
4650
+ return readGhAuthToken();
4651
+ } catch {
4652
+ return null;
4653
+ }
4654
+ }
4570
4655
  async function loadClackPrompts() {
4571
4656
  return await import("@clack/prompts");
4572
4657
  }
4658
+ function clackTextOptions(options) {
4659
+ return {
4660
+ message: options.message,
4661
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
4662
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
4663
+ };
4664
+ }
4573
4665
  async function promptRequiredText(prompts, options) {
4574
- const value = await prompts.text(options);
4666
+ const value = await prompts.text(clackTextOptions(options));
4575
4667
  if (prompts.isCancel(value))
4576
4668
  throw new CliError2("Init cancelled.", 1);
4577
4669
  const text2 = String(value ?? "").trim();
@@ -4580,7 +4672,7 @@ async function promptRequiredText(prompts, options) {
4580
4672
  return text2;
4581
4673
  }
4582
4674
  async function promptOptionalText(prompts, options) {
4583
- const value = await prompts.text(options);
4675
+ const value = await prompts.text(clackTextOptions(options));
4584
4676
  if (prompts.isCancel(value))
4585
4677
  throw new CliError2("Init cancelled.", 1);
4586
4678
  return String(value ?? "").trim();
@@ -4591,6 +4683,164 @@ async function promptSelect(prompts, options) {
4591
4683
  throw new CliError2("Init cancelled.", 1);
4592
4684
  return String(value);
4593
4685
  }
4686
+ function repoOwnerFromSlug(repoSlug) {
4687
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
4688
+ }
4689
+ function recordArray(value, key) {
4690
+ if (!value || typeof value !== "object" || Array.isArray(value))
4691
+ return [];
4692
+ const raw = value[key];
4693
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4694
+ }
4695
+ async function listGitHubProjectsForInit(context, owner, token) {
4696
+ if (token?.trim()) {
4697
+ try {
4698
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
4699
+ } catch (directError) {
4700
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
4701
+ if (recordArray(serverPayload, "projects").length > 0)
4702
+ return serverPayload;
4703
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
4704
+ }
4705
+ }
4706
+ return listGitHubProjectsViaServer(context, owner);
4707
+ }
4708
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
4709
+ if (token?.trim()) {
4710
+ try {
4711
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
4712
+ } catch (directError) {
4713
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
4714
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
4715
+ return serverPayload;
4716
+ }
4717
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
4718
+ }
4719
+ }
4720
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
4721
+ }
4722
+ var PROJECT_STATUS_PROMPTS = {
4723
+ running: "Running/In progress",
4724
+ prOpen: "PR open/review",
4725
+ ciFixing: "CI/review fixing",
4726
+ merging: "Merging",
4727
+ done: "Done",
4728
+ needsAttention: "Needs attention"
4729
+ };
4730
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
4731
+ running: "In Progress",
4732
+ prOpen: "In Review",
4733
+ ciFixing: "In Review",
4734
+ merging: "Merging",
4735
+ done: "Done",
4736
+ needsAttention: "Needs Attention"
4737
+ };
4738
+ async function promptManualProjectStatusMapping(prompts) {
4739
+ const statuses = {};
4740
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4741
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
4742
+ const value = await promptOptionalText(prompts, {
4743
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
4744
+ placeholder: defaultLabel
4745
+ });
4746
+ statuses[key] = value || defaultLabel;
4747
+ }
4748
+ return statuses;
4749
+ }
4750
+ function projectScopeError(value) {
4751
+ const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
4752
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
4753
+ }
4754
+ function optionName(option) {
4755
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
4756
+ }
4757
+ function autoProjectStatusValue(options, key, label) {
4758
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
4759
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
4760
+ if (!match)
4761
+ return null;
4762
+ return String(match.id ?? match.name);
4763
+ }
4764
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
4765
+ const projectChoice = await promptSelect(prompts, {
4766
+ message: "GitHub Projects status sync",
4767
+ initialValue: "select",
4768
+ options: [
4769
+ { value: "select", label: "Select accessible ProjectV2" },
4770
+ { value: "off", label: "Off" },
4771
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
4772
+ ]
4773
+ });
4774
+ if (projectChoice === "off")
4775
+ return { githubProject: "off" };
4776
+ if (projectChoice === "manual") {
4777
+ return {
4778
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
4779
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
4780
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
4781
+ };
4782
+ }
4783
+ const owner = repoOwnerFromSlug(repoSlug);
4784
+ if (!owner)
4785
+ throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
4786
+ let activeToken = githubToken?.trim() || null;
4787
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4788
+ let projects = recordArray(projectsPayload, "projects");
4789
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
4790
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
4791
+ const refreshedToken = refreshProjectToken();
4792
+ if (refreshedToken) {
4793
+ activeToken = refreshedToken;
4794
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4795
+ projects = recordArray(projectsPayload, "projects");
4796
+ }
4797
+ }
4798
+ if (projects.length === 0) {
4799
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
4800
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
4801
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
4802
+ }
4803
+ const selectedProjectId = await promptSelect(prompts, {
4804
+ message: "GitHub ProjectV2 project",
4805
+ options: [
4806
+ ...projects.map((project) => ({
4807
+ value: String(project.id),
4808
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
4809
+ hint: typeof project.url === "string" ? project.url : undefined
4810
+ })),
4811
+ { value: "manual", label: "Enter ProjectV2 id manually" }
4812
+ ]
4813
+ });
4814
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
4815
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
4816
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
4817
+ const rawField = fieldPayloadRecord.field;
4818
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
4819
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
4820
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4821
+ if (options.length === 0) {
4822
+ return {
4823
+ githubProject: projectId,
4824
+ githubProjectStatusField: fieldId,
4825
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
4826
+ ...activeToken ? { githubToken: activeToken } : {}
4827
+ };
4828
+ }
4829
+ const statuses = {};
4830
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4831
+ const auto = autoProjectStatusValue(options, key, label);
4832
+ statuses[key] = auto ?? await promptSelect(prompts, {
4833
+ message: `Project status option for ${label}`,
4834
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
4835
+ });
4836
+ }
4837
+ return {
4838
+ githubProject: projectId,
4839
+ githubProjectStatusField: fieldId,
4840
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
4841
+ ...activeToken ? { githubToken: activeToken } : {}
4842
+ };
4843
+ }
4594
4844
  function sleep2(ms) {
4595
4845
  return new Promise((resolve18) => setTimeout(resolve18, ms));
4596
4846
  }
@@ -4604,12 +4854,29 @@ function apiSessionTokenFrom(payload) {
4604
4854
  const token = payload.apiSessionToken;
4605
4855
  return typeof token === "string" && token.trim() ? token.trim() : null;
4606
4856
  }
4857
+ function cleanPayloadString(value) {
4858
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4859
+ }
4860
+ function remoteGitHubAuthMetadata(payload) {
4861
+ if (!payload)
4862
+ return {};
4863
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
4864
+ return {
4865
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
4866
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
4867
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
4868
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
4869
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
4870
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
4871
+ };
4872
+ }
4607
4873
  function writeRemoteGitHubAuthState(projectRoot, input) {
4608
4874
  writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
4609
4875
  authenticated: true,
4610
4876
  source: input.source,
4611
4877
  storedOnServer: true,
4612
4878
  selectedRepo: input.selectedRepo,
4879
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
4613
4880
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
4614
4881
  updatedAt: new Date().toISOString()
4615
4882
  }, null, 2)}
@@ -4702,12 +4969,13 @@ async function runControlPlaneInit(context, options) {
4702
4969
  if (token) {
4703
4970
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
4704
4971
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
4705
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4972
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4706
4973
  if (serverKind === "remote") {
4707
4974
  writeRemoteGitHubAuthState(projectRoot, {
4708
4975
  source: authMethod === "gh" ? "gh" : "init-token",
4709
4976
  selectedRepo: repo.slug,
4710
- apiSessionToken
4977
+ apiSessionToken,
4978
+ authPayload: githubAuth
4711
4979
  });
4712
4980
  }
4713
4981
  } else if (authMethod === "device") {
@@ -4726,9 +4994,9 @@ async function runControlPlaneInit(context, options) {
4726
4994
  if (completed) {
4727
4995
  const apiSessionToken = apiSessionTokenFrom(completed);
4728
4996
  if (apiSessionToken) {
4729
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
4997
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
4730
4998
  if (serverKind === "remote") {
4731
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
4999
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
4732
5000
  }
4733
5001
  }
4734
5002
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -4746,19 +5014,25 @@ async function runControlPlaneInit(context, options) {
4746
5014
  Object.assign(checkout, preparedCheckout);
4747
5015
  }
4748
5016
  }
5017
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
5018
+ if (serverKind === "remote" && checkoutPath && token) {
5019
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
5020
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
5021
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
5022
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
5023
+ }
4749
5024
  const registered = await registerProjectViaServer(context, {
4750
5025
  repoSlug: repo.slug,
4751
5026
  checkout
4752
5027
  });
4753
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
4754
5028
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
4755
- if (serverRootSwitch && token) {
4756
- githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
4757
- const apiSessionToken = apiSessionTokenFrom(githubAuth);
4758
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4759
- writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
4760
- }
4761
5029
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
5030
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
5031
+ ok: false,
5032
+ ready: false,
5033
+ labelsReady: false,
5034
+ error: error instanceof Error ? error.message : String(error)
5035
+ }));
4762
5036
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
4763
5037
  remote: true,
4764
5038
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -4789,6 +5063,7 @@ async function runControlPlaneInit(context, options) {
4789
5063
  githubAuth,
4790
5064
  deviceAuth,
4791
5065
  githubAuthWarning: remoteGhTokenWarning,
5066
+ labelSetup,
4792
5067
  pi,
4793
5068
  doctor
4794
5069
  };
@@ -4895,12 +5170,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4895
5170
  });
4896
5171
  const serverChoice = await promptSelect(prompts, {
4897
5172
  message: "Rig server",
5173
+ initialValue: "remote",
4898
5174
  options: [
4899
- { value: "local", label: "Local server", hint: "run on this machine" },
4900
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
5175
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
5176
+ { value: "local", label: "Local server", hint: "run on this machine" }
4901
5177
  ]
4902
5178
  });
4903
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
5179
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
4904
5180
  let remoteCheckout;
4905
5181
  if (serverChoice === "remote") {
4906
5182
  const checkout = await promptSelect(prompts, {
@@ -4932,38 +5208,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4932
5208
  { value: "skip", label: "Skip for now" }
4933
5209
  ]
4934
5210
  });
5211
+ let remoteGhTokenConfirmed = false;
4935
5212
  if (serverChoice === "remote" && authMethod === "gh") {
4936
5213
  if (!prompts.confirm)
4937
5214
  throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
4938
5215
  const confirmed = await prompts.confirm({
4939
5216
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
4940
- initialValue: false
5217
+ initialValue: true
4941
5218
  });
4942
5219
  if (prompts.isCancel(confirmed) || confirmed !== true) {
4943
5220
  throw new CliError2("Remote gh-token import cancelled.", 1);
4944
5221
  }
5222
+ remoteGhTokenConfirmed = true;
4945
5223
  }
4946
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
4947
- const projectChoice = await promptSelect(prompts, {
4948
- message: "GitHub Projects status sync",
4949
- options: [
4950
- { value: "off", label: "Off" },
4951
- { value: "configure", label: "Configure ProjectV2 status field" }
4952
- ]
4953
- });
4954
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
4955
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
5224
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
5225
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
5226
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
4956
5227
  const result = await runControlPlaneInit(context, {
4957
5228
  server: serverChoice,
4958
5229
  remoteUrl,
4959
5230
  repoSlug,
4960
- githubToken,
5231
+ githubToken: effectiveGithubToken,
4961
5232
  githubAuthMethod: authMethod,
4962
- githubProject,
4963
- githubProjectStatusField,
5233
+ githubProject: projectConfig.githubProject,
5234
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
5235
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
4964
5236
  remoteCheckout,
4965
5237
  repair,
4966
- privateStateOnly
5238
+ privateStateOnly,
5239
+ yes: remoteGhTokenConfirmed || undefined
4967
5240
  });
4968
5241
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
4969
5242
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -5059,7 +5332,7 @@ Usage: rig connect <list|add|use|status>`, 1);
5059
5332
  }
5060
5333
 
5061
5334
  // packages/cli/src/commands/github.ts
5062
- import { spawnSync as spawnSync3 } from "child_process";
5335
+ import { spawnSync as spawnSync2 } from "child_process";
5063
5336
  function printPayload(context, payload, fallback) {
5064
5337
  if (context.outputMode === "json")
5065
5338
  console.log(JSON.stringify(payload, null, 2));
@@ -5067,7 +5340,7 @@ function printPayload(context, payload, fallback) {
5067
5340
  console.log(fallback);
5068
5341
  }
5069
5342
  function readGhToken() {
5070
- const result = spawnSync3("gh", ["auth", "token"], { encoding: "utf8" });
5343
+ const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
5071
5344
  if (result.status !== 0) {
5072
5345
  const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
5073
5346
  throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
@@ -6099,14 +6372,10 @@ async function executeRemote(context, args) {
6099
6372
  }
6100
6373
 
6101
6374
  // packages/cli/src/commands/run.ts
6102
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
6103
- import { resolve as resolve20 } from "path";
6104
6375
  import { createInterface as createInterface2 } from "readline/promises";
6105
6376
  import {
6106
6377
  listAuthorityRuns as listAuthorityRuns3,
6107
- readAuthorityRun as readAuthorityRun4,
6108
- readJsonlFile as readJsonlFile4,
6109
- resolveAuthorityRunDir as resolveAuthorityRunDir5
6378
+ readAuthorityRun as readAuthorityRun4
6110
6379
  } from "@rig/runtime/control-plane/authority-files";
6111
6380
  import {
6112
6381
  cleanupRunState,
@@ -6114,6 +6383,7 @@ import {
6114
6383
  listOpenEpics,
6115
6384
  resolveDefaultEpic,
6116
6385
  runResume,
6386
+ runRestart,
6117
6387
  runStatus,
6118
6388
  runStop,
6119
6389
  startRun,
@@ -6121,9 +6391,9 @@ import {
6121
6391
  } from "@rig/runtime/control-plane/native/run-ops";
6122
6392
  import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
6123
6393
 
6124
- // packages/cli/src/commands/_operator-view.ts
6394
+ // packages/cli/src/commands/_operator-surface.ts
6125
6395
  import { createInterface } from "readline";
6126
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6396
+ import { createInterface as createPromptInterface } from "readline/promises";
6127
6397
  var CANONICAL_STAGES = [
6128
6398
  "Connect",
6129
6399
  "GitHub/task sync",
@@ -6138,18 +6408,188 @@ var CANONICAL_STAGES = [
6138
6408
  "Merge",
6139
6409
  "Complete"
6140
6410
  ];
6411
+ function logDetail(log3) {
6412
+ return typeof log3.detail === "string" ? log3.detail.trim() : "";
6413
+ }
6414
+ function parseProviderProtocolLog(title, detail) {
6415
+ if (title.trim().toLowerCase() !== "agent output")
6416
+ return null;
6417
+ if (!detail.startsWith("{") || !detail.endsWith("}"))
6418
+ return null;
6419
+ try {
6420
+ const record = JSON.parse(detail);
6421
+ if (!record || typeof record !== "object" || Array.isArray(record))
6422
+ return null;
6423
+ const type = record.type;
6424
+ return typeof type === "string" && [
6425
+ "assistant",
6426
+ "message_start",
6427
+ "message_update",
6428
+ "message_end",
6429
+ "stream_event",
6430
+ "tool_result",
6431
+ "tool_execution_start",
6432
+ "tool_execution_update",
6433
+ "tool_execution_end",
6434
+ "turn_start",
6435
+ "turn_end"
6436
+ ].includes(type) ? record : null;
6437
+ } catch {
6438
+ return null;
6439
+ }
6440
+ }
6441
+ function renderProviderProtocolLog(record) {
6442
+ const type = typeof record.type === "string" ? record.type : "";
6443
+ if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
6444
+ const toolName = String(record.toolName ?? record.name ?? "tool");
6445
+ 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";
6446
+ return `[Pi tool] ${toolName} ${status}`;
6447
+ }
6448
+ return null;
6449
+ }
6450
+ function entryId(entry, fallback) {
6451
+ return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
6452
+ }
6141
6453
  function renderOperatorSnapshot(snapshot) {
6142
6454
  const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
6143
6455
  const runId = String(run.runId ?? run.id ?? "run");
6144
6456
  const status = String(run.status ?? "unknown");
6145
6457
  const logs = snapshot.logs ?? [];
6458
+ const latestByStage = new Map;
6459
+ for (const log3 of logs) {
6460
+ const title = String(log3.title ?? "").toLowerCase();
6461
+ const stageName = String(log3.stage ?? "").toLowerCase();
6462
+ const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6463
+ if (stage)
6464
+ latestByStage.set(stage, log3);
6465
+ }
6146
6466
  const stageLines = CANONICAL_STAGES.flatMap((stage) => {
6147
- const match = logs.find((log3) => String(log3.title ?? "").toLowerCase() === stage.toLowerCase() || String(log3.stage ?? "").toLowerCase() === stage.toLowerCase());
6148
- return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
6467
+ const match = latestByStage.get(stage);
6468
+ return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
6149
6469
  });
6150
6470
  return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
6151
6471
  `);
6152
6472
  }
6473
+ function createPiRunStreamRenderer(output = process.stdout) {
6474
+ let lastSnapshot = "";
6475
+ const assistantTextById = new Map;
6476
+ const seenTimeline = new Set;
6477
+ const seenLogs = new Set;
6478
+ const writeLine = (line) => output.write(`${line}
6479
+ `);
6480
+ return {
6481
+ renderSnapshot(snapshot) {
6482
+ const rendered = renderOperatorSnapshot(snapshot);
6483
+ if (rendered && rendered !== lastSnapshot) {
6484
+ writeLine(rendered);
6485
+ lastSnapshot = rendered;
6486
+ }
6487
+ },
6488
+ renderTimeline(entries) {
6489
+ for (const [index, entry] of entries.entries()) {
6490
+ const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
6491
+ if (entry.type === "assistant_message" && typeof entry.text === "string") {
6492
+ const text2 = entry.text;
6493
+ const previousText = assistantTextById.get(id) ?? "";
6494
+ if (!previousText && text2.trim()) {
6495
+ writeLine("[Pi assistant]");
6496
+ }
6497
+ if (text2.startsWith(previousText)) {
6498
+ const delta = text2.slice(previousText.length);
6499
+ if (delta)
6500
+ output.write(delta);
6501
+ } else if (text2.trim() && text2 !== previousText) {
6502
+ if (previousText)
6503
+ writeLine(`
6504
+ [Pi assistant]`);
6505
+ output.write(text2);
6506
+ }
6507
+ assistantTextById.set(id, text2);
6508
+ continue;
6509
+ }
6510
+ if (seenTimeline.has(id))
6511
+ continue;
6512
+ seenTimeline.add(id);
6513
+ if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
6514
+ writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
6515
+ continue;
6516
+ }
6517
+ if (entry.type === "timeline_warning") {
6518
+ writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
6519
+ }
6520
+ }
6521
+ },
6522
+ renderLogs(entries) {
6523
+ for (const [index, entry] of entries.entries()) {
6524
+ const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
6525
+ if (seenLogs.has(id))
6526
+ continue;
6527
+ seenLogs.add(id);
6528
+ const title = String(entry.title ?? "");
6529
+ if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
6530
+ continue;
6531
+ const detail = logDetail(entry);
6532
+ if (!detail)
6533
+ continue;
6534
+ const protocolRecord = parseProviderProtocolLog(title, detail);
6535
+ if (protocolRecord) {
6536
+ const protocolLine = renderProviderProtocolLog(protocolRecord);
6537
+ if (protocolLine)
6538
+ writeLine(protocolLine);
6539
+ continue;
6540
+ }
6541
+ writeLine(`[${title || "Rig log"}] ${detail}`);
6542
+ }
6543
+ }
6544
+ };
6545
+ }
6546
+ function createOperatorSurface(options = {}) {
6547
+ const input = options.input ?? process.stdin;
6548
+ const output = options.output ?? process.stdout;
6549
+ const errorOutput = options.errorOutput ?? process.stderr;
6550
+ const renderer = createPiRunStreamRenderer(output);
6551
+ const writeLine = (line) => output.write(`${line}
6552
+ `);
6553
+ return {
6554
+ mode: "pi-compatible-text",
6555
+ ...renderer,
6556
+ info: writeLine,
6557
+ error: (message2) => errorOutput.write(`${message2}
6558
+ `),
6559
+ attachCommandInput(handler) {
6560
+ if (options.interactive === false || !input.isTTY)
6561
+ return null;
6562
+ const rl = createInterface({ input, output: process.stdout, terminal: false });
6563
+ rl.on("line", (line) => {
6564
+ Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6565
+ });
6566
+ return { close: () => rl.close() };
6567
+ }
6568
+ };
6569
+ }
6570
+ function taskId(task) {
6571
+ return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6572
+ }
6573
+ function taskTitle(task) {
6574
+ return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6575
+ }
6576
+ function taskStatus(task) {
6577
+ return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6578
+ }
6579
+ function renderTaskPickerRows(tasks) {
6580
+ return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6581
+ }
6582
+ async function promptForTaskSelection(question) {
6583
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
6584
+ try {
6585
+ return await rl.question(question);
6586
+ } finally {
6587
+ rl.close();
6588
+ }
6589
+ }
6590
+
6591
+ // packages/cli/src/commands/_operator-view.ts
6592
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6153
6593
  function runStatusFromPayload(payload) {
6154
6594
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6155
6595
  return String(run.status ?? "unknown").toLowerCase();
@@ -6171,11 +6611,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
6171
6611
  await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
6172
6612
  return { action: "continue", message: "Steering message queued." };
6173
6613
  }
6174
- async function readOperatorSnapshot(context, runId) {
6614
+ async function readOperatorSnapshot(context, runId, options = {}) {
6175
6615
  const run = await getRunDetailsViaServer(context, runId);
6176
6616
  const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
6177
- const entries = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6178
- return { run, logs: entries, rendered: renderOperatorSnapshot({ run, logs: entries }) };
6617
+ const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
6618
+ entries: [{
6619
+ id: `timeline-unavailable:${runId}`,
6620
+ type: "timeline_warning",
6621
+ detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
6622
+ createdAt: new Date().toISOString()
6623
+ }],
6624
+ nextCursor: options.timelineCursor ?? null
6625
+ }));
6626
+ const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
6627
+ const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6628
+ const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6629
+ return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6179
6630
  }
6180
6631
  async function attachRunOperatorView(context, input) {
6181
6632
  let steered = false;
@@ -6183,45 +6634,266 @@ async function attachRunOperatorView(context, input) {
6183
6634
  await steerRunViaServer(context, input.runId, input.message.trim());
6184
6635
  steered = true;
6185
6636
  }
6637
+ const surface = createOperatorSurface({ interactive: input.interactive !== false });
6186
6638
  let snapshot = await readOperatorSnapshot(context, input.runId);
6187
6639
  if (context.outputMode === "text") {
6188
- console.log(snapshot.rendered);
6640
+ surface.renderSnapshot(snapshot);
6641
+ surface.renderTimeline(snapshot.timeline);
6642
+ surface.renderLogs(snapshot.logs);
6189
6643
  if (steered)
6190
- console.log("Steering message queued.");
6644
+ surface.info("Steering message queued.");
6191
6645
  }
6192
6646
  let detached = false;
6193
- let rl = null;
6647
+ let commandInput = null;
6194
6648
  if (input.follow && !input.once && context.outputMode === "text") {
6195
6649
  if (input.interactive !== false && process.stdin.isTTY) {
6196
- console.log("Controls: /user <message>, /stop, /detach");
6197
- rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
6198
- rl.on("line", (line) => {
6199
- applyOperatorCommand(context, { runId: input.runId, line }).then((result) => {
6200
- if (result.message)
6201
- console.log(result.message);
6202
- if (result.action === "detach" || result.action === "stopped") {
6203
- detached = true;
6204
- rl?.close();
6205
- }
6206
- }).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6650
+ surface.info("Controls: /user <message>, /stop, /detach");
6651
+ commandInput = surface.attachCommandInput(async (line) => {
6652
+ const result = await applyOperatorCommand(context, { runId: input.runId, line });
6653
+ if (result.message)
6654
+ surface.info(result.message);
6655
+ if (result.action === "detach" || result.action === "stopped") {
6656
+ detached = true;
6657
+ commandInput?.close();
6658
+ }
6207
6659
  });
6208
6660
  }
6209
- let lastRendered = snapshot.rendered;
6210
6661
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6662
+ let timelineCursor = snapshot.timelineCursor;
6211
6663
  while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
6212
6664
  await Bun.sleep(pollMs);
6213
- snapshot = await readOperatorSnapshot(context, input.runId);
6214
- if (snapshot.rendered !== lastRendered) {
6215
- console.log(snapshot.rendered);
6216
- lastRendered = snapshot.rendered;
6217
- }
6665
+ snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6666
+ timelineCursor = snapshot.timelineCursor;
6667
+ surface.renderSnapshot(snapshot);
6668
+ surface.renderTimeline(snapshot.timeline);
6669
+ surface.renderLogs(snapshot.logs);
6218
6670
  }
6219
- rl?.close();
6671
+ commandInput?.close();
6220
6672
  }
6221
6673
  return { ...snapshot, steered, detached };
6222
6674
  }
6223
6675
 
6676
+ // packages/cli/src/commands/_cli-format.ts
6677
+ import pc3 from "picocolors";
6678
+ function stringField(record, key, fallback = "") {
6679
+ const value = record[key];
6680
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
6681
+ }
6682
+ function arrayField(record, key) {
6683
+ const value = record[key];
6684
+ return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
6685
+ }
6686
+ function rawObject(record) {
6687
+ const raw = record.raw;
6688
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
6689
+ }
6690
+ function truncate(value, width) {
6691
+ if (value.length <= width)
6692
+ return value;
6693
+ if (width <= 1)
6694
+ return "\u2026";
6695
+ return `${value.slice(0, width - 1)}\u2026`;
6696
+ }
6697
+ function pad(value, width) {
6698
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
6699
+ }
6700
+ function statusColor(status) {
6701
+ const normalized = status.toLowerCase();
6702
+ if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
6703
+ return pc3.green;
6704
+ if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
6705
+ return pc3.red;
6706
+ if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
6707
+ return pc3.cyan;
6708
+ if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
6709
+ return pc3.yellow;
6710
+ return pc3.dim;
6711
+ }
6712
+ function formatTaskList(tasks, options = {}) {
6713
+ if (tasks.length === 0)
6714
+ return pc3.dim("No matching tasks.");
6715
+ if (options.raw)
6716
+ return tasks.map((task) => JSON.stringify(task)).join(`
6717
+ `);
6718
+ const rows = tasks.map((task) => {
6719
+ const raw = rawObject(task);
6720
+ const id = stringField(task, "id", "<unknown>");
6721
+ const status = stringField(task, "status", "unknown");
6722
+ const title = stringField(task, "title", "Untitled task");
6723
+ const source = stringField(task, "source", stringField(raw, "source", ""));
6724
+ const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
6725
+ return { id, status, title, source, labels };
6726
+ });
6727
+ const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
6728
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6729
+ const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6730
+ const body = rows.map((row) => {
6731
+ const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
6732
+ const source = row.source ? pc3.dim(` ${row.source}`) : "";
6733
+ return [
6734
+ pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
6735
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6736
+ `${row.title}${labels}${source}`
6737
+ ].join(" ");
6738
+ });
6739
+ return [pc3.bold("Rig tasks"), header, ...body].join(`
6740
+ `);
6741
+ }
6742
+ function formatRunList(runs, options = {}) {
6743
+ if (runs.length === 0) {
6744
+ return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
6745
+ }
6746
+ const rows = runs.map((run) => {
6747
+ const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
6748
+ const status = stringField(run, "status", "unknown");
6749
+ const taskId2 = stringField(run, "taskId", "");
6750
+ const title = stringField(run, "title", taskId2 || "(untitled)");
6751
+ const runtime = stringField(run, "runtimeAdapter", "");
6752
+ return { runId, status, title, runtime };
6753
+ });
6754
+ const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
6755
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6756
+ const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6757
+ const body = rows.map((row) => [
6758
+ pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
6759
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6760
+ `${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
6761
+ ].join(" "));
6762
+ return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
6763
+ `);
6764
+ }
6765
+ function formatSubmittedRun(input) {
6766
+ const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
6767
+ if (input.task) {
6768
+ const id = stringField(input.task, "id", "<unknown>");
6769
+ const status = stringField(input.task, "status", "unknown");
6770
+ const title = stringField(input.task, "title", "Untitled task");
6771
+ lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
6772
+ }
6773
+ return lines.join(`
6774
+ `);
6775
+ }
6776
+
6777
+ // packages/cli/src/commands/_pi-session.ts
6778
+ import { spawn as spawn2 } from "child_process";
6779
+ function buildPiRigSessionEnv(input) {
6780
+ return {
6781
+ RIG_PROJECT_ROOT: input.projectRoot,
6782
+ PROJECT_RIG_ROOT: input.projectRoot,
6783
+ RIG_RUN_ID: input.runId,
6784
+ RIG_SERVER_RUN_ID: input.runId,
6785
+ RIG_RUNTIME_ADAPTER: "pi",
6786
+ RIG_SERVER_URL: input.serverUrl,
6787
+ RIG_SERVER_BASE_URL: input.serverUrl,
6788
+ RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6789
+ RIG_PI_OPERATOR_SESSION: "1",
6790
+ ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6791
+ ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6792
+ };
6793
+ }
6794
+ function shellBinary(name) {
6795
+ const explicit = process.env.RIG_PI_BINARY?.trim();
6796
+ if (explicit)
6797
+ return explicit;
6798
+ return Bun.which(name) || name;
6799
+ }
6800
+ function buildPiRigSessionCommand(input) {
6801
+ const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6802
+ const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6803
+ const initialCommand = `/rig attach ${input.runId}`;
6804
+ return [
6805
+ shellBinary("pi"),
6806
+ "--no-extensions",
6807
+ "--extension",
6808
+ extensionSource,
6809
+ initialCommand
6810
+ ];
6811
+ }
6812
+ async function launchPiRigSession(context, input) {
6813
+ if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6814
+ return { launched: false, exitCode: null, command: [] };
6815
+ }
6816
+ if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6817
+ return { launched: false, exitCode: null, command: [] };
6818
+ }
6819
+ const server = await ensureServerForCli(context.projectRoot);
6820
+ const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6821
+ const env = {
6822
+ ...process.env,
6823
+ ...buildPiRigSessionEnv({
6824
+ projectRoot: context.projectRoot,
6825
+ runId: input.runId,
6826
+ taskId: input.taskId,
6827
+ serverUrl: server.baseUrl,
6828
+ authToken: server.authToken
6829
+ })
6830
+ };
6831
+ process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6832
+ `);
6833
+ process.stdout.write(`Pi command: ${formatCommand(command)}
6834
+ `);
6835
+ const launchedAt = Date.now();
6836
+ const child = spawn2(command[0], command.slice(1), {
6837
+ cwd: context.projectRoot,
6838
+ env,
6839
+ stdio: "inherit"
6840
+ });
6841
+ const launchError = await new Promise((resolve20) => {
6842
+ child.once("error", (error) => {
6843
+ resolve20({ error: error.message });
6844
+ });
6845
+ child.once("close", (code) => resolve20({ code }));
6846
+ });
6847
+ if ("error" in launchError) {
6848
+ process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6849
+ `);
6850
+ return { launched: false, exitCode: null, command, error: launchError.error };
6851
+ }
6852
+ const exitCode = launchError.code;
6853
+ const elapsedMs = Date.now() - launchedAt;
6854
+ if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6855
+ const error = `Pi exited during startup with code ${exitCode}.`;
6856
+ process.stderr.write(`${error} Falling back to Rig attach view.
6857
+ `);
6858
+ return { launched: false, exitCode, command, error };
6859
+ }
6860
+ return { launched: true, exitCode, command };
6861
+ }
6862
+
6224
6863
  // packages/cli/src/commands/run.ts
6864
+ function normalizeRemoteRunDetails(payload) {
6865
+ const run = payload.run;
6866
+ if (!run || typeof run !== "object" || Array.isArray(run))
6867
+ return null;
6868
+ return {
6869
+ ...run,
6870
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6871
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6872
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6873
+ };
6874
+ }
6875
+ var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
6876
+ function isRemoteConnectionSelected(projectRoot) {
6877
+ return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
6878
+ }
6879
+ async function listRunsForSelectedConnection(context, options = {}) {
6880
+ if (isRemoteConnectionSelected(context.projectRoot)) {
6881
+ return { runs: await listRunsViaServer(context, options), source: "server" };
6882
+ }
6883
+ return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
6884
+ }
6885
+ function runStringField(run, key, fallback = "") {
6886
+ const value = run[key];
6887
+ return typeof value === "string" && value.trim() ? value : fallback;
6888
+ }
6889
+ function runDisplayTitle(run) {
6890
+ return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
6891
+ }
6892
+ function buildServerRunStatus(runs) {
6893
+ const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6894
+ const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6895
+ return { activeRuns, recentRuns, runs };
6896
+ }
6225
6897
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6226
6898
  if (noEpicPrompt) {
6227
6899
  return false;
@@ -6287,17 +6959,11 @@ async function executeRun(context, args) {
6287
6959
  switch (command) {
6288
6960
  case "list": {
6289
6961
  requireNoExtraArgs(rest, "bun run rig run list");
6290
- const runs = listAuthorityRuns3(context.projectRoot);
6962
+ const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
6291
6963
  if (context.outputMode === "text") {
6292
- if (runs.length === 0) {
6293
- console.log("No runs recorded in .rig/runs.");
6294
- } else {
6295
- for (const run of runs) {
6296
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
6297
- }
6298
- }
6964
+ console.log(formatRunList(runs, { source }));
6299
6965
  }
6300
- return { ok: true, group: "run", command, details: { runs } };
6966
+ return { ok: true, group: "run", command, details: { runs, source } };
6301
6967
  }
6302
6968
  case "delete": {
6303
6969
  let pending = rest;
@@ -6360,7 +7026,7 @@ async function executeRun(context, args) {
6360
7026
  if (!run.value) {
6361
7027
  throw new CliError2("run show requires --run <id>.");
6362
7028
  }
6363
- const record = readAuthorityRun4(context.projectRoot, run.value);
7029
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6364
7030
  if (!record) {
6365
7031
  throw new CliError2(`Run not found: ${run.value}`, 2);
6366
7032
  }
@@ -6379,34 +7045,24 @@ async function executeRun(context, args) {
6379
7045
  if (!run.value) {
6380
7046
  throw new CliError2("run timeline requires --run <id>.");
6381
7047
  }
6382
- const timelinePath = resolve20(resolveAuthorityRunDir5(context.projectRoot, run.value), "timeline.jsonl");
6383
- const printEvents = () => {
6384
- const events2 = readJsonlFile4(timelinePath);
6385
- if (context.outputMode === "text") {
6386
- for (const event of events2) {
6387
- console.log(JSON.stringify(event));
6388
- }
6389
- }
6390
- return events2;
6391
- };
6392
- const events = printEvents();
7048
+ const renderer = createPiRunStreamRenderer();
7049
+ let cursor = null;
7050
+ const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
7051
+ const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
7052
+ cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
7053
+ if (context.outputMode === "text") {
7054
+ renderer.renderTimeline(events);
7055
+ }
6393
7056
  if (follow.value && context.outputMode === "text") {
6394
- let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
6395
7057
  while (true) {
6396
7058
  await Bun.sleep(1000);
6397
- if (!existsSync12(timelinePath))
6398
- continue;
6399
- const next = readFileSync9(timelinePath, "utf8");
6400
- if (next.length <= lastLength)
6401
- continue;
6402
- const delta = next.slice(lastLength);
6403
- lastLength = next.length;
6404
- for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
6405
- console.log(line);
6406
- }
7059
+ const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
7060
+ const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
7061
+ cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
7062
+ renderer.renderTimeline(nextEvents);
6407
7063
  }
6408
7064
  }
6409
- return { ok: true, group: "run", command, details: { runId: run.value, events } };
7065
+ return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
6410
7066
  }
6411
7067
  case "attach": {
6412
7068
  let pending = rest;
@@ -6427,14 +7083,26 @@ async function executeRun(context, args) {
6427
7083
  if (!runId) {
6428
7084
  throw new CliError2("run attach requires a run id.", 2);
6429
7085
  }
7086
+ let steered = false;
7087
+ const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
7088
+ if (shouldTryPiAttach && messageOption.value?.trim()) {
7089
+ await steerRunViaServer(context, runId, messageOption.value.trim());
7090
+ steered = true;
7091
+ }
7092
+ if (shouldTryPiAttach) {
7093
+ const piSession = await launchPiRigSession(context, { runId });
7094
+ if (piSession.launched) {
7095
+ return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
7096
+ }
7097
+ }
6430
7098
  const attached = await attachRunOperatorView(context, {
6431
7099
  runId,
6432
- message: messageOption.value ?? null,
7100
+ message: shouldTryPiAttach ? null : messageOption.value ?? null,
6433
7101
  once: once.value,
6434
7102
  follow: follow.value,
6435
7103
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
6436
7104
  });
6437
- return { ok: true, group: "run", command, details: attached };
7105
+ return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
6438
7106
  }
6439
7107
  case "status": {
6440
7108
  requireNoExtraArgs(rest, "bun run rig run status");
@@ -6444,17 +7112,19 @@ async function executeRun(context, args) {
6444
7112
  }
6445
7113
  return { ok: true, group: "run", command };
6446
7114
  }
6447
- const summary = runStatus(context.projectRoot, runtimeContext);
7115
+ const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
7116
+ const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
7117
+ const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
6448
7118
  if (context.outputMode === "text") {
6449
- console.log(`Active runs: ${summary.activeRuns.length}`);
6450
- for (const run of summary.activeRuns) {
6451
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
7119
+ console.log(`Active runs: ${activeRuns.length}`);
7120
+ for (const run of activeRuns) {
7121
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6452
7122
  }
6453
- if (summary.recentRuns.length > 0) {
7123
+ if (recentRuns.length > 0) {
6454
7124
  console.log("");
6455
7125
  console.log("Recent runs:");
6456
- for (const run of summary.recentRuns) {
6457
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
7126
+ for (const run of recentRuns) {
7127
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6458
7128
  }
6459
7129
  }
6460
7130
  }
@@ -6541,6 +7211,20 @@ async function executeRun(context, args) {
6541
7211
  }
6542
7212
  return { ok: true, group: "run", command, details: resumed };
6543
7213
  }
7214
+ case "restart": {
7215
+ requireNoExtraArgs(rest, "bun run rig run restart");
7216
+ if (context.dryRun) {
7217
+ if (context.outputMode === "text") {
7218
+ console.log("[dry-run] rig run restart");
7219
+ }
7220
+ return { ok: true, group: "run", command };
7221
+ }
7222
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
7223
+ if (context.outputMode === "text") {
7224
+ console.log(`Restarted run: ${restarted.runId}`);
7225
+ }
7226
+ return { ok: true, group: "run", command, details: restarted };
7227
+ }
6544
7228
  case "stop": {
6545
7229
  const runOption = takeOption(rest, "--run");
6546
7230
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -6598,7 +7282,7 @@ async function executeServer(context, args, options) {
6598
7282
  const authTokenResult = takeOption(pending, "--auth-token");
6599
7283
  pending = authTokenResult.rest;
6600
7284
  requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
6601
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "start"];
7285
+ const commandParts = ["rig-server", "start"];
6602
7286
  if (hostResult.value) {
6603
7287
  commandParts.push("--host", hostResult.value);
6604
7288
  }
@@ -6621,7 +7305,7 @@ async function executeServer(context, args, options) {
6621
7305
  const eventResult = takeOption(pending, "--event");
6622
7306
  pending = eventResult.rest;
6623
7307
  requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
6624
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "notify-test"];
7308
+ const commandParts = ["rig-server", "notify-test"];
6625
7309
  if (eventResult.value) {
6626
7310
  commandParts.push("--event", eventResult.value);
6627
7311
  }
@@ -6682,10 +7366,10 @@ async function executeServer(context, args, options) {
6682
7366
  }
6683
7367
 
6684
7368
  // packages/cli/src/commands/task.ts
6685
- import { readFileSync as readFileSync10 } from "fs";
6686
- import { spawnSync as spawnSync4 } from "child_process";
6687
- import { createInterface as createInterface4 } from "readline/promises";
6688
- import { resolve as resolve21 } from "path";
7369
+ import { readFileSync as readFileSync9 } from "fs";
7370
+ import { spawnSync as spawnSync3 } from "child_process";
7371
+ import { resolve as resolve20 } from "path";
7372
+ import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
6689
7373
  import {
6690
7374
  taskArtifactDir,
6691
7375
  taskArtifacts,
@@ -6703,19 +7387,10 @@ import {
6703
7387
  } from "@rig/runtime/control-plane/native/task-ops";
6704
7388
 
6705
7389
  // packages/cli/src/commands/_task-picker.ts
6706
- import { createInterface as createInterface3 } from "readline/promises";
6707
- function taskId(task) {
7390
+ import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
7391
+ function taskId2(task) {
6708
7392
  return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6709
7393
  }
6710
- function taskTitle(task) {
6711
- return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6712
- }
6713
- function taskStatus(task) {
6714
- return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6715
- }
6716
- function renderTaskPickerRows(tasks) {
6717
- return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6718
- }
6719
7394
  async function selectTaskWithTextPicker(tasks, io = {}) {
6720
7395
  if (tasks.length === 0)
6721
7396
  return null;
@@ -6725,25 +7400,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
6725
7400
  if (!isTty) {
6726
7401
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
6727
7402
  }
6728
- const prompt = io.prompt ?? (async (question) => {
6729
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6730
- try {
6731
- return await rl.question(question);
6732
- } finally {
6733
- rl.close();
7403
+ if (io.prompt || io.renderer) {
7404
+ const prompt = io.prompt ?? promptForTaskSelection;
7405
+ const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
7406
+ `) };
7407
+ renderer.writeLine("Select Rig task:");
7408
+ for (const row of renderTaskPickerRows(tasks))
7409
+ renderer.writeLine(` ${row}`);
7410
+ const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
7411
+ if (!answer2)
7412
+ return null;
7413
+ if (/^\d+$/.test(answer2)) {
7414
+ const index2 = Number.parseInt(answer2, 10) - 1;
7415
+ return tasks[index2] ?? null;
6734
7416
  }
7417
+ return tasks.find((task) => taskId2(task) === answer2) ?? null;
7418
+ }
7419
+ const options = tasks.map((task, index2) => ({
7420
+ value: `${index2}`,
7421
+ label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
7422
+ hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
7423
+ }));
7424
+ const answer = await select2({
7425
+ message: "Select Rig task",
7426
+ options
6735
7427
  });
6736
- console.log("Select Rig task:");
6737
- for (const row of renderTaskPickerRows(tasks))
6738
- console.log(` ${row}`);
6739
- const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
6740
- if (!answer)
7428
+ if (isCancel2(answer)) {
7429
+ cancel2("No task selected.");
6741
7430
  return null;
6742
- if (/^\d+$/.test(answer)) {
6743
- const index = Number.parseInt(answer, 10) - 1;
6744
- return tasks[index] ?? null;
6745
7431
  }
6746
- return tasks.find((task) => taskId(task) === answer) ?? null;
7432
+ const index = Number.parseInt(String(answer), 10);
7433
+ return Number.isFinite(index) ? tasks[index] ?? null : null;
6747
7434
  }
6748
7435
 
6749
7436
  // packages/cli/src/commands/task.ts
@@ -6821,7 +7508,7 @@ function normalizePrMode(value) {
6821
7508
  throw new CliError2("--pr must be auto, ask, or off.", 2);
6822
7509
  }
6823
7510
  function detectLocalDirtyState(projectRoot) {
6824
- const result = spawnSync4("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
7511
+ const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
6825
7512
  if (result.status !== 0)
6826
7513
  return { dirty: false, modified: 0, untracked: 0, lines: [] };
6827
7514
  const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -6855,13 +7542,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
6855
7542
  if (explicit)
6856
7543
  return { mode: explicit, state };
6857
7544
  if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
6858
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
6859
- try {
6860
- const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
6861
- return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
6862
- } finally {
6863
- rl.close();
7545
+ const answer = await confirm2({
7546
+ message: "Include current uncommitted changes in run baseline?",
7547
+ initialValue: false
7548
+ });
7549
+ if (isCancel3(answer)) {
7550
+ cancel3("Run cancelled.");
7551
+ throw new CliError2("Run cancelled by user.", 1);
6864
7552
  }
7553
+ return { mode: answer ? "dirty-snapshot" : "head", state };
6865
7554
  }
6866
7555
  return { mode: "head", state };
6867
7556
  }
@@ -6896,10 +7585,7 @@ function summarizeTask(task, options = {}) {
6896
7585
  };
6897
7586
  }
6898
7587
  function printTaskSummary(task) {
6899
- const id = readTaskId(task) ?? "<unknown>";
6900
- const title = readTaskString(task, "title") ?? "Untitled task";
6901
- const status = readTaskString(task, "status") ?? "unknown";
6902
- console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
7588
+ console.log(formatTaskList([task]));
6903
7589
  }
6904
7590
  async function validatorRegistryForTaskCommands(projectRoot) {
6905
7591
  return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
@@ -6917,16 +7603,8 @@ async function executeTask(context, args, options) {
6917
7603
  requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
6918
7604
  const tasks = await listWorkspaceTasksViaServer(context, filters);
6919
7605
  if (context.outputMode === "text") {
6920
- if (tasks.length === 0) {
6921
- console.log("No matching tasks.");
6922
- } else {
6923
- for (const task of tasks) {
6924
- if (rawResult.value)
6925
- console.log(JSON.stringify(summarizeTask(task, { raw: true })));
6926
- else
6927
- printTaskSummary(task);
6928
- }
6929
- }
7606
+ const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
7607
+ console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
6930
7608
  }
6931
7609
  return {
6932
7610
  ok: true,
@@ -6940,12 +7618,12 @@ async function executeTask(context, args, options) {
6940
7618
  const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
6941
7619
  const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
6942
7620
  requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
6943
- const taskId2 = normalizeTaskRunTaskId(taskOption.value ?? positional);
6944
- if (!taskId2)
7621
+ const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
7622
+ if (!taskId3)
6945
7623
  throw new CliError2("task show requires a task id.", 2);
6946
- const task = await getWorkspaceTaskViaServer(context, taskId2);
7624
+ const task = await getWorkspaceTaskViaServer(context, taskId3);
6947
7625
  if (!task)
6948
- throw new CliError2(`Task not found: ${taskId2}`, 3);
7626
+ throw new CliError2(`Task not found: ${taskId3}`, 3);
6949
7627
  const summary = summarizeTask(task, { raw: true });
6950
7628
  if (context.outputMode === "text")
6951
7629
  console.log(JSON.stringify(summary, null, 2));
@@ -7016,7 +7694,7 @@ async function executeTask(context, args, options) {
7016
7694
  const fileFlag = takeOption(rest.slice(1), "--file");
7017
7695
  let content;
7018
7696
  if (fileFlag.value) {
7019
- content = readFileSync10(resolve21(context.projectRoot, fileFlag.value), "utf-8");
7697
+ content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
7020
7698
  } else {
7021
7699
  content = await readStdin();
7022
7700
  }
@@ -7145,16 +7823,23 @@ async function executeTask(context, args, options) {
7145
7823
  });
7146
7824
  let attachDetails = null;
7147
7825
  if (!detachResult.value && context.outputMode === "text") {
7148
- console.log(`Run submitted: ${submitted.runId}`);
7149
- if (selectedTask) {
7150
- printTaskSummary(selectedTask);
7826
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7827
+ if (runtimeAdapter === "pi") {
7828
+ const piSession = await launchPiRigSession(context, {
7829
+ runId: submitted.runId,
7830
+ taskId: selectedTaskId,
7831
+ title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7832
+ runtimeAdapter
7833
+ });
7834
+ attachDetails = { mode: "pi", ...piSession };
7835
+ if (!piSession.launched) {
7836
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7837
+ }
7838
+ } else {
7839
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7151
7840
  }
7152
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7153
7841
  } else if (context.outputMode === "text") {
7154
- console.log(`Run submitted: ${submitted.runId}`);
7155
- if (selectedTask) {
7156
- printTaskSummary(selectedTask);
7157
- }
7842
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7158
7843
  }
7159
7844
  return {
7160
7845
  ok: true,
@@ -7237,9 +7922,9 @@ async function executeTask(context, args, options) {
7237
7922
  }
7238
7923
 
7239
7924
  // packages/cli/src/commands/task-run-driver.ts
7240
- import { copyFileSync as copyFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7241
- import { resolve as resolve22 } from "path";
7242
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
7925
+ import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7926
+ import { resolve as resolve21 } from "path";
7927
+ import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
7243
7928
  import { createInterface as createLineInterface } from "readline";
7244
7929
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7245
7930
  import {
@@ -7264,15 +7949,31 @@ import {
7264
7949
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
7265
7950
  import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
7266
7951
  import {
7267
- buildTaskRunLifecycleComment,
7268
- updateConfiguredTaskSourceTask
7952
+ buildTaskRunLifecycleComment
7269
7953
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
7270
7954
  import {
7271
7955
  closeIssueAfterMergedPr,
7272
7956
  commitRunChanges,
7273
7957
  runPrAutomation
7274
7958
  } from "@rig/runtime/control-plane/native/pr-automation";
7959
+ function looksLikeGitHubToken(value) {
7960
+ const token = value?.trim();
7961
+ if (!token)
7962
+ return false;
7963
+ return /^(gh[opusr]_|github_pat_)/.test(token);
7964
+ }
7965
+ function githubBridgeEnv(token) {
7966
+ const clean = token?.trim();
7967
+ if (!clean)
7968
+ return {};
7969
+ return {
7970
+ RIG_GITHUB_TOKEN: clean,
7971
+ GITHUB_TOKEN: clean,
7972
+ GH_TOKEN: clean
7973
+ };
7974
+ }
7275
7975
  function buildPiRigBridgeEnv(input) {
7976
+ const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
7276
7977
  return {
7277
7978
  RIG_PROJECT_ROOT: input.projectRoot,
7278
7979
  PROJECT_RIG_ROOT: input.projectRoot,
@@ -7282,11 +7983,11 @@ function buildPiRigBridgeEnv(input) {
7282
7983
  RIG_RUNTIME_ADAPTER: "pi",
7283
7984
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7284
7985
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7285
- ...input.githubToken ? { RIG_GITHUB_TOKEN: input.githubToken } : {}
7986
+ ...githubBridgeEnv(githubToken)
7286
7987
  };
7287
7988
  }
7288
7989
  function runGitSync(cwd, args, input) {
7289
- const result = spawnSync5("git", [...args], {
7990
+ const result = spawnSync4("git", [...args], {
7290
7991
  cwd,
7291
7992
  input,
7292
7993
  encoding: "utf8",
@@ -7304,12 +8005,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
7304
8005
  return 0;
7305
8006
  let copied = 0;
7306
8007
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
7307
- const sourcePath = resolve22(sourceRoot, relativePath);
7308
- const targetPath = resolve22(targetRoot, relativePath);
8008
+ const sourcePath = resolve21(sourceRoot, relativePath);
8009
+ const targetPath = resolve21(targetRoot, relativePath);
7309
8010
  try {
7310
8011
  if (!statSync2(sourcePath).isFile())
7311
8012
  continue;
7312
- mkdirSync8(resolve22(targetPath, ".."), { recursive: true });
8013
+ mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
7313
8014
  copyFileSync3(sourcePath, targetPath);
7314
8015
  copied += 1;
7315
8016
  } catch {}
@@ -7348,7 +8049,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
7348
8049
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
7349
8050
  return {
7350
8051
  RIG_BASELINE_MODE: "dirty-snapshot",
7351
- RIG_DIRTY_BASELINE_READY_FILE: resolve22(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
8052
+ RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7352
8053
  };
7353
8054
  }
7354
8055
  function positiveInt(value, fallback) {
@@ -7356,7 +8057,7 @@ function positiveInt(value, fallback) {
7356
8057
  }
7357
8058
  function resolveTaskRunAutomationLimits(config, env = process.env) {
7358
8059
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
7359
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
8060
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
7360
8061
  return {
7361
8062
  maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
7362
8063
  maxPrFixIterations: configuredPrFixIterations
@@ -7451,7 +8152,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
7451
8152
  }
7452
8153
  function createCommandRunner(binary) {
7453
8154
  return async (args, options) => {
7454
- const child = spawn2(binary, [...args], {
8155
+ const child = spawn3(binary, [...args], {
7455
8156
  cwd: options?.cwd,
7456
8157
  stdio: ["ignore", "pipe", "pipe"]
7457
8158
  });
@@ -7459,9 +8160,9 @@ function createCommandRunner(binary) {
7459
8160
  const stderrChunks = [];
7460
8161
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7461
8162
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7462
- return await new Promise((resolve23) => {
7463
- child.once("error", (error) => resolve23({ exitCode: 1, stderr: error.message }));
7464
- child.once("close", (code) => resolve23({
8163
+ return await new Promise((resolve22) => {
8164
+ child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
8165
+ child.once("close", (code) => resolve22({
7465
8166
  exitCode: code ?? 1,
7466
8167
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
7467
8168
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -7481,12 +8182,13 @@ async function resolvePostValidationBranch(input) {
7481
8182
  return input.configuredBranch;
7482
8183
  }
7483
8184
  async function runTaskRunPostValidationLifecycle(input) {
7484
- const taskId2 = input.taskId?.trim();
7485
- if (!taskId2) {
8185
+ const taskId3 = input.taskId?.trim();
8186
+ if (!taskId3) {
7486
8187
  return { status: "skipped" };
7487
8188
  }
7488
- const config = input.config ?? {};
7489
- const prMode = config.pr?.mode ?? "auto";
8189
+ const configInput = input.config ?? null;
8190
+ const config = configInput ?? {};
8191
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
7490
8192
  if (prMode === "off" || prMode === "ask") {
7491
8193
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
7492
8194
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -7502,7 +8204,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7502
8204
  gitCommand
7503
8205
  });
7504
8206
  const prAutomation = input.prAutomation ?? runPrAutomation;
7505
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
8207
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
7506
8208
  const stage = input.appendStage ?? (() => {
7507
8209
  return;
7508
8210
  });
@@ -7520,7 +8222,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7520
8222
  stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
7521
8223
  const commit = await commitRunChanges({
7522
8224
  cwd: workspace,
7523
- message: `rig: complete task ${taskId2}`,
8225
+ message: `rig: complete task ${taskId3}`,
7524
8226
  command: gitCommand
7525
8227
  });
7526
8228
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
@@ -7528,13 +8230,15 @@ async function runTaskRunPostValidationLifecycle(input) {
7528
8230
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
7529
8231
  const pr = await prAutomation({
7530
8232
  projectRoot: workspace,
7531
- taskId: taskId2,
8233
+ taskId: taskId3,
7532
8234
  runId: input.runId,
7533
8235
  branch,
7534
8236
  config,
7535
8237
  sourceTask: input.sourceTask,
7536
8238
  uploadedSnapshot: input.uploadedSnapshot,
8239
+ artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
7537
8240
  command: ghCommand,
8241
+ gitCommand,
7538
8242
  steerPi,
7539
8243
  lifecycle: {
7540
8244
  onPrOpened: async ({ prUrl }) => {
@@ -7542,7 +8246,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7542
8246
  try {
7543
8247
  if (shouldWriteDriverIssueUpdate(config, "under_review")) {
7544
8248
  await updateTaskSource(input.projectRoot, {
7545
- taskId: taskId2,
8249
+ taskId: taskId3,
7546
8250
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7547
8251
  update: {
7548
8252
  status: "under_review",
@@ -7572,7 +8276,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7572
8276
  `), "reviewing", "error");
7573
8277
  if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
7574
8278
  await updateTaskSource(input.projectRoot, {
7575
- taskId: taskId2,
8279
+ taskId: taskId3,
7576
8280
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7577
8281
  update: {
7578
8282
  status: "ci_fixing",
@@ -7583,8 +8287,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7583
8287
  runtimeWorkspace: input.runtimeWorkspace ?? null
7584
8288
  })
7585
8289
  }
7586
- }).catch(() => {
7587
- return;
7588
8290
  });
7589
8291
  }
7590
8292
  },
@@ -7592,7 +8294,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7592
8294
  stage("Merge", prUrl, "running", "tool");
7593
8295
  if (shouldWriteDriverIssueUpdate(config, "merging")) {
7594
8296
  await updateTaskSource(input.projectRoot, {
7595
- taskId: taskId2,
8297
+ taskId: taskId3,
7596
8298
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7597
8299
  update: {
7598
8300
  status: "merging",
@@ -7603,8 +8305,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7603
8305
  runtimeWorkspace: input.runtimeWorkspace ?? null
7604
8306
  })
7605
8307
  }
7606
- }).catch(() => {
7607
- return;
7608
8308
  });
7609
8309
  }
7610
8310
  },
@@ -7621,7 +8321,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7621
8321
  stage("Needs attention", detail, "needs_attention", "error");
7622
8322
  if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
7623
8323
  await updateTaskSource(input.projectRoot, {
7624
- taskId: taskId2,
8324
+ taskId: taskId3,
7625
8325
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7626
8326
  update: {
7627
8327
  status: "needs_attention",
@@ -7633,8 +8333,17 @@ async function runTaskRunPostValidationLifecycle(input) {
7633
8333
  errorText: detail
7634
8334
  })
7635
8335
  }
7636
- }).catch(() => {
7637
- return;
8336
+ }).catch((error) => {
8337
+ try {
8338
+ appendRunLog(input.projectRoot, input.runId, {
8339
+ id: `log:${input.runId}:task-source-needs-attention-update`,
8340
+ title: "Task source needs-attention update failed",
8341
+ detail: error instanceof Error ? error.message : String(error),
8342
+ tone: "error",
8343
+ status: "needs_attention",
8344
+ createdAt: new Date().toISOString()
8345
+ });
8346
+ } catch {}
7638
8347
  });
7639
8348
  }
7640
8349
  return { status: "needs_attention", pr };
@@ -7642,7 +8351,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7642
8351
  if (shouldWriteDriverIssueUpdate(config, "closed")) {
7643
8352
  await closeIssueAfterMergedPr({
7644
8353
  projectRoot: input.projectRoot,
7645
- taskId: taskId2,
8354
+ taskId: taskId3,
7646
8355
  runId: input.runId,
7647
8356
  prUrl: pr.prUrl,
7648
8357
  sourceTask: input.sourceTask,
@@ -7652,12 +8361,12 @@ async function runTaskRunPostValidationLifecycle(input) {
7652
8361
  stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
7653
8362
  return { status: "completed", pr };
7654
8363
  }
7655
- function summarizeValidationFailure(projectRoot, taskId2) {
7656
- if (!taskId2) {
8364
+ function summarizeValidationFailure(projectRoot, taskId3) {
8365
+ if (!taskId3) {
7657
8366
  return null;
7658
8367
  }
7659
- for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId2)) {
7660
- const summary = readJsonFile3(resolve22(artifactDir, "validation-summary.json"), null);
8368
+ for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
8369
+ const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
7661
8370
  if (!summary || summary.status !== "fail") {
7662
8371
  continue;
7663
8372
  }
@@ -7738,9 +8447,9 @@ function readTaskRunAcceptedArtifactState(input) {
7738
8447
  if (!input.taskId || !input.workspaceDir) {
7739
8448
  return { accepted: false, reason: null };
7740
8449
  }
7741
- const artifactDir = resolve22(input.workspaceDir, "artifacts", input.taskId);
7742
- const reviewStatusPath = resolve22(artifactDir, "review-status.txt");
7743
- const taskResultPath = resolve22(artifactDir, "task-result.json");
8450
+ const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
8451
+ const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
8452
+ const taskResultPath = resolve21(artifactDir, "task-result.json");
7744
8453
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7745
8454
  if (reviewStatus !== "APPROVED") {
7746
8455
  return { accepted: false, reason: null };
@@ -7777,12 +8486,12 @@ function resolveTaskRunRetryContext(input) {
7777
8486
  if (!input.taskId || !input.workspaceDir) {
7778
8487
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
7779
8488
  }
7780
- const artifactDir = resolve22(input.workspaceDir, "artifacts", input.taskId);
7781
- const reviewStatePath = resolve22(artifactDir, "review-state.json");
7782
- const reviewFeedbackPath = resolve22(artifactDir, "review-feedback.md");
7783
- const reviewStatusPath = resolve22(artifactDir, "review-status.txt");
7784
- const failedApproachesPath = resolve22(input.workspaceDir, ".rig", "state", "failed_approaches.md");
7785
- const validationSummaryPath = resolve22(artifactDir, "validation-summary.json");
8489
+ const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
8490
+ const reviewStatePath = resolve21(artifactDir, "review-state.json");
8491
+ const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
8492
+ const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
8493
+ const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
8494
+ const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
7786
8495
  const reviewState = readJsonFile3(reviewStatePath, null);
7787
8496
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7788
8497
  const reviewRejected = isTaskRunReviewRejected(reviewState);
@@ -7836,12 +8545,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
7836
8545
  }
7837
8546
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
7838
8547
  }
8548
+ function appendAssistantTimelineFromRecord(input) {
8549
+ let nextAssistantText = input.assistantText;
8550
+ if (input.record.type === "message_update") {
8551
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
8552
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
8553
+ nextAssistantText += assistantMessageEvent.delta;
8554
+ }
8555
+ } else if (input.record.type === "stream_event") {
8556
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
8557
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
8558
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
8559
+ nextAssistantText += delta.text;
8560
+ }
8561
+ } else if (input.record.type === "assistant") {
8562
+ const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
8563
+ const content = Array.isArray(message2.content) ? message2.content : [];
8564
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
8565
+ if (fullText.length > nextAssistantText.length)
8566
+ nextAssistantText = fullText;
8567
+ }
8568
+ if (nextAssistantText !== input.assistantText) {
8569
+ appendRunTimeline(input.projectRoot, input.runId, {
8570
+ id: input.messageId,
8571
+ type: "assistant_message",
8572
+ text: nextAssistantText,
8573
+ state: "streaming",
8574
+ createdAt: new Date().toISOString()
8575
+ });
8576
+ }
8577
+ return nextAssistantText;
8578
+ }
8579
+ function appendPiToolTimelineFromRecord(input) {
8580
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8581
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
8582
+ return false;
8583
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
8584
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
8585
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
8586
+ appendRunTimeline(input.projectRoot, input.runId, {
8587
+ id: `tool:${toolCallId}:${type}`,
8588
+ type,
8589
+ toolName,
8590
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
8591
+ createdAt: new Date().toISOString()
8592
+ });
8593
+ return true;
8594
+ }
8595
+ function isNonRenderablePiProtocolRecord(record) {
8596
+ const type = typeof record.type === "string" ? record.type : "";
8597
+ 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");
8598
+ }
8599
+ function appendToolTimelineFromLog(input) {
8600
+ const title = typeof input.log.title === "string" ? input.log.title : "";
8601
+ if (title !== "Tool activity")
8602
+ return;
8603
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
8604
+ 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;
8605
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
8606
+ appendRunTimeline(input.projectRoot, input.runId, {
8607
+ id: `tool:${logId}`,
8608
+ type: "tool_execution_update",
8609
+ toolName,
8610
+ status: typeof input.log.status === "string" ? input.log.status : "running",
8611
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
8612
+ payload,
8613
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
8614
+ });
8615
+ }
7839
8616
  function readTaskRunReviewStatus(reviewStatusPath) {
7840
- if (!existsSync13(reviewStatusPath)) {
8617
+ if (!existsSync12(reviewStatusPath)) {
7841
8618
  return null;
7842
8619
  }
7843
8620
  try {
7844
- const status = readFileSync11(reviewStatusPath, "utf8").trim().toUpperCase();
8621
+ const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
7845
8622
  return status === "APPROVED" || status === "REJECTED" ? status : null;
7846
8623
  } catch {
7847
8624
  return null;
@@ -7859,14 +8636,45 @@ function isTaskRunReviewRejected(reviewState) {
7859
8636
  function runSourceTaskIdentity(sourceTask) {
7860
8637
  return sourceTask;
7861
8638
  }
7862
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
7863
- if (!taskId2)
8639
+ function sourceTaskIssueNodeId(sourceTask) {
8640
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
8641
+ return null;
8642
+ const record = sourceTask;
8643
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
8644
+ if (direct?.trim())
8645
+ return direct.trim();
8646
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
8647
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
8648
+ }
8649
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
8650
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
8651
+ id: input.taskId,
8652
+ ...input.update.status ? { status: input.update.status } : {},
8653
+ ...input.update.comment ? { comment: input.update.comment } : {},
8654
+ ...input.update.title ? { title: input.update.title } : {},
8655
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
8656
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
8657
+ });
8658
+ if (serverResult.ok === false) {
8659
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
8660
+ }
8661
+ return {
8662
+ updated: serverResult.ok !== false,
8663
+ taskId: input.taskId,
8664
+ status: input.update.status,
8665
+ source: "server",
8666
+ sourceKind: "server",
8667
+ projectSync: serverResult.projectSync
8668
+ };
8669
+ };
8670
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
8671
+ if (!taskId3)
7864
8672
  return;
7865
8673
  const config = await loadTaskRunAutomationConfig(projectRoot);
7866
8674
  if (!shouldWriteDriverIssueUpdate(config, status))
7867
8675
  return;
7868
8676
  const result = await updateTaskSource(projectRoot, {
7869
- taskId: taskId2,
8677
+ taskId: taskId3,
7870
8678
  sourceTask: runSourceTaskIdentity(sourceTask),
7871
8679
  update: {
7872
8680
  status,
@@ -7885,14 +8693,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
7885
8693
  throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
7886
8694
  }
7887
8695
  }
7888
- function readRunSourceTaskContract(projectRoot, runId, taskId2) {
8696
+ function readRunSourceTaskContract(projectRoot, runId, taskId3) {
7889
8697
  const run = readAuthorityRun5(projectRoot, runId);
7890
8698
  const raw = run?.sourceTask;
7891
8699
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7892
8700
  return null;
7893
8701
  }
7894
8702
  const record = raw;
7895
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId2?.trim();
8703
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
7896
8704
  if (!id) {
7897
8705
  return null;
7898
8706
  }
@@ -7924,6 +8732,9 @@ function stringArrayField(record, key) {
7924
8732
  }
7925
8733
  async function executeRigOwnedTaskRun(context, input) {
7926
8734
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
8735
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
8736
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
8737
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7927
8738
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7928
8739
  let prompt = buildRunPrompt({
7929
8740
  projectRoot: context.projectRoot,
@@ -7979,14 +8790,14 @@ async function executeRigOwnedTaskRun(context, input) {
7979
8790
  taskId: runtimeTaskId,
7980
8791
  createdAt: startedAt,
7981
8792
  runtimeAdapter: input.runtimeAdapter,
7982
- status: "created"
8793
+ status: resumeMode ? "preparing" : "created"
7983
8794
  });
7984
8795
  patchAuthorityRun(context.projectRoot, input.runId, {
7985
8796
  status: "preparing",
7986
8797
  startedAt,
7987
8798
  completedAt: null,
7988
8799
  errorText: null,
7989
- artifactRoot: null,
8800
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
7990
8801
  runtimeAdapter: input.runtimeAdapter,
7991
8802
  runtimeMode: input.runtimeMode,
7992
8803
  interactionMode: input.interactionMode,
@@ -8002,9 +8813,9 @@ async function executeRigOwnedTaskRun(context, input) {
8002
8813
  detail: input.taskId ?? input.title ?? runtimeTaskId
8003
8814
  });
8004
8815
  appendRunLog(context.projectRoot, input.runId, {
8005
- id: `log:${input.runId}:start`,
8006
- title: "Rig task run started",
8007
- detail: input.taskId ?? input.title ?? runtimeTaskId,
8816
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
8817
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
8818
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
8008
8819
  tone: "info",
8009
8820
  status: "preparing",
8010
8821
  createdAt: startedAt
@@ -8025,15 +8836,15 @@ async function executeRigOwnedTaskRun(context, input) {
8025
8836
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
8026
8837
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
8027
8838
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
8028
- const planningArtifactPath = resolve22("artifacts", runtimeTaskId, "implementation-plan.md");
8839
+ const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
8029
8840
  const persistedPlanning = {
8030
8841
  ...planningClassification,
8031
8842
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
8032
8843
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
8033
8844
  classifiedAt: new Date().toISOString()
8034
8845
  };
8035
- mkdirSync8(resolve22(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8036
- writeFileSync6(resolve22(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8846
+ mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8847
+ writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8037
8848
  `, "utf8");
8038
8849
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
8039
8850
  prompt = `${prompt}
@@ -8082,11 +8893,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8082
8893
  let reviewAction;
8083
8894
  let verificationStarted = false;
8084
8895
  let reviewStarted = false;
8085
- let latestRuntimeWorkspace = null;
8086
- let latestSessionDir = null;
8087
- let latestLogsDir = null;
8896
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
8897
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
8898
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
8088
8899
  let latestProviderCommand = null;
8089
- let latestRuntimeBranch = null;
8900
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8090
8901
  let snapshotSidecarPromise = null;
8091
8902
  let dirtyBaselineApplied = false;
8092
8903
  const childEnv = {
@@ -8104,7 +8915,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8104
8915
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
8105
8916
  RIG_SERVER_RUN_ID: input.runId
8106
8917
  },
8107
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
8918
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
8919
+ ...resumeMode ? {
8920
+ RIG_RUN_RESUME: "1",
8921
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
8922
+ } : {}
8108
8923
  };
8109
8924
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
8110
8925
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -8162,10 +8977,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8162
8977
  patchAuthorityRun(context.projectRoot, input.runId, {
8163
8978
  status: "running",
8164
8979
  worktreePath: latestRuntimeWorkspace,
8165
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve22(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8980
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8166
8981
  logRoot: latestLogsDir,
8167
- sessionPath: latestSessionDir ? resolve22(latestSessionDir, "session.json") : null,
8168
- sessionLogPath: latestLogsDir ? resolve22(latestLogsDir, "agent-stdout.log") : null,
8982
+ sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
8983
+ sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
8169
8984
  branch: runtimeId
8170
8985
  });
8171
8986
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -8173,7 +8988,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8173
8988
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
8174
8989
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
8175
8990
  if (readyFile) {
8176
- mkdirSync8(resolve22(readyFile, ".."), { recursive: true });
8991
+ mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
8177
8992
  writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
8178
8993
  `, "utf8");
8179
8994
  }
@@ -8306,6 +9121,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8306
9121
  try {
8307
9122
  const record = JSON.parse(trimmed);
8308
9123
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9124
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
9125
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9126
+ return;
9127
+ }
8309
9128
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
8310
9129
  runId: input.runId,
8311
9130
  record,
@@ -8322,7 +9141,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8322
9141
  if (providerLogs.length > 0) {
8323
9142
  for (const providerLog of providerLogs) {
8324
9143
  appendRunLog(context.projectRoot, input.runId, providerLog);
9144
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
8325
9145
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9146
+ if (providerLog.title === "Tool activity")
9147
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8326
9148
  }
8327
9149
  }
8328
9150
  if (input.runtimeAdapter === "codex") {
@@ -8374,6 +9196,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8374
9196
  return;
8375
9197
  }
8376
9198
  }
9199
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
9200
+ return;
9201
+ }
8377
9202
  if (record.type === "assistant") {
8378
9203
  const message2 = record.message && typeof record.message === "object" ? record.message : record;
8379
9204
  const content = Array.isArray(message2.content) ? message2.content : [];
@@ -8410,9 +9235,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8410
9235
  let reviewFailureDetail = null;
8411
9236
  const stderrLines = [];
8412
9237
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8413
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
9238
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
9239
+ appendRunLog(context.projectRoot, input.runId, {
9240
+ id: `log:${input.runId}:resume-closeout-phase`,
9241
+ title: "Resume continuing from closeout phase",
9242
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
9243
+ tone: "info",
9244
+ status: resumePreviousStatus,
9245
+ createdAt: new Date().toISOString()
9246
+ });
9247
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
9248
+ exit = { code: 0, signal: null };
9249
+ } else if (resumeMode && latestRuntimeWorkspace) {
9250
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
9251
+ taskId: input.taskId ?? runtimeTaskId,
9252
+ workspaceDir: latestRuntimeWorkspace
9253
+ });
9254
+ if (acceptedArtifactState.accepted) {
9255
+ appendRunLog(context.projectRoot, input.runId, {
9256
+ id: `log:${input.runId}:resume-accepted-artifacts`,
9257
+ title: "Resume found accepted artifacts; continuing closeout",
9258
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
9259
+ tone: "info",
9260
+ status: "validating",
9261
+ createdAt: new Date().toISOString()
9262
+ });
9263
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
9264
+ exit = { code: 0, signal: null };
9265
+ }
9266
+ }
9267
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8414
9268
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8415
- const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9269
+ const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8416
9270
  cwd: context.projectRoot,
8417
9271
  env: childEnv,
8418
9272
  stdio: ["pipe", "pipe", "pipe"]
@@ -8451,7 +9305,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8451
9305
  let acceptedArtifactObservedAt = null;
8452
9306
  let acceptedArtifactPollTimer = null;
8453
9307
  let acceptedArtifactKillTimer = null;
8454
- const attemptExit = await new Promise((resolve23) => {
9308
+ const attemptExit = await new Promise((resolve22) => {
8455
9309
  let settled = false;
8456
9310
  const settle = (result) => {
8457
9311
  if (settled)
@@ -8459,7 +9313,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8459
9313
  settled = true;
8460
9314
  if (acceptedArtifactPollTimer)
8461
9315
  clearInterval(acceptedArtifactPollTimer);
8462
- resolve23(result);
9316
+ resolve22(result);
8463
9317
  };
8464
9318
  const pollAcceptedArtifacts = () => {
8465
9319
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -8532,7 +9386,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8532
9386
  });
8533
9387
  for (const pendingLog of pendingLogs) {
8534
9388
  appendRunLog(context.projectRoot, input.runId, pendingLog);
9389
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
8535
9390
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9391
+ if (pendingLog.title === "Tool activity")
9392
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8536
9393
  }
8537
9394
  process.off("SIGTERM", forwardSigterm);
8538
9395
  if (attemptExit.error) {
@@ -8658,8 +9515,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8658
9515
  }
8659
9516
  if (planningClassification.planningRequired) {
8660
9517
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
8661
- const expectedPlanPath = resolve22(planWorkspace, planningArtifactPath);
8662
- if (!existsSync13(expectedPlanPath)) {
9518
+ const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
9519
+ if (!existsSync12(expectedPlanPath)) {
8663
9520
  const failedAt = new Date().toISOString();
8664
9521
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
8665
9522
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -8679,6 +9536,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8679
9536
  throw new CliError2(failureDetail, 1);
8680
9537
  }
8681
9538
  }
9539
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
9540
+ appendPiStageLog({
9541
+ projectRoot: context.projectRoot,
9542
+ runId: input.runId,
9543
+ stage: "Validate",
9544
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
9545
+ status: "completed"
9546
+ });
9547
+ if (verificationAction && !reviewStarted) {
9548
+ verificationAction.complete("Completion verification checks finished.");
9549
+ }
9550
+ if (!reviewAction) {
9551
+ promoteToReviewing("Server-owned closeout is queued.");
9552
+ }
9553
+ if (reviewAction) {
9554
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
9555
+ }
9556
+ const requestedAt = new Date().toISOString();
9557
+ patchAuthorityRun(context.projectRoot, input.runId, {
9558
+ status: "reviewing",
9559
+ completedAt: null,
9560
+ errorText: null,
9561
+ serverCloseout: {
9562
+ status: "pending",
9563
+ phase: "queued",
9564
+ requestedAt,
9565
+ updatedAt: requestedAt,
9566
+ runtimeWorkspace: latestRuntimeWorkspace,
9567
+ branch: latestRuntimeBranch,
9568
+ taskId: input.taskId ?? runtimeTaskId
9569
+ }
9570
+ });
9571
+ appendRunLog(context.projectRoot, input.runId, {
9572
+ id: `log:${input.runId}:server-closeout-requested`,
9573
+ title: "Server-owned closeout requested",
9574
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
9575
+ tone: "info",
9576
+ status: "reviewing",
9577
+ createdAt: requestedAt,
9578
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
9579
+ });
9580
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
9581
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
9582
+ await context.emitEvent("command.finished", {
9583
+ command: [
9584
+ "rig",
9585
+ "server",
9586
+ "task-run",
9587
+ ...input.taskId ? ["--task", input.taskId] : [],
9588
+ ...input.title ? ["--title", input.title] : []
9589
+ ],
9590
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
9591
+ exitCode: 0,
9592
+ durationMs: 0,
9593
+ startedAt,
9594
+ finishedAt: requestedAt
9595
+ });
9596
+ return;
9597
+ }
8682
9598
  const runPiPrFeedbackFix = async (message2) => {
8683
9599
  appendPiStageLog({
8684
9600
  projectRoot: context.projectRoot,
@@ -8697,7 +9613,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8697
9613
  });
8698
9614
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
8699
9615
  const feedbackCommand = buildHostAgentCommand(message2);
8700
- const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9616
+ const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
8701
9617
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
8702
9618
  env: childEnv,
8703
9619
  stdio: ["pipe", "pipe", "pipe"]
@@ -8706,11 +9622,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8706
9622
  child.stdin.write(message2);
8707
9623
  }
8708
9624
  child.stdin.end();
9625
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
9626
+ let feedbackAssistantText = "";
9627
+ const feedbackPendingToolUses = new Map;
8709
9628
  const stdout = createLineInterface({ input: child.stdout });
8710
9629
  stdout.on("line", (line) => {
8711
9630
  const trimmed = line.trim();
8712
9631
  if (!trimmed)
8713
9632
  return;
9633
+ try {
9634
+ const record = JSON.parse(trimmed);
9635
+ const providerLogs = buildClaudeLogsFromRecord({
9636
+ runId: input.runId,
9637
+ record,
9638
+ createdAtFallback: new Date().toISOString(),
9639
+ status: "reviewing",
9640
+ pendingToolUses: feedbackPendingToolUses
9641
+ });
9642
+ for (const providerLog of providerLogs) {
9643
+ appendRunLog(context.projectRoot, input.runId, providerLog);
9644
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
9645
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9646
+ if (providerLog.title === "Tool activity")
9647
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9648
+ }
9649
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
9650
+ projectRoot: context.projectRoot,
9651
+ runId: input.runId,
9652
+ messageId: feedbackAssistantMessageId,
9653
+ record,
9654
+ assistantText: feedbackAssistantText
9655
+ });
9656
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
9657
+ if (hadAssistantDelta) {
9658
+ feedbackAssistantText = nextFeedbackAssistantText;
9659
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9660
+ }
9661
+ if (providerLogs.length > 0 || hadAssistantDelta)
9662
+ return;
9663
+ } catch {}
8714
9664
  appendRunLog(context.projectRoot, input.runId, {
8715
9665
  id: nextRunLogId(),
8716
9666
  title: "Pi PR feedback fix output",
@@ -8736,10 +9686,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8736
9686
  });
8737
9687
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
8738
9688
  });
8739
- const exitCode = await new Promise((resolve23) => {
8740
- child.once("error", () => resolve23(1));
8741
- child.once("close", (code) => resolve23(code ?? 1));
9689
+ const exitCode = await new Promise((resolve22) => {
9690
+ child.once("error", () => resolve22(1));
9691
+ child.once("close", (code) => resolve22(code ?? 1));
8742
9692
  });
9693
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
9694
+ runId: input.runId,
9695
+ status: exitCode === 0 ? "completed" : "failed",
9696
+ pendingToolUses: feedbackPendingToolUses
9697
+ })) {
9698
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
9699
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
9700
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9701
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9702
+ }
9703
+ if (feedbackAssistantText.trim()) {
9704
+ appendRunTimeline(context.projectRoot, input.runId, {
9705
+ id: feedbackAssistantMessageId,
9706
+ type: "assistant_message",
9707
+ text: feedbackAssistantText,
9708
+ state: "completed",
9709
+ createdAt: new Date().toISOString(),
9710
+ completedAt: new Date().toISOString()
9711
+ });
9712
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9713
+ }
8743
9714
  if (exitCode !== 0) {
8744
9715
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
8745
9716
  }
@@ -8857,8 +9828,8 @@ async function executeTest(context, args) {
8857
9828
  }
8858
9829
 
8859
9830
  // packages/cli/src/commands/setup.ts
8860
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
8861
- import { resolve as resolve23 } from "path";
9831
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
9832
+ import { resolve as resolve22 } from "path";
8862
9833
  import { createPluginHost } from "@rig/core";
8863
9834
  import {
8864
9835
  isSupportedBunVersion as isSupportedBunVersion2,
@@ -8921,8 +9892,8 @@ function runSetupInit(projectRoot) {
8921
9892
  mkdirSync9(stateDir, { recursive: true });
8922
9893
  mkdirSync9(logsDir, { recursive: true });
8923
9894
  mkdirSync9(artifactsDir, { recursive: true });
8924
- const failuresPath = resolve23(stateDir, "failed_approaches.md");
8925
- if (!existsSync14(failuresPath)) {
9895
+ const failuresPath = resolve22(stateDir, "failed_approaches.md");
9896
+ if (!existsSync13(failuresPath)) {
8926
9897
  writeFileSync7(failuresPath, `# Failed Approaches
8927
9898
 
8928
9899
  `, "utf-8");
@@ -8940,18 +9911,18 @@ async function runSetupCheck(projectRoot) {
8940
9911
  }
8941
9912
  async function runSetupPreflight(projectRoot) {
8942
9913
  await runSetupCheck(projectRoot);
8943
- const validationRoot = resolve23(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
8944
- if (existsSync14(validationRoot)) {
9914
+ const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
9915
+ if (existsSync13(validationRoot)) {
8945
9916
  const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
8946
9917
  for (const validator of validators) {
8947
- const script = resolve23(validationRoot, validator.name, "validate.sh");
8948
- if (existsSync14(script)) {
9918
+ const script = resolve22(validationRoot, validator.name, "validate.sh");
9919
+ if (existsSync13(script)) {
8949
9920
  console.log(`OK: validator script ${script}`);
8950
9921
  }
8951
9922
  }
8952
9923
  }
8953
- const hooksRoot = resolve23(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
8954
- if (existsSync14(hooksRoot)) {
9924
+ const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
9925
+ if (existsSync13(hooksRoot)) {
8955
9926
  const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
8956
9927
  for (const hook of hooks) {
8957
9928
  console.log(`OK: hook ${hook}`);
@@ -9345,8 +10316,8 @@ var __testOnly = {
9345
10316
  validateRequiredBugPromptValue
9346
10317
  };
9347
10318
  // packages/cli/src/launcher.ts
9348
- import { existsSync as existsSync15 } from "fs";
9349
- import { resolve as resolve24 } from "path";
10319
+ import { existsSync as existsSync14 } from "fs";
10320
+ import { basename as basename2, resolve as resolve23 } from "path";
9350
10321
  import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
9351
10322
  import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
9352
10323
  function parsePolicyMode(value) {
@@ -9359,7 +10330,7 @@ function parsePolicyMode(value) {
9359
10330
  throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
9360
10331
  }
9361
10332
  function hasRigProjectMarker(candidate) {
9362
- return existsSync15(resolve24(candidate, RIG_DEFINITION_DIRNAME)) || existsSync15(resolve24(candidate, RIG_STATE_DIRNAME)) || existsSync15(resolve24(candidate, "rig.config.ts")) || existsSync15(resolve24(candidate, "rig.config.json"));
10333
+ return existsSync14(resolve23(candidate, RIG_DEFINITION_DIRNAME)) || existsSync14(resolve23(candidate, RIG_STATE_DIRNAME)) || existsSync14(resolve23(candidate, "rig.config.ts")) || existsSync14(resolve23(candidate, "rig.config.json")) || existsSync14(resolve23(candidate, ".git"));
9363
10334
  }
9364
10335
  function resolveProjectRoot({
9365
10336
  envProjectRoot,
@@ -9368,17 +10339,19 @@ function resolveProjectRoot({
9368
10339
  cwd = process.cwd()
9369
10340
  }) {
9370
10341
  if (envProjectRoot) {
9371
- return resolve24(cwd, envProjectRoot);
10342
+ return resolve23(cwd, envProjectRoot);
9372
10343
  }
9373
10344
  const fallbackImportDir = importDir ?? cwd;
9374
- const candidates = [cwd, resolve24(execPath, "..", ".."), resolve24(fallbackImportDir, "..")];
10345
+ const execName = basename2(execPath).toLowerCase();
10346
+ const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
10347
+ const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
9375
10348
  for (const candidate of candidates) {
9376
10349
  const nearest = resolveNearestRigProjectRoot(candidate);
9377
10350
  if (hasRigProjectMarker(nearest)) {
9378
10351
  return nearest;
9379
10352
  }
9380
10353
  }
9381
- return resolve24(cwd);
10354
+ return resolve23(cwd);
9382
10355
  }
9383
10356
  function normalizeCliErrorCode(message2, isCliError) {
9384
10357
  if (message2.startsWith("Invalid --policy-mode value:")) {
@@ -9445,7 +10418,7 @@ async function runRigCli(module, options = {}) {
9445
10418
  runId: context.runId,
9446
10419
  outcome,
9447
10420
  eventsFile: context.eventBus.getEventsFile(),
9448
- policyFile: resolve24(projectRoot, "rig", "policy", "policy.json"),
10421
+ policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
9449
10422
  policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
9450
10423
  }, null, 2));
9451
10424
  }