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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/rig.js CHANGED
@@ -105,12 +105,13 @@ async function runCommand(context, parts) {
105
105
  const envMode = process.env.RIG_BASH_MODE;
106
106
  const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
107
107
  const controlledPath = `${resolve(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
108
- const controlledBash = await ensureAgentShellBinary(context.projectRoot);
108
+ const usesInfrastructureBinary = parts[0] === "rig-server";
109
+ const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
109
110
  const commandEnv = [
110
111
  "env",
111
112
  `PATH=${controlledPath}`,
112
113
  `PROJECT_RIG_ROOT=${context.projectRoot}`,
113
- `BASH=${controlledBash}`,
114
+ ...controlledBash ? [`BASH=${controlledBash}`] : [],
114
115
  `RIG_BASH_MODE=${effectiveMode}`,
115
116
  `RIG_POLICY_FILE=${resolve(context.projectRoot, "rig/policy/policy.json")}`,
116
117
  ...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
@@ -2670,17 +2671,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
2670
2671
  }
2671
2672
 
2672
2673
  // packages/cli/src/commands/_server-client.ts
2673
- import { spawnSync } from "child_process";
2674
2674
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
2675
2675
  import { resolve as resolve9 } from "path";
2676
2676
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
2677
- var cachedGitHubBearerToken;
2677
+ var scopedGitHubBearerTokens = new Map;
2678
2678
  function cleanToken(value) {
2679
2679
  const trimmed = value?.trim();
2680
2680
  return trimmed ? trimmed : null;
2681
2681
  }
2682
- function setGitHubBearerTokenForCurrentProcess(token) {
2683
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
2682
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
2683
+ const scopedKey = resolve9(projectRoot ?? process.cwd());
2684
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
2684
2685
  }
2685
2686
  function readPrivateRemoteSessionToken(projectRoot) {
2686
2687
  const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
@@ -2694,25 +2695,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
2694
2695
  }
2695
2696
  }
2696
2697
  function readGitHubBearerTokenForRemote(projectRoot) {
2697
- if (cachedGitHubBearerToken !== undefined)
2698
- return cachedGitHubBearerToken;
2698
+ const scopedKey = resolve9(projectRoot);
2699
+ if (scopedGitHubBearerTokens.has(scopedKey))
2700
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
2699
2701
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
2700
- if (privateSession) {
2701
- cachedGitHubBearerToken = privateSession;
2702
- return cachedGitHubBearerToken;
2703
- }
2704
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
2705
- if (envToken) {
2706
- cachedGitHubBearerToken = envToken;
2707
- return cachedGitHubBearerToken;
2708
- }
2709
- const result = spawnSync("gh", ["auth", "token"], {
2710
- encoding: "utf8",
2711
- timeout: 5000,
2712
- stdio: ["ignore", "pipe", "ignore"]
2713
- });
2714
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
2715
- return cachedGitHubBearerToken;
2702
+ if (privateSession)
2703
+ return privateSession;
2704
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
2716
2705
  }
2717
2706
  async function ensureServerForCli(projectRoot) {
2718
2707
  try {
@@ -2850,16 +2839,36 @@ async function registerProjectViaServer(context, input) {
2850
2839
  function sleep(ms) {
2851
2840
  return new Promise((resolve10) => setTimeout(resolve10, ms));
2852
2841
  }
2842
+ function isRetryableProjectRootSwitchError(error) {
2843
+ if (!(error instanceof Error))
2844
+ return false;
2845
+ const message = error.message.toLowerCase();
2846
+ 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");
2847
+ }
2853
2848
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
2854
- const switched = await requestServerJson(context, "/api/server/project-root", {
2855
- method: "POST",
2856
- headers: { "content-type": "application/json" },
2857
- body: JSON.stringify({ projectRoot })
2858
- });
2859
2849
  const timeoutMs = options.timeoutMs ?? 30000;
2860
2850
  const pollMs = options.pollMs ?? 1000;
2861
2851
  const deadline = Date.now() + timeoutMs;
2862
2852
  let lastError;
2853
+ let switched = null;
2854
+ while (Date.now() < deadline) {
2855
+ try {
2856
+ switched = await requestServerJson(context, "/api/server/project-root", {
2857
+ method: "POST",
2858
+ headers: { "content-type": "application/json" },
2859
+ body: JSON.stringify({ projectRoot })
2860
+ });
2861
+ break;
2862
+ } catch (error) {
2863
+ lastError = error;
2864
+ if (!isRetryableProjectRootSwitchError(error))
2865
+ throw error;
2866
+ await sleep(pollMs);
2867
+ }
2868
+ }
2869
+ if (!switched) {
2870
+ 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);
2871
+ }
2863
2872
  while (Date.now() < deadline) {
2864
2873
  try {
2865
2874
  const status = await requestServerJson(context, "/api/server/status");
@@ -2877,6 +2886,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
2877
2886
  }
2878
2887
  throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
2879
2888
  }
2889
+ async function listRunsViaServer(context, options = {}) {
2890
+ const url = new URL("http://rig.local/api/runs");
2891
+ if (options.limit !== undefined)
2892
+ url.searchParams.set("limit", String(options.limit));
2893
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2894
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
2895
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
2896
+ }
2880
2897
  async function getRunDetailsViaServer(context, runId) {
2881
2898
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
2882
2899
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
@@ -2890,6 +2907,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
2890
2907
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2891
2908
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2892
2909
  }
2910
+ async function getRunTimelineViaServer(context, runId, options = {}) {
2911
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
2912
+ if (options.limit !== undefined)
2913
+ url.searchParams.set("limit", String(options.limit));
2914
+ if (options.cursor)
2915
+ url.searchParams.set("cursor", options.cursor);
2916
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2917
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2918
+ }
2919
+ async function ensureTaskLabelsViaServer(context) {
2920
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
2921
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2922
+ }
2923
+ async function listGitHubProjectsViaServer(context, owner) {
2924
+ const url = new URL("http://rig.local/api/github/projects");
2925
+ url.searchParams.set("owner", owner);
2926
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2927
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
2928
+ }
2929
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
2930
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
2931
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2932
+ }
2933
+ async function updateWorkspaceTaskViaServer(context, input) {
2934
+ const payload = await requestServerJson(context, "/api/tasks/update", {
2935
+ method: "POST",
2936
+ headers: { "content-type": "application/json" },
2937
+ body: JSON.stringify(input)
2938
+ });
2939
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2940
+ }
2893
2941
  async function stopRunViaServer(context, runId) {
2894
2942
  const payload = await requestServerJson(context, "/api/runs/stop", {
2895
2943
  method: "POST",
@@ -2942,7 +2990,8 @@ async function submitTaskRunViaServer(context, input) {
2942
2990
  import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2943
2991
  import { homedir as homedir3 } from "os";
2944
2992
  import { resolve as resolve10 } from "path";
2945
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2993
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2994
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2946
2995
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2947
2996
  export { default } from '@rig/pi-rig';
2948
2997
  `;
@@ -2970,7 +3019,7 @@ function resolvePiHomeDir(inputHomeDir) {
2970
3019
  function piListContainsPiRig(output) {
2971
3020
  return output.split(/\r?\n/).some((line) => {
2972
3021
  const normalized = line.trim();
2973
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
3022
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
2974
3023
  });
2975
3024
  }
2976
3025
  async function safeRun(runner, command, options) {
@@ -3086,7 +3135,7 @@ async function ensureRemotePiRigInstalled(input) {
3086
3135
  const payload = await input.requestJson("/api/pi-rig/install", {
3087
3136
  method: "POST",
3088
3137
  headers: { "content-type": "application/json" },
3089
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
3138
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
3090
3139
  });
3091
3140
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
3092
3141
  const piOk = record.piOk === true || record.ok === true;
@@ -3169,6 +3218,9 @@ function permissionAllowsPr(payload) {
3169
3218
  }
3170
3219
  return null;
3171
3220
  }
3221
+ function isNotFoundError(error) {
3222
+ return /\b(404|not found)\b/i.test(message(error));
3223
+ }
3172
3224
  function projectCheckoutReady(payload) {
3173
3225
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
3174
3226
  return null;
@@ -3201,19 +3253,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
3201
3253
  const checks = [];
3202
3254
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
3203
3255
  const taskId = options.taskId?.trim() || null;
3256
+ const requiresCurrentRunApi = Boolean(taskId);
3257
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
3258
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
3259
+ let legacyServerCompatibility = false;
3204
3260
  try {
3205
3261
  await request("/api/server/status");
3206
3262
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
3207
3263
  } catch (error) {
3208
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3264
+ if (isNotFoundError(error)) {
3265
+ try {
3266
+ await request("/health");
3267
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
3268
+ 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"));
3269
+ } catch (healthError) {
3270
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
3271
+ }
3272
+ } else {
3273
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3274
+ }
3209
3275
  }
3210
3276
  const repo = readRepoConnection(context.projectRoot);
3211
- 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>`."));
3277
+ 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>`."));
3212
3278
  try {
3213
3279
  const auth = await request("/api/github/auth/status");
3214
- 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>`."));
3280
+ 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>`."));
3215
3281
  } catch (error) {
3216
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3282
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3217
3283
  }
3218
3284
  try {
3219
3285
  const projection = await request("/api/workspace/task-projection");
@@ -3241,9 +3307,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
3241
3307
  try {
3242
3308
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
3243
3309
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
3244
- 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."));
3310
+ 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."));
3245
3311
  } catch (error) {
3246
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
3312
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
3247
3313
  }
3248
3314
  try {
3249
3315
  const runs = await request("/api/runs?limit=200");
@@ -3446,6 +3512,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3446
3512
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3447
3513
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3448
3514
  const next = {
3515
+ ...existing ?? {},
3449
3516
  runId: input.runId,
3450
3517
  projectRoot,
3451
3518
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -4154,9 +4221,10 @@ async function executeInbox(context, args) {
4154
4221
 
4155
4222
  // packages/cli/src/commands/init.ts
4156
4223
  import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4157
- import { spawnSync as spawnSync2 } from "child_process";
4224
+ import { spawnSync } from "child_process";
4158
4225
  import { resolve as resolve17 } from "path";
4159
4226
  import { buildRigInitConfigSource } from "@rig/core";
4227
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4160
4228
 
4161
4229
  // packages/cli/src/commands/_snapshot-upload.ts
4162
4230
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -4470,6 +4538,7 @@ function countDoctorFailures(checks) {
4470
4538
 
4471
4539
  // packages/cli/src/commands/init.ts
4472
4540
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
4541
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
4473
4542
  var RIG_CONFIG_DEV_DEPENDENCIES = {
4474
4543
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
4475
4544
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -4480,7 +4549,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
4480
4549
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
4481
4550
  }
4482
4551
  function detectOriginRepoSlug(projectRoot) {
4483
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4552
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4484
4553
  if (result.status !== 0)
4485
4554
  return null;
4486
4555
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -4536,11 +4605,14 @@ function applyGitHubProjectConfig(source, options) {
4536
4605
  return source;
4537
4606
  const projectId = JSON.stringify(options.githubProject);
4538
4607
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
4608
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
4609
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
4610
+ `)},` : "";
4539
4611
  return source.replace(` projects: { enabled: false },`, [
4540
4612
  ` projects: {`,
4541
4613
  ` enabled: true,`,
4542
4614
  ` projectId: ${projectId},`,
4543
- ` statusFieldId: ${statusFieldId},`,
4615
+ ` statusFieldId: ${statusFieldId},${statuses}`,
4544
4616
  ` },`
4545
4617
  ].join(`
4546
4618
  `));
@@ -4561,21 +4633,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
4561
4633
  }
4562
4634
  }
4563
4635
  function detectGhLogin() {
4564
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4636
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4565
4637
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
4566
4638
  }
4567
4639
  function readGhAuthToken() {
4568
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4640
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
4569
4641
  if (result.status !== 0 || !result.stdout.trim()) {
4570
4642
  throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
4571
4643
  }
4572
4644
  return result.stdout.trim();
4573
4645
  }
4646
+ function refreshGhProjectScopesAndReadToken() {
4647
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
4648
+ encoding: "utf8",
4649
+ stdio: ["inherit", "pipe", "pipe"]
4650
+ });
4651
+ if (result.status !== 0)
4652
+ return null;
4653
+ try {
4654
+ return readGhAuthToken();
4655
+ } catch {
4656
+ return null;
4657
+ }
4658
+ }
4574
4659
  async function loadClackPrompts() {
4575
4660
  return await import("@clack/prompts");
4576
4661
  }
4662
+ function clackTextOptions(options) {
4663
+ return {
4664
+ message: options.message,
4665
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
4666
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
4667
+ };
4668
+ }
4577
4669
  async function promptRequiredText(prompts, options) {
4578
- const value = await prompts.text(options);
4670
+ const value = await prompts.text(clackTextOptions(options));
4579
4671
  if (prompts.isCancel(value))
4580
4672
  throw new CliError2("Init cancelled.", 1);
4581
4673
  const text2 = String(value ?? "").trim();
@@ -4584,7 +4676,7 @@ async function promptRequiredText(prompts, options) {
4584
4676
  return text2;
4585
4677
  }
4586
4678
  async function promptOptionalText(prompts, options) {
4587
- const value = await prompts.text(options);
4679
+ const value = await prompts.text(clackTextOptions(options));
4588
4680
  if (prompts.isCancel(value))
4589
4681
  throw new CliError2("Init cancelled.", 1);
4590
4682
  return String(value ?? "").trim();
@@ -4595,6 +4687,164 @@ async function promptSelect(prompts, options) {
4595
4687
  throw new CliError2("Init cancelled.", 1);
4596
4688
  return String(value);
4597
4689
  }
4690
+ function repoOwnerFromSlug(repoSlug) {
4691
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
4692
+ }
4693
+ function recordArray(value, key) {
4694
+ if (!value || typeof value !== "object" || Array.isArray(value))
4695
+ return [];
4696
+ const raw = value[key];
4697
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4698
+ }
4699
+ async function listGitHubProjectsForInit(context, owner, token) {
4700
+ if (token?.trim()) {
4701
+ try {
4702
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
4703
+ } catch (directError) {
4704
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
4705
+ if (recordArray(serverPayload, "projects").length > 0)
4706
+ return serverPayload;
4707
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
4708
+ }
4709
+ }
4710
+ return listGitHubProjectsViaServer(context, owner);
4711
+ }
4712
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
4713
+ if (token?.trim()) {
4714
+ try {
4715
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
4716
+ } catch (directError) {
4717
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
4718
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
4719
+ return serverPayload;
4720
+ }
4721
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
4722
+ }
4723
+ }
4724
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
4725
+ }
4726
+ var PROJECT_STATUS_PROMPTS = {
4727
+ running: "Running/In progress",
4728
+ prOpen: "PR open/review",
4729
+ ciFixing: "CI/review fixing",
4730
+ merging: "Merging",
4731
+ done: "Done",
4732
+ needsAttention: "Needs attention"
4733
+ };
4734
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
4735
+ running: "In Progress",
4736
+ prOpen: "In Review",
4737
+ ciFixing: "In Review",
4738
+ merging: "Merging",
4739
+ done: "Done",
4740
+ needsAttention: "Needs Attention"
4741
+ };
4742
+ async function promptManualProjectStatusMapping(prompts) {
4743
+ const statuses = {};
4744
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4745
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
4746
+ const value = await promptOptionalText(prompts, {
4747
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
4748
+ placeholder: defaultLabel
4749
+ });
4750
+ statuses[key] = value || defaultLabel;
4751
+ }
4752
+ return statuses;
4753
+ }
4754
+ function projectScopeError(value) {
4755
+ const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
4756
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
4757
+ }
4758
+ function optionName(option) {
4759
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
4760
+ }
4761
+ function autoProjectStatusValue(options, key, label) {
4762
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
4763
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
4764
+ if (!match)
4765
+ return null;
4766
+ return String(match.id ?? match.name);
4767
+ }
4768
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
4769
+ const projectChoice = await promptSelect(prompts, {
4770
+ message: "GitHub Projects status sync",
4771
+ initialValue: "select",
4772
+ options: [
4773
+ { value: "select", label: "Select accessible ProjectV2" },
4774
+ { value: "off", label: "Off" },
4775
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
4776
+ ]
4777
+ });
4778
+ if (projectChoice === "off")
4779
+ return { githubProject: "off" };
4780
+ if (projectChoice === "manual") {
4781
+ return {
4782
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
4783
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
4784
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
4785
+ };
4786
+ }
4787
+ const owner = repoOwnerFromSlug(repoSlug);
4788
+ if (!owner)
4789
+ throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
4790
+ let activeToken = githubToken?.trim() || null;
4791
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4792
+ let projects = recordArray(projectsPayload, "projects");
4793
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
4794
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
4795
+ const refreshedToken = refreshProjectToken();
4796
+ if (refreshedToken) {
4797
+ activeToken = refreshedToken;
4798
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4799
+ projects = recordArray(projectsPayload, "projects");
4800
+ }
4801
+ }
4802
+ if (projects.length === 0) {
4803
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
4804
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
4805
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
4806
+ }
4807
+ const selectedProjectId = await promptSelect(prompts, {
4808
+ message: "GitHub ProjectV2 project",
4809
+ options: [
4810
+ ...projects.map((project) => ({
4811
+ value: String(project.id),
4812
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
4813
+ hint: typeof project.url === "string" ? project.url : undefined
4814
+ })),
4815
+ { value: "manual", label: "Enter ProjectV2 id manually" }
4816
+ ]
4817
+ });
4818
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
4819
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
4820
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
4821
+ const rawField = fieldPayloadRecord.field;
4822
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
4823
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
4824
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4825
+ if (options.length === 0) {
4826
+ return {
4827
+ githubProject: projectId,
4828
+ githubProjectStatusField: fieldId,
4829
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
4830
+ ...activeToken ? { githubToken: activeToken } : {}
4831
+ };
4832
+ }
4833
+ const statuses = {};
4834
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4835
+ const auto = autoProjectStatusValue(options, key, label);
4836
+ statuses[key] = auto ?? await promptSelect(prompts, {
4837
+ message: `Project status option for ${label}`,
4838
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
4839
+ });
4840
+ }
4841
+ return {
4842
+ githubProject: projectId,
4843
+ githubProjectStatusField: fieldId,
4844
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
4845
+ ...activeToken ? { githubToken: activeToken } : {}
4846
+ };
4847
+ }
4598
4848
  function sleep2(ms) {
4599
4849
  return new Promise((resolve18) => setTimeout(resolve18, ms));
4600
4850
  }
@@ -4608,12 +4858,29 @@ function apiSessionTokenFrom(payload) {
4608
4858
  const token = payload.apiSessionToken;
4609
4859
  return typeof token === "string" && token.trim() ? token.trim() : null;
4610
4860
  }
4861
+ function cleanPayloadString(value) {
4862
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4863
+ }
4864
+ function remoteGitHubAuthMetadata(payload) {
4865
+ if (!payload)
4866
+ return {};
4867
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
4868
+ return {
4869
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
4870
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
4871
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
4872
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
4873
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
4874
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
4875
+ };
4876
+ }
4611
4877
  function writeRemoteGitHubAuthState(projectRoot, input) {
4612
4878
  writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
4613
4879
  authenticated: true,
4614
4880
  source: input.source,
4615
4881
  storedOnServer: true,
4616
4882
  selectedRepo: input.selectedRepo,
4883
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
4617
4884
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
4618
4885
  updatedAt: new Date().toISOString()
4619
4886
  }, null, 2)}
@@ -4706,12 +4973,13 @@ async function runControlPlaneInit(context, options) {
4706
4973
  if (token) {
4707
4974
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
4708
4975
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
4709
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4976
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4710
4977
  if (serverKind === "remote") {
4711
4978
  writeRemoteGitHubAuthState(projectRoot, {
4712
4979
  source: authMethod === "gh" ? "gh" : "init-token",
4713
4980
  selectedRepo: repo.slug,
4714
- apiSessionToken
4981
+ apiSessionToken,
4982
+ authPayload: githubAuth
4715
4983
  });
4716
4984
  }
4717
4985
  } else if (authMethod === "device") {
@@ -4730,9 +4998,9 @@ async function runControlPlaneInit(context, options) {
4730
4998
  if (completed) {
4731
4999
  const apiSessionToken = apiSessionTokenFrom(completed);
4732
5000
  if (apiSessionToken) {
4733
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
5001
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
4734
5002
  if (serverKind === "remote") {
4735
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
5003
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
4736
5004
  }
4737
5005
  }
4738
5006
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -4750,19 +5018,25 @@ async function runControlPlaneInit(context, options) {
4750
5018
  Object.assign(checkout, preparedCheckout);
4751
5019
  }
4752
5020
  }
5021
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
5022
+ if (serverKind === "remote" && checkoutPath && token) {
5023
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
5024
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
5025
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
5026
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
5027
+ }
4753
5028
  const registered = await registerProjectViaServer(context, {
4754
5029
  repoSlug: repo.slug,
4755
5030
  checkout
4756
5031
  });
4757
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
4758
5032
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
4759
- if (serverRootSwitch && token) {
4760
- githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
4761
- const apiSessionToken = apiSessionTokenFrom(githubAuth);
4762
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4763
- writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
4764
- }
4765
5033
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
5034
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
5035
+ ok: false,
5036
+ ready: false,
5037
+ labelsReady: false,
5038
+ error: error instanceof Error ? error.message : String(error)
5039
+ }));
4766
5040
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
4767
5041
  remote: true,
4768
5042
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -4793,6 +5067,7 @@ async function runControlPlaneInit(context, options) {
4793
5067
  githubAuth,
4794
5068
  deviceAuth,
4795
5069
  githubAuthWarning: remoteGhTokenWarning,
5070
+ labelSetup,
4796
5071
  pi,
4797
5072
  doctor
4798
5073
  };
@@ -4899,12 +5174,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4899
5174
  });
4900
5175
  const serverChoice = await promptSelect(prompts, {
4901
5176
  message: "Rig server",
5177
+ initialValue: "remote",
4902
5178
  options: [
4903
- { value: "local", label: "Local server", hint: "run on this machine" },
4904
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
5179
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
5180
+ { value: "local", label: "Local server", hint: "run on this machine" }
4905
5181
  ]
4906
5182
  });
4907
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
5183
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
4908
5184
  let remoteCheckout;
4909
5185
  if (serverChoice === "remote") {
4910
5186
  const checkout = await promptSelect(prompts, {
@@ -4936,38 +5212,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4936
5212
  { value: "skip", label: "Skip for now" }
4937
5213
  ]
4938
5214
  });
5215
+ let remoteGhTokenConfirmed = false;
4939
5216
  if (serverChoice === "remote" && authMethod === "gh") {
4940
5217
  if (!prompts.confirm)
4941
5218
  throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
4942
5219
  const confirmed = await prompts.confirm({
4943
5220
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
4944
- initialValue: false
5221
+ initialValue: true
4945
5222
  });
4946
5223
  if (prompts.isCancel(confirmed) || confirmed !== true) {
4947
5224
  throw new CliError2("Remote gh-token import cancelled.", 1);
4948
5225
  }
5226
+ remoteGhTokenConfirmed = true;
4949
5227
  }
4950
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
4951
- const projectChoice = await promptSelect(prompts, {
4952
- message: "GitHub Projects status sync",
4953
- options: [
4954
- { value: "off", label: "Off" },
4955
- { value: "configure", label: "Configure ProjectV2 status field" }
4956
- ]
4957
- });
4958
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
4959
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
5228
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
5229
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
5230
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
4960
5231
  const result = await runControlPlaneInit(context, {
4961
5232
  server: serverChoice,
4962
5233
  remoteUrl,
4963
5234
  repoSlug,
4964
- githubToken,
5235
+ githubToken: effectiveGithubToken,
4965
5236
  githubAuthMethod: authMethod,
4966
- githubProject,
4967
- githubProjectStatusField,
5237
+ githubProject: projectConfig.githubProject,
5238
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
5239
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
4968
5240
  remoteCheckout,
4969
5241
  repair,
4970
- privateStateOnly
5242
+ privateStateOnly,
5243
+ yes: remoteGhTokenConfirmed || undefined
4971
5244
  });
4972
5245
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
4973
5246
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -5063,7 +5336,7 @@ Usage: rig connect <list|add|use|status>`, 1);
5063
5336
  }
5064
5337
 
5065
5338
  // packages/cli/src/commands/github.ts
5066
- import { spawnSync as spawnSync3 } from "child_process";
5339
+ import { spawnSync as spawnSync2 } from "child_process";
5067
5340
  function printPayload(context, payload, fallback) {
5068
5341
  if (context.outputMode === "json")
5069
5342
  console.log(JSON.stringify(payload, null, 2));
@@ -5071,7 +5344,7 @@ function printPayload(context, payload, fallback) {
5071
5344
  console.log(fallback);
5072
5345
  }
5073
5346
  function readGhToken() {
5074
- const result = spawnSync3("gh", ["auth", "token"], { encoding: "utf8" });
5347
+ const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
5075
5348
  if (result.status !== 0) {
5076
5349
  const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
5077
5350
  throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
@@ -6103,14 +6376,10 @@ async function executeRemote(context, args) {
6103
6376
  }
6104
6377
 
6105
6378
  // packages/cli/src/commands/run.ts
6106
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
6107
- import { resolve as resolve20 } from "path";
6108
6379
  import { createInterface as createInterface2 } from "readline/promises";
6109
6380
  import {
6110
6381
  listAuthorityRuns as listAuthorityRuns3,
6111
- readAuthorityRun as readAuthorityRun4,
6112
- readJsonlFile as readJsonlFile4,
6113
- resolveAuthorityRunDir as resolveAuthorityRunDir5
6382
+ readAuthorityRun as readAuthorityRun4
6114
6383
  } from "@rig/runtime/control-plane/authority-files";
6115
6384
  import {
6116
6385
  cleanupRunState,
@@ -6118,6 +6387,7 @@ import {
6118
6387
  listOpenEpics,
6119
6388
  resolveDefaultEpic,
6120
6389
  runResume,
6390
+ runRestart,
6121
6391
  runStatus,
6122
6392
  runStop,
6123
6393
  startRun,
@@ -6125,9 +6395,9 @@ import {
6125
6395
  } from "@rig/runtime/control-plane/native/run-ops";
6126
6396
  import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
6127
6397
 
6128
- // packages/cli/src/commands/_operator-view.ts
6398
+ // packages/cli/src/commands/_operator-surface.ts
6129
6399
  import { createInterface } from "readline";
6130
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6400
+ import { createInterface as createPromptInterface } from "readline/promises";
6131
6401
  var CANONICAL_STAGES = [
6132
6402
  "Connect",
6133
6403
  "GitHub/task sync",
@@ -6142,18 +6412,188 @@ var CANONICAL_STAGES = [
6142
6412
  "Merge",
6143
6413
  "Complete"
6144
6414
  ];
6415
+ function logDetail(log3) {
6416
+ return typeof log3.detail === "string" ? log3.detail.trim() : "";
6417
+ }
6418
+ function parseProviderProtocolLog(title, detail) {
6419
+ if (title.trim().toLowerCase() !== "agent output")
6420
+ return null;
6421
+ if (!detail.startsWith("{") || !detail.endsWith("}"))
6422
+ return null;
6423
+ try {
6424
+ const record = JSON.parse(detail);
6425
+ if (!record || typeof record !== "object" || Array.isArray(record))
6426
+ return null;
6427
+ const type = record.type;
6428
+ return typeof type === "string" && [
6429
+ "assistant",
6430
+ "message_start",
6431
+ "message_update",
6432
+ "message_end",
6433
+ "stream_event",
6434
+ "tool_result",
6435
+ "tool_execution_start",
6436
+ "tool_execution_update",
6437
+ "tool_execution_end",
6438
+ "turn_start",
6439
+ "turn_end"
6440
+ ].includes(type) ? record : null;
6441
+ } catch {
6442
+ return null;
6443
+ }
6444
+ }
6445
+ function renderProviderProtocolLog(record) {
6446
+ const type = typeof record.type === "string" ? record.type : "";
6447
+ if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
6448
+ const toolName = String(record.toolName ?? record.name ?? "tool");
6449
+ 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";
6450
+ return `[Pi tool] ${toolName} ${status}`;
6451
+ }
6452
+ return null;
6453
+ }
6454
+ function entryId(entry, fallback) {
6455
+ return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
6456
+ }
6145
6457
  function renderOperatorSnapshot(snapshot) {
6146
6458
  const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
6147
6459
  const runId = String(run.runId ?? run.id ?? "run");
6148
6460
  const status = String(run.status ?? "unknown");
6149
6461
  const logs = snapshot.logs ?? [];
6462
+ const latestByStage = new Map;
6463
+ for (const log3 of logs) {
6464
+ const title = String(log3.title ?? "").toLowerCase();
6465
+ const stageName = String(log3.stage ?? "").toLowerCase();
6466
+ const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6467
+ if (stage)
6468
+ latestByStage.set(stage, log3);
6469
+ }
6150
6470
  const stageLines = CANONICAL_STAGES.flatMap((stage) => {
6151
- const match = logs.find((log3) => String(log3.title ?? "").toLowerCase() === stage.toLowerCase() || String(log3.stage ?? "").toLowerCase() === stage.toLowerCase());
6152
- return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
6471
+ const match = latestByStage.get(stage);
6472
+ return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
6153
6473
  });
6154
6474
  return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
6155
6475
  `);
6156
6476
  }
6477
+ function createPiRunStreamRenderer(output = process.stdout) {
6478
+ let lastSnapshot = "";
6479
+ const assistantTextById = new Map;
6480
+ const seenTimeline = new Set;
6481
+ const seenLogs = new Set;
6482
+ const writeLine = (line) => output.write(`${line}
6483
+ `);
6484
+ return {
6485
+ renderSnapshot(snapshot) {
6486
+ const rendered = renderOperatorSnapshot(snapshot);
6487
+ if (rendered && rendered !== lastSnapshot) {
6488
+ writeLine(rendered);
6489
+ lastSnapshot = rendered;
6490
+ }
6491
+ },
6492
+ renderTimeline(entries) {
6493
+ for (const [index, entry] of entries.entries()) {
6494
+ const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
6495
+ if (entry.type === "assistant_message" && typeof entry.text === "string") {
6496
+ const text2 = entry.text;
6497
+ const previousText = assistantTextById.get(id) ?? "";
6498
+ if (!previousText && text2.trim()) {
6499
+ writeLine("[Pi assistant]");
6500
+ }
6501
+ if (text2.startsWith(previousText)) {
6502
+ const delta = text2.slice(previousText.length);
6503
+ if (delta)
6504
+ output.write(delta);
6505
+ } else if (text2.trim() && text2 !== previousText) {
6506
+ if (previousText)
6507
+ writeLine(`
6508
+ [Pi assistant]`);
6509
+ output.write(text2);
6510
+ }
6511
+ assistantTextById.set(id, text2);
6512
+ continue;
6513
+ }
6514
+ if (seenTimeline.has(id))
6515
+ continue;
6516
+ seenTimeline.add(id);
6517
+ if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
6518
+ writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
6519
+ continue;
6520
+ }
6521
+ if (entry.type === "timeline_warning") {
6522
+ writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
6523
+ }
6524
+ }
6525
+ },
6526
+ renderLogs(entries) {
6527
+ for (const [index, entry] of entries.entries()) {
6528
+ const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
6529
+ if (seenLogs.has(id))
6530
+ continue;
6531
+ seenLogs.add(id);
6532
+ const title = String(entry.title ?? "");
6533
+ if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
6534
+ continue;
6535
+ const detail = logDetail(entry);
6536
+ if (!detail)
6537
+ continue;
6538
+ const protocolRecord = parseProviderProtocolLog(title, detail);
6539
+ if (protocolRecord) {
6540
+ const protocolLine = renderProviderProtocolLog(protocolRecord);
6541
+ if (protocolLine)
6542
+ writeLine(protocolLine);
6543
+ continue;
6544
+ }
6545
+ writeLine(`[${title || "Rig log"}] ${detail}`);
6546
+ }
6547
+ }
6548
+ };
6549
+ }
6550
+ function createOperatorSurface(options = {}) {
6551
+ const input = options.input ?? process.stdin;
6552
+ const output = options.output ?? process.stdout;
6553
+ const errorOutput = options.errorOutput ?? process.stderr;
6554
+ const renderer = createPiRunStreamRenderer(output);
6555
+ const writeLine = (line) => output.write(`${line}
6556
+ `);
6557
+ return {
6558
+ mode: "pi-compatible-text",
6559
+ ...renderer,
6560
+ info: writeLine,
6561
+ error: (message2) => errorOutput.write(`${message2}
6562
+ `),
6563
+ attachCommandInput(handler) {
6564
+ if (options.interactive === false || !input.isTTY)
6565
+ return null;
6566
+ const rl = createInterface({ input, output: process.stdout, terminal: false });
6567
+ rl.on("line", (line) => {
6568
+ Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6569
+ });
6570
+ return { close: () => rl.close() };
6571
+ }
6572
+ };
6573
+ }
6574
+ function taskId(task) {
6575
+ return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6576
+ }
6577
+ function taskTitle(task) {
6578
+ return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6579
+ }
6580
+ function taskStatus(task) {
6581
+ return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6582
+ }
6583
+ function renderTaskPickerRows(tasks) {
6584
+ return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6585
+ }
6586
+ async function promptForTaskSelection(question) {
6587
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
6588
+ try {
6589
+ return await rl.question(question);
6590
+ } finally {
6591
+ rl.close();
6592
+ }
6593
+ }
6594
+
6595
+ // packages/cli/src/commands/_operator-view.ts
6596
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6157
6597
  function runStatusFromPayload(payload) {
6158
6598
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6159
6599
  return String(run.status ?? "unknown").toLowerCase();
@@ -6175,11 +6615,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
6175
6615
  await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
6176
6616
  return { action: "continue", message: "Steering message queued." };
6177
6617
  }
6178
- async function readOperatorSnapshot(context, runId) {
6618
+ async function readOperatorSnapshot(context, runId, options = {}) {
6179
6619
  const run = await getRunDetailsViaServer(context, runId);
6180
6620
  const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
6181
- const entries = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6182
- return { run, logs: entries, rendered: renderOperatorSnapshot({ run, logs: entries }) };
6621
+ const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
6622
+ entries: [{
6623
+ id: `timeline-unavailable:${runId}`,
6624
+ type: "timeline_warning",
6625
+ detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
6626
+ createdAt: new Date().toISOString()
6627
+ }],
6628
+ nextCursor: options.timelineCursor ?? null
6629
+ }));
6630
+ const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
6631
+ const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6632
+ const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6633
+ return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6183
6634
  }
6184
6635
  async function attachRunOperatorView(context, input) {
6185
6636
  let steered = false;
@@ -6187,45 +6638,266 @@ async function attachRunOperatorView(context, input) {
6187
6638
  await steerRunViaServer(context, input.runId, input.message.trim());
6188
6639
  steered = true;
6189
6640
  }
6641
+ const surface = createOperatorSurface({ interactive: input.interactive !== false });
6190
6642
  let snapshot = await readOperatorSnapshot(context, input.runId);
6191
6643
  if (context.outputMode === "text") {
6192
- console.log(snapshot.rendered);
6644
+ surface.renderSnapshot(snapshot);
6645
+ surface.renderTimeline(snapshot.timeline);
6646
+ surface.renderLogs(snapshot.logs);
6193
6647
  if (steered)
6194
- console.log("Steering message queued.");
6648
+ surface.info("Steering message queued.");
6195
6649
  }
6196
6650
  let detached = false;
6197
- let rl = null;
6651
+ let commandInput = null;
6198
6652
  if (input.follow && !input.once && context.outputMode === "text") {
6199
6653
  if (input.interactive !== false && process.stdin.isTTY) {
6200
- console.log("Controls: /user <message>, /stop, /detach");
6201
- rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
6202
- rl.on("line", (line) => {
6203
- applyOperatorCommand(context, { runId: input.runId, line }).then((result) => {
6204
- if (result.message)
6205
- console.log(result.message);
6206
- if (result.action === "detach" || result.action === "stopped") {
6207
- detached = true;
6208
- rl?.close();
6209
- }
6210
- }).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6654
+ surface.info("Controls: /user <message>, /stop, /detach");
6655
+ commandInput = surface.attachCommandInput(async (line) => {
6656
+ const result = await applyOperatorCommand(context, { runId: input.runId, line });
6657
+ if (result.message)
6658
+ surface.info(result.message);
6659
+ if (result.action === "detach" || result.action === "stopped") {
6660
+ detached = true;
6661
+ commandInput?.close();
6662
+ }
6211
6663
  });
6212
6664
  }
6213
- let lastRendered = snapshot.rendered;
6214
6665
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6666
+ let timelineCursor = snapshot.timelineCursor;
6215
6667
  while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
6216
6668
  await Bun.sleep(pollMs);
6217
- snapshot = await readOperatorSnapshot(context, input.runId);
6218
- if (snapshot.rendered !== lastRendered) {
6219
- console.log(snapshot.rendered);
6220
- lastRendered = snapshot.rendered;
6221
- }
6669
+ snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6670
+ timelineCursor = snapshot.timelineCursor;
6671
+ surface.renderSnapshot(snapshot);
6672
+ surface.renderTimeline(snapshot.timeline);
6673
+ surface.renderLogs(snapshot.logs);
6222
6674
  }
6223
- rl?.close();
6675
+ commandInput?.close();
6224
6676
  }
6225
6677
  return { ...snapshot, steered, detached };
6226
6678
  }
6227
6679
 
6680
+ // packages/cli/src/commands/_cli-format.ts
6681
+ import pc3 from "picocolors";
6682
+ function stringField(record, key, fallback = "") {
6683
+ const value = record[key];
6684
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
6685
+ }
6686
+ function arrayField(record, key) {
6687
+ const value = record[key];
6688
+ return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
6689
+ }
6690
+ function rawObject(record) {
6691
+ const raw = record.raw;
6692
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
6693
+ }
6694
+ function truncate(value, width) {
6695
+ if (value.length <= width)
6696
+ return value;
6697
+ if (width <= 1)
6698
+ return "\u2026";
6699
+ return `${value.slice(0, width - 1)}\u2026`;
6700
+ }
6701
+ function pad(value, width) {
6702
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
6703
+ }
6704
+ function statusColor(status) {
6705
+ const normalized = status.toLowerCase();
6706
+ if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
6707
+ return pc3.green;
6708
+ if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
6709
+ return pc3.red;
6710
+ if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
6711
+ return pc3.cyan;
6712
+ if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
6713
+ return pc3.yellow;
6714
+ return pc3.dim;
6715
+ }
6716
+ function formatTaskList(tasks, options = {}) {
6717
+ if (tasks.length === 0)
6718
+ return pc3.dim("No matching tasks.");
6719
+ if (options.raw)
6720
+ return tasks.map((task) => JSON.stringify(task)).join(`
6721
+ `);
6722
+ const rows = tasks.map((task) => {
6723
+ const raw = rawObject(task);
6724
+ const id = stringField(task, "id", "<unknown>");
6725
+ const status = stringField(task, "status", "unknown");
6726
+ const title = stringField(task, "title", "Untitled task");
6727
+ const source = stringField(task, "source", stringField(raw, "source", ""));
6728
+ const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
6729
+ return { id, status, title, source, labels };
6730
+ });
6731
+ const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
6732
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6733
+ const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6734
+ const body = rows.map((row) => {
6735
+ const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
6736
+ const source = row.source ? pc3.dim(` ${row.source}`) : "";
6737
+ return [
6738
+ pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
6739
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6740
+ `${row.title}${labels}${source}`
6741
+ ].join(" ");
6742
+ });
6743
+ return [pc3.bold("Rig tasks"), header, ...body].join(`
6744
+ `);
6745
+ }
6746
+ function formatRunList(runs, options = {}) {
6747
+ if (runs.length === 0) {
6748
+ return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
6749
+ }
6750
+ const rows = runs.map((run) => {
6751
+ const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
6752
+ const status = stringField(run, "status", "unknown");
6753
+ const taskId2 = stringField(run, "taskId", "");
6754
+ const title = stringField(run, "title", taskId2 || "(untitled)");
6755
+ const runtime = stringField(run, "runtimeAdapter", "");
6756
+ return { runId, status, title, runtime };
6757
+ });
6758
+ const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
6759
+ const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
6760
+ const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
6761
+ const body = rows.map((row) => [
6762
+ pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
6763
+ statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
6764
+ `${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
6765
+ ].join(" "));
6766
+ return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
6767
+ `);
6768
+ }
6769
+ function formatSubmittedRun(input) {
6770
+ const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
6771
+ if (input.task) {
6772
+ const id = stringField(input.task, "id", "<unknown>");
6773
+ const status = stringField(input.task, "status", "unknown");
6774
+ const title = stringField(input.task, "title", "Untitled task");
6775
+ lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
6776
+ }
6777
+ return lines.join(`
6778
+ `);
6779
+ }
6780
+
6781
+ // packages/cli/src/commands/_pi-session.ts
6782
+ import { spawn as spawn2 } from "child_process";
6783
+ function buildPiRigSessionEnv(input) {
6784
+ return {
6785
+ RIG_PROJECT_ROOT: input.projectRoot,
6786
+ PROJECT_RIG_ROOT: input.projectRoot,
6787
+ RIG_RUN_ID: input.runId,
6788
+ RIG_SERVER_RUN_ID: input.runId,
6789
+ RIG_RUNTIME_ADAPTER: "pi",
6790
+ RIG_SERVER_URL: input.serverUrl,
6791
+ RIG_SERVER_BASE_URL: input.serverUrl,
6792
+ RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6793
+ RIG_PI_OPERATOR_SESSION: "1",
6794
+ ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6795
+ ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6796
+ };
6797
+ }
6798
+ function shellBinary(name) {
6799
+ const explicit = process.env.RIG_PI_BINARY?.trim();
6800
+ if (explicit)
6801
+ return explicit;
6802
+ return Bun.which(name) || name;
6803
+ }
6804
+ function buildPiRigSessionCommand(input) {
6805
+ const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6806
+ const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6807
+ const initialCommand = `/rig attach ${input.runId}`;
6808
+ return [
6809
+ shellBinary("pi"),
6810
+ "--no-extensions",
6811
+ "--extension",
6812
+ extensionSource,
6813
+ initialCommand
6814
+ ];
6815
+ }
6816
+ async function launchPiRigSession(context, input) {
6817
+ if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6818
+ return { launched: false, exitCode: null, command: [] };
6819
+ }
6820
+ if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6821
+ return { launched: false, exitCode: null, command: [] };
6822
+ }
6823
+ const server = await ensureServerForCli(context.projectRoot);
6824
+ const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6825
+ const env = {
6826
+ ...process.env,
6827
+ ...buildPiRigSessionEnv({
6828
+ projectRoot: context.projectRoot,
6829
+ runId: input.runId,
6830
+ taskId: input.taskId,
6831
+ serverUrl: server.baseUrl,
6832
+ authToken: server.authToken
6833
+ })
6834
+ };
6835
+ process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6836
+ `);
6837
+ process.stdout.write(`Pi command: ${formatCommand(command)}
6838
+ `);
6839
+ const launchedAt = Date.now();
6840
+ const child = spawn2(command[0], command.slice(1), {
6841
+ cwd: context.projectRoot,
6842
+ env,
6843
+ stdio: "inherit"
6844
+ });
6845
+ const launchError = await new Promise((resolve20) => {
6846
+ child.once("error", (error) => {
6847
+ resolve20({ error: error.message });
6848
+ });
6849
+ child.once("close", (code) => resolve20({ code }));
6850
+ });
6851
+ if ("error" in launchError) {
6852
+ process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6853
+ `);
6854
+ return { launched: false, exitCode: null, command, error: launchError.error };
6855
+ }
6856
+ const exitCode = launchError.code;
6857
+ const elapsedMs = Date.now() - launchedAt;
6858
+ if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6859
+ const error = `Pi exited during startup with code ${exitCode}.`;
6860
+ process.stderr.write(`${error} Falling back to Rig attach view.
6861
+ `);
6862
+ return { launched: false, exitCode, command, error };
6863
+ }
6864
+ return { launched: true, exitCode, command };
6865
+ }
6866
+
6228
6867
  // packages/cli/src/commands/run.ts
6868
+ function normalizeRemoteRunDetails(payload) {
6869
+ const run = payload.run;
6870
+ if (!run || typeof run !== "object" || Array.isArray(run))
6871
+ return null;
6872
+ return {
6873
+ ...run,
6874
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6875
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6876
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6877
+ };
6878
+ }
6879
+ var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
6880
+ function isRemoteConnectionSelected(projectRoot) {
6881
+ return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
6882
+ }
6883
+ async function listRunsForSelectedConnection(context, options = {}) {
6884
+ if (isRemoteConnectionSelected(context.projectRoot)) {
6885
+ return { runs: await listRunsViaServer(context, options), source: "server" };
6886
+ }
6887
+ return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
6888
+ }
6889
+ function runStringField(run, key, fallback = "") {
6890
+ const value = run[key];
6891
+ return typeof value === "string" && value.trim() ? value : fallback;
6892
+ }
6893
+ function runDisplayTitle(run) {
6894
+ return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
6895
+ }
6896
+ function buildServerRunStatus(runs) {
6897
+ const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6898
+ const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
6899
+ return { activeRuns, recentRuns, runs };
6900
+ }
6229
6901
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6230
6902
  if (noEpicPrompt) {
6231
6903
  return false;
@@ -6291,17 +6963,11 @@ async function executeRun(context, args) {
6291
6963
  switch (command) {
6292
6964
  case "list": {
6293
6965
  requireNoExtraArgs(rest, "bun run rig run list");
6294
- const runs = listAuthorityRuns3(context.projectRoot);
6966
+ const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
6295
6967
  if (context.outputMode === "text") {
6296
- if (runs.length === 0) {
6297
- console.log("No runs recorded in .rig/runs.");
6298
- } else {
6299
- for (const run of runs) {
6300
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
6301
- }
6302
- }
6968
+ console.log(formatRunList(runs, { source }));
6303
6969
  }
6304
- return { ok: true, group: "run", command, details: { runs } };
6970
+ return { ok: true, group: "run", command, details: { runs, source } };
6305
6971
  }
6306
6972
  case "delete": {
6307
6973
  let pending = rest;
@@ -6364,7 +7030,7 @@ async function executeRun(context, args) {
6364
7030
  if (!run.value) {
6365
7031
  throw new CliError2("run show requires --run <id>.");
6366
7032
  }
6367
- const record = readAuthorityRun4(context.projectRoot, run.value);
7033
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6368
7034
  if (!record) {
6369
7035
  throw new CliError2(`Run not found: ${run.value}`, 2);
6370
7036
  }
@@ -6383,34 +7049,24 @@ async function executeRun(context, args) {
6383
7049
  if (!run.value) {
6384
7050
  throw new CliError2("run timeline requires --run <id>.");
6385
7051
  }
6386
- const timelinePath = resolve20(resolveAuthorityRunDir5(context.projectRoot, run.value), "timeline.jsonl");
6387
- const printEvents = () => {
6388
- const events2 = readJsonlFile4(timelinePath);
6389
- if (context.outputMode === "text") {
6390
- for (const event of events2) {
6391
- console.log(JSON.stringify(event));
6392
- }
6393
- }
6394
- return events2;
6395
- };
6396
- const events = printEvents();
7052
+ const renderer = createPiRunStreamRenderer();
7053
+ let cursor = null;
7054
+ const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
7055
+ const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
7056
+ cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
7057
+ if (context.outputMode === "text") {
7058
+ renderer.renderTimeline(events);
7059
+ }
6397
7060
  if (follow.value && context.outputMode === "text") {
6398
- let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
6399
7061
  while (true) {
6400
7062
  await Bun.sleep(1000);
6401
- if (!existsSync12(timelinePath))
6402
- continue;
6403
- const next = readFileSync9(timelinePath, "utf8");
6404
- if (next.length <= lastLength)
6405
- continue;
6406
- const delta = next.slice(lastLength);
6407
- lastLength = next.length;
6408
- for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
6409
- console.log(line);
6410
- }
7063
+ const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
7064
+ const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
7065
+ cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
7066
+ renderer.renderTimeline(nextEvents);
6411
7067
  }
6412
7068
  }
6413
- return { ok: true, group: "run", command, details: { runId: run.value, events } };
7069
+ return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
6414
7070
  }
6415
7071
  case "attach": {
6416
7072
  let pending = rest;
@@ -6431,14 +7087,26 @@ async function executeRun(context, args) {
6431
7087
  if (!runId) {
6432
7088
  throw new CliError2("run attach requires a run id.", 2);
6433
7089
  }
7090
+ let steered = false;
7091
+ const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
7092
+ if (shouldTryPiAttach && messageOption.value?.trim()) {
7093
+ await steerRunViaServer(context, runId, messageOption.value.trim());
7094
+ steered = true;
7095
+ }
7096
+ if (shouldTryPiAttach) {
7097
+ const piSession = await launchPiRigSession(context, { runId });
7098
+ if (piSession.launched) {
7099
+ return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
7100
+ }
7101
+ }
6434
7102
  const attached = await attachRunOperatorView(context, {
6435
7103
  runId,
6436
- message: messageOption.value ?? null,
7104
+ message: shouldTryPiAttach ? null : messageOption.value ?? null,
6437
7105
  once: once.value,
6438
7106
  follow: follow.value,
6439
7107
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
6440
7108
  });
6441
- return { ok: true, group: "run", command, details: attached };
7109
+ return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
6442
7110
  }
6443
7111
  case "status": {
6444
7112
  requireNoExtraArgs(rest, "bun run rig run status");
@@ -6448,17 +7116,19 @@ async function executeRun(context, args) {
6448
7116
  }
6449
7117
  return { ok: true, group: "run", command };
6450
7118
  }
6451
- const summary = runStatus(context.projectRoot, runtimeContext);
7119
+ const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
7120
+ const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
7121
+ const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
6452
7122
  if (context.outputMode === "text") {
6453
- console.log(`Active runs: ${summary.activeRuns.length}`);
6454
- for (const run of summary.activeRuns) {
6455
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
7123
+ console.log(`Active runs: ${activeRuns.length}`);
7124
+ for (const run of activeRuns) {
7125
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6456
7126
  }
6457
- if (summary.recentRuns.length > 0) {
7127
+ if (recentRuns.length > 0) {
6458
7128
  console.log("");
6459
7129
  console.log("Recent runs:");
6460
- for (const run of summary.recentRuns) {
6461
- console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.taskId ?? run.title}`);
7130
+ for (const run of recentRuns) {
7131
+ console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
6462
7132
  }
6463
7133
  }
6464
7134
  }
@@ -6545,6 +7215,20 @@ async function executeRun(context, args) {
6545
7215
  }
6546
7216
  return { ok: true, group: "run", command, details: resumed };
6547
7217
  }
7218
+ case "restart": {
7219
+ requireNoExtraArgs(rest, "bun run rig run restart");
7220
+ if (context.dryRun) {
7221
+ if (context.outputMode === "text") {
7222
+ console.log("[dry-run] rig run restart");
7223
+ }
7224
+ return { ok: true, group: "run", command };
7225
+ }
7226
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
7227
+ if (context.outputMode === "text") {
7228
+ console.log(`Restarted run: ${restarted.runId}`);
7229
+ }
7230
+ return { ok: true, group: "run", command, details: restarted };
7231
+ }
6548
7232
  case "stop": {
6549
7233
  const runOption = takeOption(rest, "--run");
6550
7234
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -6602,7 +7286,7 @@ async function executeServer(context, args, options) {
6602
7286
  const authTokenResult = takeOption(pending, "--auth-token");
6603
7287
  pending = authTokenResult.rest;
6604
7288
  requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
6605
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "start"];
7289
+ const commandParts = ["rig-server", "start"];
6606
7290
  if (hostResult.value) {
6607
7291
  commandParts.push("--host", hostResult.value);
6608
7292
  }
@@ -6625,7 +7309,7 @@ async function executeServer(context, args, options) {
6625
7309
  const eventResult = takeOption(pending, "--event");
6626
7310
  pending = eventResult.rest;
6627
7311
  requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
6628
- const commandParts = ["bun", "run", "packages/server/src/server.ts", "notify-test"];
7312
+ const commandParts = ["rig-server", "notify-test"];
6629
7313
  if (eventResult.value) {
6630
7314
  commandParts.push("--event", eventResult.value);
6631
7315
  }
@@ -6686,10 +7370,10 @@ async function executeServer(context, args, options) {
6686
7370
  }
6687
7371
 
6688
7372
  // packages/cli/src/commands/task.ts
6689
- import { readFileSync as readFileSync10 } from "fs";
6690
- import { spawnSync as spawnSync4 } from "child_process";
6691
- import { createInterface as createInterface4 } from "readline/promises";
6692
- import { resolve as resolve21 } from "path";
7373
+ import { readFileSync as readFileSync9 } from "fs";
7374
+ import { spawnSync as spawnSync3 } from "child_process";
7375
+ import { resolve as resolve20 } from "path";
7376
+ import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
6693
7377
  import {
6694
7378
  taskArtifactDir,
6695
7379
  taskArtifacts,
@@ -6707,19 +7391,10 @@ import {
6707
7391
  } from "@rig/runtime/control-plane/native/task-ops";
6708
7392
 
6709
7393
  // packages/cli/src/commands/_task-picker.ts
6710
- import { createInterface as createInterface3 } from "readline/promises";
6711
- function taskId(task) {
7394
+ import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
7395
+ function taskId2(task) {
6712
7396
  return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6713
7397
  }
6714
- function taskTitle(task) {
6715
- return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6716
- }
6717
- function taskStatus(task) {
6718
- return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6719
- }
6720
- function renderTaskPickerRows(tasks) {
6721
- return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6722
- }
6723
7398
  async function selectTaskWithTextPicker(tasks, io = {}) {
6724
7399
  if (tasks.length === 0)
6725
7400
  return null;
@@ -6729,25 +7404,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
6729
7404
  if (!isTty) {
6730
7405
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
6731
7406
  }
6732
- const prompt = io.prompt ?? (async (question) => {
6733
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6734
- try {
6735
- return await rl.question(question);
6736
- } finally {
6737
- rl.close();
7407
+ if (io.prompt || io.renderer) {
7408
+ const prompt = io.prompt ?? promptForTaskSelection;
7409
+ const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
7410
+ `) };
7411
+ renderer.writeLine("Select Rig task:");
7412
+ for (const row of renderTaskPickerRows(tasks))
7413
+ renderer.writeLine(` ${row}`);
7414
+ const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
7415
+ if (!answer2)
7416
+ return null;
7417
+ if (/^\d+$/.test(answer2)) {
7418
+ const index2 = Number.parseInt(answer2, 10) - 1;
7419
+ return tasks[index2] ?? null;
6738
7420
  }
7421
+ return tasks.find((task) => taskId2(task) === answer2) ?? null;
7422
+ }
7423
+ const options = tasks.map((task, index2) => ({
7424
+ value: `${index2}`,
7425
+ label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
7426
+ hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
7427
+ }));
7428
+ const answer = await select2({
7429
+ message: "Select Rig task",
7430
+ options
6739
7431
  });
6740
- console.log("Select Rig task:");
6741
- for (const row of renderTaskPickerRows(tasks))
6742
- console.log(` ${row}`);
6743
- const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
6744
- if (!answer)
7432
+ if (isCancel2(answer)) {
7433
+ cancel2("No task selected.");
6745
7434
  return null;
6746
- if (/^\d+$/.test(answer)) {
6747
- const index = Number.parseInt(answer, 10) - 1;
6748
- return tasks[index] ?? null;
6749
7435
  }
6750
- return tasks.find((task) => taskId(task) === answer) ?? null;
7436
+ const index = Number.parseInt(String(answer), 10);
7437
+ return Number.isFinite(index) ? tasks[index] ?? null : null;
6751
7438
  }
6752
7439
 
6753
7440
  // packages/cli/src/commands/task.ts
@@ -6825,7 +7512,7 @@ function normalizePrMode(value) {
6825
7512
  throw new CliError2("--pr must be auto, ask, or off.", 2);
6826
7513
  }
6827
7514
  function detectLocalDirtyState(projectRoot) {
6828
- const result = spawnSync4("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
7515
+ const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
6829
7516
  if (result.status !== 0)
6830
7517
  return { dirty: false, modified: 0, untracked: 0, lines: [] };
6831
7518
  const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -6859,13 +7546,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
6859
7546
  if (explicit)
6860
7547
  return { mode: explicit, state };
6861
7548
  if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
6862
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
6863
- try {
6864
- const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
6865
- return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
6866
- } finally {
6867
- rl.close();
7549
+ const answer = await confirm2({
7550
+ message: "Include current uncommitted changes in run baseline?",
7551
+ initialValue: false
7552
+ });
7553
+ if (isCancel3(answer)) {
7554
+ cancel3("Run cancelled.");
7555
+ throw new CliError2("Run cancelled by user.", 1);
6868
7556
  }
7557
+ return { mode: answer ? "dirty-snapshot" : "head", state };
6869
7558
  }
6870
7559
  return { mode: "head", state };
6871
7560
  }
@@ -6900,10 +7589,7 @@ function summarizeTask(task, options = {}) {
6900
7589
  };
6901
7590
  }
6902
7591
  function printTaskSummary(task) {
6903
- const id = readTaskId(task) ?? "<unknown>";
6904
- const title = readTaskString(task, "title") ?? "Untitled task";
6905
- const status = readTaskString(task, "status") ?? "unknown";
6906
- console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
7592
+ console.log(formatTaskList([task]));
6907
7593
  }
6908
7594
  async function validatorRegistryForTaskCommands(projectRoot) {
6909
7595
  return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
@@ -6921,16 +7607,8 @@ async function executeTask(context, args, options) {
6921
7607
  requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
6922
7608
  const tasks = await listWorkspaceTasksViaServer(context, filters);
6923
7609
  if (context.outputMode === "text") {
6924
- if (tasks.length === 0) {
6925
- console.log("No matching tasks.");
6926
- } else {
6927
- for (const task of tasks) {
6928
- if (rawResult.value)
6929
- console.log(JSON.stringify(summarizeTask(task, { raw: true })));
6930
- else
6931
- printTaskSummary(task);
6932
- }
6933
- }
7610
+ const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
7611
+ console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
6934
7612
  }
6935
7613
  return {
6936
7614
  ok: true,
@@ -6944,12 +7622,12 @@ async function executeTask(context, args, options) {
6944
7622
  const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
6945
7623
  const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
6946
7624
  requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
6947
- const taskId2 = normalizeTaskRunTaskId(taskOption.value ?? positional);
6948
- if (!taskId2)
7625
+ const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
7626
+ if (!taskId3)
6949
7627
  throw new CliError2("task show requires a task id.", 2);
6950
- const task = await getWorkspaceTaskViaServer(context, taskId2);
7628
+ const task = await getWorkspaceTaskViaServer(context, taskId3);
6951
7629
  if (!task)
6952
- throw new CliError2(`Task not found: ${taskId2}`, 3);
7630
+ throw new CliError2(`Task not found: ${taskId3}`, 3);
6953
7631
  const summary = summarizeTask(task, { raw: true });
6954
7632
  if (context.outputMode === "text")
6955
7633
  console.log(JSON.stringify(summary, null, 2));
@@ -7020,7 +7698,7 @@ async function executeTask(context, args, options) {
7020
7698
  const fileFlag = takeOption(rest.slice(1), "--file");
7021
7699
  let content;
7022
7700
  if (fileFlag.value) {
7023
- content = readFileSync10(resolve21(context.projectRoot, fileFlag.value), "utf-8");
7701
+ content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
7024
7702
  } else {
7025
7703
  content = await readStdin();
7026
7704
  }
@@ -7149,16 +7827,23 @@ async function executeTask(context, args, options) {
7149
7827
  });
7150
7828
  let attachDetails = null;
7151
7829
  if (!detachResult.value && context.outputMode === "text") {
7152
- console.log(`Run submitted: ${submitted.runId}`);
7153
- if (selectedTask) {
7154
- printTaskSummary(selectedTask);
7830
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7831
+ if (runtimeAdapter === "pi") {
7832
+ const piSession = await launchPiRigSession(context, {
7833
+ runId: submitted.runId,
7834
+ taskId: selectedTaskId,
7835
+ title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7836
+ runtimeAdapter
7837
+ });
7838
+ attachDetails = { mode: "pi", ...piSession };
7839
+ if (!piSession.launched) {
7840
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7841
+ }
7842
+ } else {
7843
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7155
7844
  }
7156
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7157
7845
  } else if (context.outputMode === "text") {
7158
- console.log(`Run submitted: ${submitted.runId}`);
7159
- if (selectedTask) {
7160
- printTaskSummary(selectedTask);
7161
- }
7846
+ console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7162
7847
  }
7163
7848
  return {
7164
7849
  ok: true,
@@ -7241,9 +7926,9 @@ async function executeTask(context, args, options) {
7241
7926
  }
7242
7927
 
7243
7928
  // packages/cli/src/commands/task-run-driver.ts
7244
- import { copyFileSync as copyFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7245
- import { resolve as resolve22 } from "path";
7246
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
7929
+ import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7930
+ import { resolve as resolve21 } from "path";
7931
+ import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
7247
7932
  import { createInterface as createLineInterface } from "readline";
7248
7933
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7249
7934
  import {
@@ -7268,15 +7953,31 @@ import {
7268
7953
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
7269
7954
  import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
7270
7955
  import {
7271
- buildTaskRunLifecycleComment,
7272
- updateConfiguredTaskSourceTask
7956
+ buildTaskRunLifecycleComment
7273
7957
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
7274
7958
  import {
7275
7959
  closeIssueAfterMergedPr,
7276
7960
  commitRunChanges,
7277
7961
  runPrAutomation
7278
7962
  } from "@rig/runtime/control-plane/native/pr-automation";
7963
+ function looksLikeGitHubToken(value) {
7964
+ const token = value?.trim();
7965
+ if (!token)
7966
+ return false;
7967
+ return /^(gh[opusr]_|github_pat_)/.test(token);
7968
+ }
7969
+ function githubBridgeEnv(token) {
7970
+ const clean = token?.trim();
7971
+ if (!clean)
7972
+ return {};
7973
+ return {
7974
+ RIG_GITHUB_TOKEN: clean,
7975
+ GITHUB_TOKEN: clean,
7976
+ GH_TOKEN: clean
7977
+ };
7978
+ }
7279
7979
  function buildPiRigBridgeEnv(input) {
7980
+ const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
7280
7981
  return {
7281
7982
  RIG_PROJECT_ROOT: input.projectRoot,
7282
7983
  PROJECT_RIG_ROOT: input.projectRoot,
@@ -7286,11 +7987,11 @@ function buildPiRigBridgeEnv(input) {
7286
7987
  RIG_RUNTIME_ADAPTER: "pi",
7287
7988
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7288
7989
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7289
- ...input.githubToken ? { RIG_GITHUB_TOKEN: input.githubToken } : {}
7990
+ ...githubBridgeEnv(githubToken)
7290
7991
  };
7291
7992
  }
7292
7993
  function runGitSync(cwd, args, input) {
7293
- const result = spawnSync5("git", [...args], {
7994
+ const result = spawnSync4("git", [...args], {
7294
7995
  cwd,
7295
7996
  input,
7296
7997
  encoding: "utf8",
@@ -7308,12 +8009,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
7308
8009
  return 0;
7309
8010
  let copied = 0;
7310
8011
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
7311
- const sourcePath = resolve22(sourceRoot, relativePath);
7312
- const targetPath = resolve22(targetRoot, relativePath);
8012
+ const sourcePath = resolve21(sourceRoot, relativePath);
8013
+ const targetPath = resolve21(targetRoot, relativePath);
7313
8014
  try {
7314
8015
  if (!statSync2(sourcePath).isFile())
7315
8016
  continue;
7316
- mkdirSync8(resolve22(targetPath, ".."), { recursive: true });
8017
+ mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
7317
8018
  copyFileSync3(sourcePath, targetPath);
7318
8019
  copied += 1;
7319
8020
  } catch {}
@@ -7352,7 +8053,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
7352
8053
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
7353
8054
  return {
7354
8055
  RIG_BASELINE_MODE: "dirty-snapshot",
7355
- RIG_DIRTY_BASELINE_READY_FILE: resolve22(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
8056
+ RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7356
8057
  };
7357
8058
  }
7358
8059
  function positiveInt(value, fallback) {
@@ -7360,7 +8061,7 @@ function positiveInt(value, fallback) {
7360
8061
  }
7361
8062
  function resolveTaskRunAutomationLimits(config, env = process.env) {
7362
8063
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
7363
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
8064
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
7364
8065
  return {
7365
8066
  maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
7366
8067
  maxPrFixIterations: configuredPrFixIterations
@@ -7455,7 +8156,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
7455
8156
  }
7456
8157
  function createCommandRunner(binary) {
7457
8158
  return async (args, options) => {
7458
- const child = spawn2(binary, [...args], {
8159
+ const child = spawn3(binary, [...args], {
7459
8160
  cwd: options?.cwd,
7460
8161
  stdio: ["ignore", "pipe", "pipe"]
7461
8162
  });
@@ -7463,9 +8164,9 @@ function createCommandRunner(binary) {
7463
8164
  const stderrChunks = [];
7464
8165
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7465
8166
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7466
- return await new Promise((resolve23) => {
7467
- child.once("error", (error) => resolve23({ exitCode: 1, stderr: error.message }));
7468
- child.once("close", (code) => resolve23({
8167
+ return await new Promise((resolve22) => {
8168
+ child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
8169
+ child.once("close", (code) => resolve22({
7469
8170
  exitCode: code ?? 1,
7470
8171
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
7471
8172
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -7485,12 +8186,13 @@ async function resolvePostValidationBranch(input) {
7485
8186
  return input.configuredBranch;
7486
8187
  }
7487
8188
  async function runTaskRunPostValidationLifecycle(input) {
7488
- const taskId2 = input.taskId?.trim();
7489
- if (!taskId2) {
8189
+ const taskId3 = input.taskId?.trim();
8190
+ if (!taskId3) {
7490
8191
  return { status: "skipped" };
7491
8192
  }
7492
- const config = input.config ?? {};
7493
- const prMode = config.pr?.mode ?? "auto";
8193
+ const configInput = input.config ?? null;
8194
+ const config = configInput ?? {};
8195
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
7494
8196
  if (prMode === "off" || prMode === "ask") {
7495
8197
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
7496
8198
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -7506,7 +8208,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7506
8208
  gitCommand
7507
8209
  });
7508
8210
  const prAutomation = input.prAutomation ?? runPrAutomation;
7509
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
8211
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
7510
8212
  const stage = input.appendStage ?? (() => {
7511
8213
  return;
7512
8214
  });
@@ -7524,7 +8226,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7524
8226
  stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
7525
8227
  const commit = await commitRunChanges({
7526
8228
  cwd: workspace,
7527
- message: `rig: complete task ${taskId2}`,
8229
+ message: `rig: complete task ${taskId3}`,
7528
8230
  command: gitCommand
7529
8231
  });
7530
8232
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
@@ -7532,13 +8234,15 @@ async function runTaskRunPostValidationLifecycle(input) {
7532
8234
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
7533
8235
  const pr = await prAutomation({
7534
8236
  projectRoot: workspace,
7535
- taskId: taskId2,
8237
+ taskId: taskId3,
7536
8238
  runId: input.runId,
7537
8239
  branch,
7538
8240
  config,
7539
8241
  sourceTask: input.sourceTask,
7540
8242
  uploadedSnapshot: input.uploadedSnapshot,
8243
+ artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
7541
8244
  command: ghCommand,
8245
+ gitCommand,
7542
8246
  steerPi,
7543
8247
  lifecycle: {
7544
8248
  onPrOpened: async ({ prUrl }) => {
@@ -7546,7 +8250,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7546
8250
  try {
7547
8251
  if (shouldWriteDriverIssueUpdate(config, "under_review")) {
7548
8252
  await updateTaskSource(input.projectRoot, {
7549
- taskId: taskId2,
8253
+ taskId: taskId3,
7550
8254
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7551
8255
  update: {
7552
8256
  status: "under_review",
@@ -7576,7 +8280,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7576
8280
  `), "reviewing", "error");
7577
8281
  if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
7578
8282
  await updateTaskSource(input.projectRoot, {
7579
- taskId: taskId2,
8283
+ taskId: taskId3,
7580
8284
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7581
8285
  update: {
7582
8286
  status: "ci_fixing",
@@ -7587,8 +8291,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7587
8291
  runtimeWorkspace: input.runtimeWorkspace ?? null
7588
8292
  })
7589
8293
  }
7590
- }).catch(() => {
7591
- return;
7592
8294
  });
7593
8295
  }
7594
8296
  },
@@ -7596,7 +8298,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7596
8298
  stage("Merge", prUrl, "running", "tool");
7597
8299
  if (shouldWriteDriverIssueUpdate(config, "merging")) {
7598
8300
  await updateTaskSource(input.projectRoot, {
7599
- taskId: taskId2,
8301
+ taskId: taskId3,
7600
8302
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7601
8303
  update: {
7602
8304
  status: "merging",
@@ -7607,8 +8309,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7607
8309
  runtimeWorkspace: input.runtimeWorkspace ?? null
7608
8310
  })
7609
8311
  }
7610
- }).catch(() => {
7611
- return;
7612
8312
  });
7613
8313
  }
7614
8314
  },
@@ -7625,7 +8325,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7625
8325
  stage("Needs attention", detail, "needs_attention", "error");
7626
8326
  if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
7627
8327
  await updateTaskSource(input.projectRoot, {
7628
- taskId: taskId2,
8328
+ taskId: taskId3,
7629
8329
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7630
8330
  update: {
7631
8331
  status: "needs_attention",
@@ -7637,8 +8337,17 @@ async function runTaskRunPostValidationLifecycle(input) {
7637
8337
  errorText: detail
7638
8338
  })
7639
8339
  }
7640
- }).catch(() => {
7641
- return;
8340
+ }).catch((error) => {
8341
+ try {
8342
+ appendRunLog(input.projectRoot, input.runId, {
8343
+ id: `log:${input.runId}:task-source-needs-attention-update`,
8344
+ title: "Task source needs-attention update failed",
8345
+ detail: error instanceof Error ? error.message : String(error),
8346
+ tone: "error",
8347
+ status: "needs_attention",
8348
+ createdAt: new Date().toISOString()
8349
+ });
8350
+ } catch {}
7642
8351
  });
7643
8352
  }
7644
8353
  return { status: "needs_attention", pr };
@@ -7646,7 +8355,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7646
8355
  if (shouldWriteDriverIssueUpdate(config, "closed")) {
7647
8356
  await closeIssueAfterMergedPr({
7648
8357
  projectRoot: input.projectRoot,
7649
- taskId: taskId2,
8358
+ taskId: taskId3,
7650
8359
  runId: input.runId,
7651
8360
  prUrl: pr.prUrl,
7652
8361
  sourceTask: input.sourceTask,
@@ -7656,12 +8365,12 @@ async function runTaskRunPostValidationLifecycle(input) {
7656
8365
  stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
7657
8366
  return { status: "completed", pr };
7658
8367
  }
7659
- function summarizeValidationFailure(projectRoot, taskId2) {
7660
- if (!taskId2) {
8368
+ function summarizeValidationFailure(projectRoot, taskId3) {
8369
+ if (!taskId3) {
7661
8370
  return null;
7662
8371
  }
7663
- for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId2)) {
7664
- const summary = readJsonFile3(resolve22(artifactDir, "validation-summary.json"), null);
8372
+ for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
8373
+ const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
7665
8374
  if (!summary || summary.status !== "fail") {
7666
8375
  continue;
7667
8376
  }
@@ -7742,9 +8451,9 @@ function readTaskRunAcceptedArtifactState(input) {
7742
8451
  if (!input.taskId || !input.workspaceDir) {
7743
8452
  return { accepted: false, reason: null };
7744
8453
  }
7745
- const artifactDir = resolve22(input.workspaceDir, "artifacts", input.taskId);
7746
- const reviewStatusPath = resolve22(artifactDir, "review-status.txt");
7747
- const taskResultPath = resolve22(artifactDir, "task-result.json");
8454
+ const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
8455
+ const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
8456
+ const taskResultPath = resolve21(artifactDir, "task-result.json");
7748
8457
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7749
8458
  if (reviewStatus !== "APPROVED") {
7750
8459
  return { accepted: false, reason: null };
@@ -7781,12 +8490,12 @@ function resolveTaskRunRetryContext(input) {
7781
8490
  if (!input.taskId || !input.workspaceDir) {
7782
8491
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
7783
8492
  }
7784
- const artifactDir = resolve22(input.workspaceDir, "artifacts", input.taskId);
7785
- const reviewStatePath = resolve22(artifactDir, "review-state.json");
7786
- const reviewFeedbackPath = resolve22(artifactDir, "review-feedback.md");
7787
- const reviewStatusPath = resolve22(artifactDir, "review-status.txt");
7788
- const failedApproachesPath = resolve22(input.workspaceDir, ".rig", "state", "failed_approaches.md");
7789
- const validationSummaryPath = resolve22(artifactDir, "validation-summary.json");
8493
+ const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
8494
+ const reviewStatePath = resolve21(artifactDir, "review-state.json");
8495
+ const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
8496
+ const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
8497
+ const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
8498
+ const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
7790
8499
  const reviewState = readJsonFile3(reviewStatePath, null);
7791
8500
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7792
8501
  const reviewRejected = isTaskRunReviewRejected(reviewState);
@@ -7840,12 +8549,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
7840
8549
  }
7841
8550
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
7842
8551
  }
8552
+ function appendAssistantTimelineFromRecord(input) {
8553
+ let nextAssistantText = input.assistantText;
8554
+ if (input.record.type === "message_update") {
8555
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
8556
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
8557
+ nextAssistantText += assistantMessageEvent.delta;
8558
+ }
8559
+ } else if (input.record.type === "stream_event") {
8560
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
8561
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
8562
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
8563
+ nextAssistantText += delta.text;
8564
+ }
8565
+ } else if (input.record.type === "assistant") {
8566
+ const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
8567
+ const content = Array.isArray(message2.content) ? message2.content : [];
8568
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
8569
+ if (fullText.length > nextAssistantText.length)
8570
+ nextAssistantText = fullText;
8571
+ }
8572
+ if (nextAssistantText !== input.assistantText) {
8573
+ appendRunTimeline(input.projectRoot, input.runId, {
8574
+ id: input.messageId,
8575
+ type: "assistant_message",
8576
+ text: nextAssistantText,
8577
+ state: "streaming",
8578
+ createdAt: new Date().toISOString()
8579
+ });
8580
+ }
8581
+ return nextAssistantText;
8582
+ }
8583
+ function appendPiToolTimelineFromRecord(input) {
8584
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8585
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
8586
+ return false;
8587
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
8588
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
8589
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
8590
+ appendRunTimeline(input.projectRoot, input.runId, {
8591
+ id: `tool:${toolCallId}:${type}`,
8592
+ type,
8593
+ toolName,
8594
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
8595
+ createdAt: new Date().toISOString()
8596
+ });
8597
+ return true;
8598
+ }
8599
+ function isNonRenderablePiProtocolRecord(record) {
8600
+ const type = typeof record.type === "string" ? record.type : "";
8601
+ 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");
8602
+ }
8603
+ function appendToolTimelineFromLog(input) {
8604
+ const title = typeof input.log.title === "string" ? input.log.title : "";
8605
+ if (title !== "Tool activity")
8606
+ return;
8607
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
8608
+ 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;
8609
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
8610
+ appendRunTimeline(input.projectRoot, input.runId, {
8611
+ id: `tool:${logId}`,
8612
+ type: "tool_execution_update",
8613
+ toolName,
8614
+ status: typeof input.log.status === "string" ? input.log.status : "running",
8615
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
8616
+ payload,
8617
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
8618
+ });
8619
+ }
7843
8620
  function readTaskRunReviewStatus(reviewStatusPath) {
7844
- if (!existsSync13(reviewStatusPath)) {
8621
+ if (!existsSync12(reviewStatusPath)) {
7845
8622
  return null;
7846
8623
  }
7847
8624
  try {
7848
- const status = readFileSync11(reviewStatusPath, "utf8").trim().toUpperCase();
8625
+ const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
7849
8626
  return status === "APPROVED" || status === "REJECTED" ? status : null;
7850
8627
  } catch {
7851
8628
  return null;
@@ -7863,14 +8640,45 @@ function isTaskRunReviewRejected(reviewState) {
7863
8640
  function runSourceTaskIdentity(sourceTask) {
7864
8641
  return sourceTask;
7865
8642
  }
7866
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
7867
- if (!taskId2)
8643
+ function sourceTaskIssueNodeId(sourceTask) {
8644
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
8645
+ return null;
8646
+ const record = sourceTask;
8647
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
8648
+ if (direct?.trim())
8649
+ return direct.trim();
8650
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
8651
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
8652
+ }
8653
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
8654
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
8655
+ id: input.taskId,
8656
+ ...input.update.status ? { status: input.update.status } : {},
8657
+ ...input.update.comment ? { comment: input.update.comment } : {},
8658
+ ...input.update.title ? { title: input.update.title } : {},
8659
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
8660
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
8661
+ });
8662
+ if (serverResult.ok === false) {
8663
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
8664
+ }
8665
+ return {
8666
+ updated: serverResult.ok !== false,
8667
+ taskId: input.taskId,
8668
+ status: input.update.status,
8669
+ source: "server",
8670
+ sourceKind: "server",
8671
+ projectSync: serverResult.projectSync
8672
+ };
8673
+ };
8674
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
8675
+ if (!taskId3)
7868
8676
  return;
7869
8677
  const config = await loadTaskRunAutomationConfig(projectRoot);
7870
8678
  if (!shouldWriteDriverIssueUpdate(config, status))
7871
8679
  return;
7872
8680
  const result = await updateTaskSource(projectRoot, {
7873
- taskId: taskId2,
8681
+ taskId: taskId3,
7874
8682
  sourceTask: runSourceTaskIdentity(sourceTask),
7875
8683
  update: {
7876
8684
  status,
@@ -7889,14 +8697,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
7889
8697
  throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
7890
8698
  }
7891
8699
  }
7892
- function readRunSourceTaskContract(projectRoot, runId, taskId2) {
8700
+ function readRunSourceTaskContract(projectRoot, runId, taskId3) {
7893
8701
  const run = readAuthorityRun5(projectRoot, runId);
7894
8702
  const raw = run?.sourceTask;
7895
8703
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7896
8704
  return null;
7897
8705
  }
7898
8706
  const record = raw;
7899
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId2?.trim();
8707
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
7900
8708
  if (!id) {
7901
8709
  return null;
7902
8710
  }
@@ -7928,6 +8736,9 @@ function stringArrayField(record, key) {
7928
8736
  }
7929
8737
  async function executeRigOwnedTaskRun(context, input) {
7930
8738
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
8739
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
8740
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
8741
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7931
8742
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7932
8743
  let prompt = buildRunPrompt({
7933
8744
  projectRoot: context.projectRoot,
@@ -7983,14 +8794,14 @@ async function executeRigOwnedTaskRun(context, input) {
7983
8794
  taskId: runtimeTaskId,
7984
8795
  createdAt: startedAt,
7985
8796
  runtimeAdapter: input.runtimeAdapter,
7986
- status: "created"
8797
+ status: resumeMode ? "preparing" : "created"
7987
8798
  });
7988
8799
  patchAuthorityRun(context.projectRoot, input.runId, {
7989
8800
  status: "preparing",
7990
8801
  startedAt,
7991
8802
  completedAt: null,
7992
8803
  errorText: null,
7993
- artifactRoot: null,
8804
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
7994
8805
  runtimeAdapter: input.runtimeAdapter,
7995
8806
  runtimeMode: input.runtimeMode,
7996
8807
  interactionMode: input.interactionMode,
@@ -8006,9 +8817,9 @@ async function executeRigOwnedTaskRun(context, input) {
8006
8817
  detail: input.taskId ?? input.title ?? runtimeTaskId
8007
8818
  });
8008
8819
  appendRunLog(context.projectRoot, input.runId, {
8009
- id: `log:${input.runId}:start`,
8010
- title: "Rig task run started",
8011
- detail: input.taskId ?? input.title ?? runtimeTaskId,
8820
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
8821
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
8822
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
8012
8823
  tone: "info",
8013
8824
  status: "preparing",
8014
8825
  createdAt: startedAt
@@ -8029,15 +8840,15 @@ async function executeRigOwnedTaskRun(context, input) {
8029
8840
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
8030
8841
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
8031
8842
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
8032
- const planningArtifactPath = resolve22("artifacts", runtimeTaskId, "implementation-plan.md");
8843
+ const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
8033
8844
  const persistedPlanning = {
8034
8845
  ...planningClassification,
8035
8846
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
8036
8847
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
8037
8848
  classifiedAt: new Date().toISOString()
8038
8849
  };
8039
- mkdirSync8(resolve22(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8040
- writeFileSync6(resolve22(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8850
+ mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8851
+ writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8041
8852
  `, "utf8");
8042
8853
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
8043
8854
  prompt = `${prompt}
@@ -8086,11 +8897,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8086
8897
  let reviewAction;
8087
8898
  let verificationStarted = false;
8088
8899
  let reviewStarted = false;
8089
- let latestRuntimeWorkspace = null;
8090
- let latestSessionDir = null;
8091
- let latestLogsDir = null;
8900
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
8901
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
8902
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
8092
8903
  let latestProviderCommand = null;
8093
- let latestRuntimeBranch = null;
8904
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8094
8905
  let snapshotSidecarPromise = null;
8095
8906
  let dirtyBaselineApplied = false;
8096
8907
  const childEnv = {
@@ -8108,7 +8919,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8108
8919
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
8109
8920
  RIG_SERVER_RUN_ID: input.runId
8110
8921
  },
8111
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
8922
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
8923
+ ...resumeMode ? {
8924
+ RIG_RUN_RESUME: "1",
8925
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
8926
+ } : {}
8112
8927
  };
8113
8928
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
8114
8929
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -8166,10 +8981,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8166
8981
  patchAuthorityRun(context.projectRoot, input.runId, {
8167
8982
  status: "running",
8168
8983
  worktreePath: latestRuntimeWorkspace,
8169
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve22(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8984
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8170
8985
  logRoot: latestLogsDir,
8171
- sessionPath: latestSessionDir ? resolve22(latestSessionDir, "session.json") : null,
8172
- sessionLogPath: latestLogsDir ? resolve22(latestLogsDir, "agent-stdout.log") : null,
8986
+ sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
8987
+ sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
8173
8988
  branch: runtimeId
8174
8989
  });
8175
8990
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -8177,7 +8992,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8177
8992
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
8178
8993
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
8179
8994
  if (readyFile) {
8180
- mkdirSync8(resolve22(readyFile, ".."), { recursive: true });
8995
+ mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
8181
8996
  writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
8182
8997
  `, "utf8");
8183
8998
  }
@@ -8310,6 +9125,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8310
9125
  try {
8311
9126
  const record = JSON.parse(trimmed);
8312
9127
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9128
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
9129
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9130
+ return;
9131
+ }
8313
9132
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
8314
9133
  runId: input.runId,
8315
9134
  record,
@@ -8326,7 +9145,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8326
9145
  if (providerLogs.length > 0) {
8327
9146
  for (const providerLog of providerLogs) {
8328
9147
  appendRunLog(context.projectRoot, input.runId, providerLog);
9148
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
8329
9149
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9150
+ if (providerLog.title === "Tool activity")
9151
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8330
9152
  }
8331
9153
  }
8332
9154
  if (input.runtimeAdapter === "codex") {
@@ -8378,6 +9200,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8378
9200
  return;
8379
9201
  }
8380
9202
  }
9203
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
9204
+ return;
9205
+ }
8381
9206
  if (record.type === "assistant") {
8382
9207
  const message2 = record.message && typeof record.message === "object" ? record.message : record;
8383
9208
  const content = Array.isArray(message2.content) ? message2.content : [];
@@ -8414,9 +9239,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8414
9239
  let reviewFailureDetail = null;
8415
9240
  const stderrLines = [];
8416
9241
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8417
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
9242
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
9243
+ appendRunLog(context.projectRoot, input.runId, {
9244
+ id: `log:${input.runId}:resume-closeout-phase`,
9245
+ title: "Resume continuing from closeout phase",
9246
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
9247
+ tone: "info",
9248
+ status: resumePreviousStatus,
9249
+ createdAt: new Date().toISOString()
9250
+ });
9251
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
9252
+ exit = { code: 0, signal: null };
9253
+ } else if (resumeMode && latestRuntimeWorkspace) {
9254
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
9255
+ taskId: input.taskId ?? runtimeTaskId,
9256
+ workspaceDir: latestRuntimeWorkspace
9257
+ });
9258
+ if (acceptedArtifactState.accepted) {
9259
+ appendRunLog(context.projectRoot, input.runId, {
9260
+ id: `log:${input.runId}:resume-accepted-artifacts`,
9261
+ title: "Resume found accepted artifacts; continuing closeout",
9262
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
9263
+ tone: "info",
9264
+ status: "validating",
9265
+ createdAt: new Date().toISOString()
9266
+ });
9267
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
9268
+ exit = { code: 0, signal: null };
9269
+ }
9270
+ }
9271
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8418
9272
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8419
- const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9273
+ const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8420
9274
  cwd: context.projectRoot,
8421
9275
  env: childEnv,
8422
9276
  stdio: ["pipe", "pipe", "pipe"]
@@ -8455,7 +9309,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8455
9309
  let acceptedArtifactObservedAt = null;
8456
9310
  let acceptedArtifactPollTimer = null;
8457
9311
  let acceptedArtifactKillTimer = null;
8458
- const attemptExit = await new Promise((resolve23) => {
9312
+ const attemptExit = await new Promise((resolve22) => {
8459
9313
  let settled = false;
8460
9314
  const settle = (result) => {
8461
9315
  if (settled)
@@ -8463,7 +9317,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8463
9317
  settled = true;
8464
9318
  if (acceptedArtifactPollTimer)
8465
9319
  clearInterval(acceptedArtifactPollTimer);
8466
- resolve23(result);
9320
+ resolve22(result);
8467
9321
  };
8468
9322
  const pollAcceptedArtifacts = () => {
8469
9323
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -8536,7 +9390,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8536
9390
  });
8537
9391
  for (const pendingLog of pendingLogs) {
8538
9392
  appendRunLog(context.projectRoot, input.runId, pendingLog);
9393
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
8539
9394
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9395
+ if (pendingLog.title === "Tool activity")
9396
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8540
9397
  }
8541
9398
  process.off("SIGTERM", forwardSigterm);
8542
9399
  if (attemptExit.error) {
@@ -8662,8 +9519,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8662
9519
  }
8663
9520
  if (planningClassification.planningRequired) {
8664
9521
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
8665
- const expectedPlanPath = resolve22(planWorkspace, planningArtifactPath);
8666
- if (!existsSync13(expectedPlanPath)) {
9522
+ const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
9523
+ if (!existsSync12(expectedPlanPath)) {
8667
9524
  const failedAt = new Date().toISOString();
8668
9525
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
8669
9526
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -8683,6 +9540,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8683
9540
  throw new CliError2(failureDetail, 1);
8684
9541
  }
8685
9542
  }
9543
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
9544
+ appendPiStageLog({
9545
+ projectRoot: context.projectRoot,
9546
+ runId: input.runId,
9547
+ stage: "Validate",
9548
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
9549
+ status: "completed"
9550
+ });
9551
+ if (verificationAction && !reviewStarted) {
9552
+ verificationAction.complete("Completion verification checks finished.");
9553
+ }
9554
+ if (!reviewAction) {
9555
+ promoteToReviewing("Server-owned closeout is queued.");
9556
+ }
9557
+ if (reviewAction) {
9558
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
9559
+ }
9560
+ const requestedAt = new Date().toISOString();
9561
+ patchAuthorityRun(context.projectRoot, input.runId, {
9562
+ status: "reviewing",
9563
+ completedAt: null,
9564
+ errorText: null,
9565
+ serverCloseout: {
9566
+ status: "pending",
9567
+ phase: "queued",
9568
+ requestedAt,
9569
+ updatedAt: requestedAt,
9570
+ runtimeWorkspace: latestRuntimeWorkspace,
9571
+ branch: latestRuntimeBranch,
9572
+ taskId: input.taskId ?? runtimeTaskId
9573
+ }
9574
+ });
9575
+ appendRunLog(context.projectRoot, input.runId, {
9576
+ id: `log:${input.runId}:server-closeout-requested`,
9577
+ title: "Server-owned closeout requested",
9578
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
9579
+ tone: "info",
9580
+ status: "reviewing",
9581
+ createdAt: requestedAt,
9582
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
9583
+ });
9584
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
9585
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
9586
+ await context.emitEvent("command.finished", {
9587
+ command: [
9588
+ "rig",
9589
+ "server",
9590
+ "task-run",
9591
+ ...input.taskId ? ["--task", input.taskId] : [],
9592
+ ...input.title ? ["--title", input.title] : []
9593
+ ],
9594
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
9595
+ exitCode: 0,
9596
+ durationMs: 0,
9597
+ startedAt,
9598
+ finishedAt: requestedAt
9599
+ });
9600
+ return;
9601
+ }
8686
9602
  const runPiPrFeedbackFix = async (message2) => {
8687
9603
  appendPiStageLog({
8688
9604
  projectRoot: context.projectRoot,
@@ -8701,7 +9617,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8701
9617
  });
8702
9618
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
8703
9619
  const feedbackCommand = buildHostAgentCommand(message2);
8704
- const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9620
+ const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
8705
9621
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
8706
9622
  env: childEnv,
8707
9623
  stdio: ["pipe", "pipe", "pipe"]
@@ -8710,11 +9626,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8710
9626
  child.stdin.write(message2);
8711
9627
  }
8712
9628
  child.stdin.end();
9629
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
9630
+ let feedbackAssistantText = "";
9631
+ const feedbackPendingToolUses = new Map;
8713
9632
  const stdout = createLineInterface({ input: child.stdout });
8714
9633
  stdout.on("line", (line) => {
8715
9634
  const trimmed = line.trim();
8716
9635
  if (!trimmed)
8717
9636
  return;
9637
+ try {
9638
+ const record = JSON.parse(trimmed);
9639
+ const providerLogs = buildClaudeLogsFromRecord({
9640
+ runId: input.runId,
9641
+ record,
9642
+ createdAtFallback: new Date().toISOString(),
9643
+ status: "reviewing",
9644
+ pendingToolUses: feedbackPendingToolUses
9645
+ });
9646
+ for (const providerLog of providerLogs) {
9647
+ appendRunLog(context.projectRoot, input.runId, providerLog);
9648
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
9649
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9650
+ if (providerLog.title === "Tool activity")
9651
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9652
+ }
9653
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
9654
+ projectRoot: context.projectRoot,
9655
+ runId: input.runId,
9656
+ messageId: feedbackAssistantMessageId,
9657
+ record,
9658
+ assistantText: feedbackAssistantText
9659
+ });
9660
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
9661
+ if (hadAssistantDelta) {
9662
+ feedbackAssistantText = nextFeedbackAssistantText;
9663
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9664
+ }
9665
+ if (providerLogs.length > 0 || hadAssistantDelta)
9666
+ return;
9667
+ } catch {}
8718
9668
  appendRunLog(context.projectRoot, input.runId, {
8719
9669
  id: nextRunLogId(),
8720
9670
  title: "Pi PR feedback fix output",
@@ -8740,10 +9690,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8740
9690
  });
8741
9691
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
8742
9692
  });
8743
- const exitCode = await new Promise((resolve23) => {
8744
- child.once("error", () => resolve23(1));
8745
- child.once("close", (code) => resolve23(code ?? 1));
9693
+ const exitCode = await new Promise((resolve22) => {
9694
+ child.once("error", () => resolve22(1));
9695
+ child.once("close", (code) => resolve22(code ?? 1));
8746
9696
  });
9697
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
9698
+ runId: input.runId,
9699
+ status: exitCode === 0 ? "completed" : "failed",
9700
+ pendingToolUses: feedbackPendingToolUses
9701
+ })) {
9702
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
9703
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
9704
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9705
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9706
+ }
9707
+ if (feedbackAssistantText.trim()) {
9708
+ appendRunTimeline(context.projectRoot, input.runId, {
9709
+ id: feedbackAssistantMessageId,
9710
+ type: "assistant_message",
9711
+ text: feedbackAssistantText,
9712
+ state: "completed",
9713
+ createdAt: new Date().toISOString(),
9714
+ completedAt: new Date().toISOString()
9715
+ });
9716
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9717
+ }
8747
9718
  if (exitCode !== 0) {
8748
9719
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
8749
9720
  }
@@ -8861,8 +9832,8 @@ async function executeTest(context, args) {
8861
9832
  }
8862
9833
 
8863
9834
  // packages/cli/src/commands/setup.ts
8864
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
8865
- import { resolve as resolve23 } from "path";
9835
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
9836
+ import { resolve as resolve22 } from "path";
8866
9837
  import { createPluginHost } from "@rig/core";
8867
9838
  import {
8868
9839
  isSupportedBunVersion as isSupportedBunVersion2,
@@ -8925,8 +9896,8 @@ function runSetupInit(projectRoot) {
8925
9896
  mkdirSync9(stateDir, { recursive: true });
8926
9897
  mkdirSync9(logsDir, { recursive: true });
8927
9898
  mkdirSync9(artifactsDir, { recursive: true });
8928
- const failuresPath = resolve23(stateDir, "failed_approaches.md");
8929
- if (!existsSync14(failuresPath)) {
9899
+ const failuresPath = resolve22(stateDir, "failed_approaches.md");
9900
+ if (!existsSync13(failuresPath)) {
8930
9901
  writeFileSync7(failuresPath, `# Failed Approaches
8931
9902
 
8932
9903
  `, "utf-8");
@@ -8944,18 +9915,18 @@ async function runSetupCheck(projectRoot) {
8944
9915
  }
8945
9916
  async function runSetupPreflight(projectRoot) {
8946
9917
  await runSetupCheck(projectRoot);
8947
- const validationRoot = resolve23(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
8948
- if (existsSync14(validationRoot)) {
9918
+ const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
9919
+ if (existsSync13(validationRoot)) {
8949
9920
  const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
8950
9921
  for (const validator of validators) {
8951
- const script = resolve23(validationRoot, validator.name, "validate.sh");
8952
- if (existsSync14(script)) {
9922
+ const script = resolve22(validationRoot, validator.name, "validate.sh");
9923
+ if (existsSync13(script)) {
8953
9924
  console.log(`OK: validator script ${script}`);
8954
9925
  }
8955
9926
  }
8956
9927
  }
8957
- const hooksRoot = resolve23(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
8958
- if (existsSync14(hooksRoot)) {
9928
+ const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
9929
+ if (existsSync13(hooksRoot)) {
8959
9930
  const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
8960
9931
  for (const hook of hooks) {
8961
9932
  console.log(`OK: hook ${hook}`);
@@ -9332,8 +10303,8 @@ async function executeGroup(context, group, args) {
9332
10303
  }
9333
10304
  }
9334
10305
  // packages/cli/src/launcher.ts
9335
- import { existsSync as existsSync15 } from "fs";
9336
- import { resolve as resolve24 } from "path";
10306
+ import { existsSync as existsSync14 } from "fs";
10307
+ import { basename as basename2, resolve as resolve23 } from "path";
9337
10308
  import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
9338
10309
  import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
9339
10310
  function parsePolicyMode(value) {
@@ -9346,7 +10317,7 @@ function parsePolicyMode(value) {
9346
10317
  throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
9347
10318
  }
9348
10319
  function hasRigProjectMarker(candidate) {
9349
- 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"));
10320
+ 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"));
9350
10321
  }
9351
10322
  function resolveProjectRoot({
9352
10323
  envProjectRoot,
@@ -9355,17 +10326,19 @@ function resolveProjectRoot({
9355
10326
  cwd = process.cwd()
9356
10327
  }) {
9357
10328
  if (envProjectRoot) {
9358
- return resolve24(cwd, envProjectRoot);
10329
+ return resolve23(cwd, envProjectRoot);
9359
10330
  }
9360
10331
  const fallbackImportDir = importDir ?? cwd;
9361
- const candidates = [cwd, resolve24(execPath, "..", ".."), resolve24(fallbackImportDir, "..")];
10332
+ const execName = basename2(execPath).toLowerCase();
10333
+ const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
10334
+ const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
9362
10335
  for (const candidate of candidates) {
9363
10336
  const nearest = resolveNearestRigProjectRoot(candidate);
9364
10337
  if (hasRigProjectMarker(nearest)) {
9365
10338
  return nearest;
9366
10339
  }
9367
10340
  }
9368
- return resolve24(cwd);
10341
+ return resolve23(cwd);
9369
10342
  }
9370
10343
  function normalizeCliErrorCode(message2, isCliError) {
9371
10344
  if (message2.startsWith("Invalid --policy-mode value:")) {
@@ -9432,7 +10405,7 @@ async function runRigCli(module, options = {}) {
9432
10405
  runId: context.runId,
9433
10406
  outcome,
9434
10407
  eventsFile: context.eventBus.getEventsFile(),
9435
- policyFile: resolve24(projectRoot, "rig", "policy", "policy.json"),
10408
+ policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
9436
10409
  policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
9437
10410
  }, null, 2));
9438
10411
  }