@h-rig/cli 0.0.6-alpha.12 → 0.0.6-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2464,17 +2464,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
2464
2464
  }
2465
2465
 
2466
2466
  // packages/cli/src/commands/_server-client.ts
2467
- import { spawnSync } from "child_process";
2468
2467
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2469
2468
  import { resolve as resolve8 } from "path";
2470
2469
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
2471
- var cachedGitHubBearerToken;
2470
+ var scopedGitHubBearerTokens = new Map;
2472
2471
  function cleanToken(value) {
2473
2472
  const trimmed = value?.trim();
2474
2473
  return trimmed ? trimmed : null;
2475
2474
  }
2476
- function setGitHubBearerTokenForCurrentProcess(token) {
2477
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
2475
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
2476
+ const scopedKey = resolve8(projectRoot ?? process.cwd());
2477
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
2478
2478
  }
2479
2479
  function readPrivateRemoteSessionToken(projectRoot) {
2480
2480
  const path = resolve8(projectRoot, ".rig", "state", "github-auth.json");
@@ -2488,25 +2488,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
2488
2488
  }
2489
2489
  }
2490
2490
  function readGitHubBearerTokenForRemote(projectRoot) {
2491
- if (cachedGitHubBearerToken !== undefined)
2492
- return cachedGitHubBearerToken;
2491
+ const scopedKey = resolve8(projectRoot);
2492
+ if (scopedGitHubBearerTokens.has(scopedKey))
2493
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
2493
2494
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
2494
- if (privateSession) {
2495
- cachedGitHubBearerToken = privateSession;
2496
- return cachedGitHubBearerToken;
2497
- }
2498
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
2499
- if (envToken) {
2500
- cachedGitHubBearerToken = envToken;
2501
- return cachedGitHubBearerToken;
2502
- }
2503
- const result = spawnSync("gh", ["auth", "token"], {
2504
- encoding: "utf8",
2505
- timeout: 5000,
2506
- stdio: ["ignore", "pipe", "ignore"]
2507
- });
2508
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
2509
- return cachedGitHubBearerToken;
2495
+ if (privateSession)
2496
+ return privateSession;
2497
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
2510
2498
  }
2511
2499
  async function ensureServerForCli(projectRoot) {
2512
2500
  try {
@@ -2704,6 +2692,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
2704
2692
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2705
2693
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2706
2694
  }
2695
+ async function getRunTimelineViaServer(context, runId, options = {}) {
2696
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
2697
+ if (options.limit !== undefined)
2698
+ url.searchParams.set("limit", String(options.limit));
2699
+ if (options.cursor)
2700
+ url.searchParams.set("cursor", options.cursor);
2701
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2702
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
2703
+ }
2704
+ async function ensureTaskLabelsViaServer(context) {
2705
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
2706
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2707
+ }
2708
+ async function listGitHubProjectsViaServer(context, owner) {
2709
+ const url = new URL("http://rig.local/api/github/projects");
2710
+ url.searchParams.set("owner", owner);
2711
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
2712
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
2713
+ }
2714
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
2715
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
2716
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2717
+ }
2718
+ async function updateWorkspaceTaskViaServer(context, input) {
2719
+ const payload = await requestServerJson(context, "/api/tasks/update", {
2720
+ method: "POST",
2721
+ headers: { "content-type": "application/json" },
2722
+ body: JSON.stringify(input)
2723
+ });
2724
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2725
+ }
2707
2726
  async function stopRunViaServer(context, runId) {
2708
2727
  const payload = await requestServerJson(context, "/api/runs/stop", {
2709
2728
  method: "POST",
@@ -2983,6 +3002,9 @@ function permissionAllowsPr(payload) {
2983
3002
  }
2984
3003
  return null;
2985
3004
  }
3005
+ function isNotFoundError(error) {
3006
+ return /\b(404|not found)\b/i.test(message(error));
3007
+ }
2986
3008
  function projectCheckoutReady(payload) {
2987
3009
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
2988
3010
  return null;
@@ -3015,19 +3037,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
3015
3037
  const checks = [];
3016
3038
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
3017
3039
  const taskId = options.taskId?.trim() || null;
3040
+ const requiresCurrentRunApi = Boolean(taskId);
3041
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
3042
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
3043
+ let legacyServerCompatibility = false;
3018
3044
  try {
3019
3045
  await request("/api/server/status");
3020
3046
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
3021
3047
  } catch (error) {
3022
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3048
+ if (isNotFoundError(error)) {
3049
+ try {
3050
+ await request("/health");
3051
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
3052
+ 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"));
3053
+ } catch (healthError) {
3054
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
3055
+ }
3056
+ } else {
3057
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
3058
+ }
3023
3059
  }
3024
3060
  const repo = readRepoConnection(context.projectRoot);
3025
- 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>`."));
3061
+ 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>`."));
3026
3062
  try {
3027
3063
  const auth = await request("/api/github/auth/status");
3028
- 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>`."));
3064
+ 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>`."));
3029
3065
  } catch (error) {
3030
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3066
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
3031
3067
  }
3032
3068
  try {
3033
3069
  const projection = await request("/api/workspace/task-projection");
@@ -3055,9 +3091,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
3055
3091
  try {
3056
3092
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
3057
3093
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
3058
- 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."));
3094
+ 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."));
3059
3095
  } catch (error) {
3060
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
3096
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
3061
3097
  }
3062
3098
  try {
3063
3099
  const runs = await request("/api/runs?limit=200");
@@ -3969,9 +4005,10 @@ async function executeInbox(context, args) {
3969
4005
 
3970
4006
  // packages/cli/src/commands/init.ts
3971
4007
  import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3972
- import { spawnSync as spawnSync2 } from "child_process";
4008
+ import { spawnSync } from "child_process";
3973
4009
  import { resolve as resolve16 } from "path";
3974
4010
  import { buildRigInitConfigSource } from "@rig/core";
4011
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
3975
4012
 
3976
4013
  // packages/cli/src/commands/_snapshot-upload.ts
3977
4014
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -4295,7 +4332,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
4295
4332
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
4296
4333
  }
4297
4334
  function detectOriginRepoSlug(projectRoot) {
4298
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4335
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
4299
4336
  if (result.status !== 0)
4300
4337
  return null;
4301
4338
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -4351,11 +4388,14 @@ function applyGitHubProjectConfig(source, options) {
4351
4388
  return source;
4352
4389
  const projectId = JSON.stringify(options.githubProject);
4353
4390
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
4391
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
4392
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
4393
+ `)},` : "";
4354
4394
  return source.replace(` projects: { enabled: false },`, [
4355
4395
  ` projects: {`,
4356
4396
  ` enabled: true,`,
4357
4397
  ` projectId: ${projectId},`,
4358
- ` statusFieldId: ${statusFieldId},`,
4398
+ ` statusFieldId: ${statusFieldId},${statuses}`,
4359
4399
  ` },`
4360
4400
  ].join(`
4361
4401
  `));
@@ -4376,11 +4416,11 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
4376
4416
  }
4377
4417
  }
4378
4418
  function detectGhLogin() {
4379
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4419
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
4380
4420
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
4381
4421
  }
4382
4422
  function readGhAuthToken() {
4383
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4423
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
4384
4424
  if (result.status !== 0 || !result.stdout.trim()) {
4385
4425
  throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
4386
4426
  }
@@ -4389,8 +4429,15 @@ function readGhAuthToken() {
4389
4429
  async function loadClackPrompts() {
4390
4430
  return await import("@clack/prompts");
4391
4431
  }
4432
+ function clackTextOptions(options) {
4433
+ return {
4434
+ message: options.message,
4435
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
4436
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
4437
+ };
4438
+ }
4392
4439
  async function promptRequiredText(prompts, options) {
4393
- const value = await prompts.text(options);
4440
+ const value = await prompts.text(clackTextOptions(options));
4394
4441
  if (prompts.isCancel(value))
4395
4442
  throw new CliError2("Init cancelled.", 1);
4396
4443
  const text2 = String(value ?? "").trim();
@@ -4399,7 +4446,7 @@ async function promptRequiredText(prompts, options) {
4399
4446
  return text2;
4400
4447
  }
4401
4448
  async function promptOptionalText(prompts, options) {
4402
- const value = await prompts.text(options);
4449
+ const value = await prompts.text(clackTextOptions(options));
4403
4450
  if (prompts.isCancel(value))
4404
4451
  throw new CliError2("Init cancelled.", 1);
4405
4452
  return String(value ?? "").trim();
@@ -4410,6 +4457,132 @@ async function promptSelect(prompts, options) {
4410
4457
  throw new CliError2("Init cancelled.", 1);
4411
4458
  return String(value);
4412
4459
  }
4460
+ function repoOwnerFromSlug(repoSlug) {
4461
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
4462
+ }
4463
+ function recordArray(value, key) {
4464
+ if (!value || typeof value !== "object" || Array.isArray(value))
4465
+ return [];
4466
+ const raw = value[key];
4467
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4468
+ }
4469
+ async function listGitHubProjectsForInit(context, owner, token) {
4470
+ if (token?.trim()) {
4471
+ try {
4472
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
4473
+ } catch (directError) {
4474
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
4475
+ if (recordArray(serverPayload, "projects").length > 0)
4476
+ return serverPayload;
4477
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
4478
+ }
4479
+ }
4480
+ return listGitHubProjectsViaServer(context, owner);
4481
+ }
4482
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
4483
+ if (token?.trim()) {
4484
+ try {
4485
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
4486
+ } catch (directError) {
4487
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
4488
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
4489
+ return serverPayload;
4490
+ }
4491
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
4492
+ }
4493
+ }
4494
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
4495
+ }
4496
+ var PROJECT_STATUS_PROMPTS = {
4497
+ running: "Running/In progress",
4498
+ prOpen: "PR open/review",
4499
+ ciFixing: "CI/review fixing",
4500
+ merging: "Merging",
4501
+ done: "Done",
4502
+ needsAttention: "Needs attention"
4503
+ };
4504
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
4505
+ running: "In Progress",
4506
+ prOpen: "In Review",
4507
+ ciFixing: "In Review",
4508
+ merging: "Merging",
4509
+ done: "Done",
4510
+ needsAttention: "Needs Attention"
4511
+ };
4512
+ async function promptManualProjectStatusMapping(prompts) {
4513
+ const statuses = {};
4514
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4515
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
4516
+ const value = await promptOptionalText(prompts, {
4517
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
4518
+ placeholder: defaultLabel
4519
+ });
4520
+ statuses[key] = value || defaultLabel;
4521
+ }
4522
+ return statuses;
4523
+ }
4524
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken) {
4525
+ const projectChoice = await promptSelect(prompts, {
4526
+ message: "GitHub Projects status sync",
4527
+ options: [
4528
+ { value: "off", label: "Off" },
4529
+ { value: "select", label: "Select accessible ProjectV2" },
4530
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
4531
+ ]
4532
+ });
4533
+ if (projectChoice === "off")
4534
+ return { githubProject: "off" };
4535
+ if (projectChoice === "manual") {
4536
+ return {
4537
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
4538
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
4539
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
4540
+ };
4541
+ }
4542
+ const owner = repoOwnerFromSlug(repoSlug);
4543
+ if (!owner)
4544
+ throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
4545
+ const projectsPayload = await listGitHubProjectsForInit(context, owner, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
4546
+ const projects = recordArray(projectsPayload, "projects");
4547
+ if (projects.length === 0) {
4548
+ const error = typeof projectsPayload.error === "string" ? ` (${projectsPayload.error})` : "";
4549
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; falling back to manual ProjectV2 ids.`);
4550
+ return {
4551
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
4552
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
4553
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
4554
+ };
4555
+ }
4556
+ const selectedProjectId = await promptSelect(prompts, {
4557
+ message: "GitHub ProjectV2 project",
4558
+ options: [
4559
+ ...projects.map((project) => ({
4560
+ value: String(project.id),
4561
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
4562
+ hint: typeof project.url === "string" ? project.url : undefined
4563
+ })),
4564
+ { value: "manual", label: "Enter ProjectV2 id manually" }
4565
+ ]
4566
+ });
4567
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
4568
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
4569
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
4570
+ const rawField = fieldPayloadRecord.field;
4571
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
4572
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
4573
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
4574
+ if (options.length === 0) {
4575
+ return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: await promptManualProjectStatusMapping(prompts) };
4576
+ }
4577
+ const statuses = {};
4578
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
4579
+ statuses[key] = await promptSelect(prompts, {
4580
+ message: `Project status option for ${label}`,
4581
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: String(option.name ?? option.id) }))
4582
+ });
4583
+ }
4584
+ return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined };
4585
+ }
4413
4586
  function sleep2(ms) {
4414
4587
  return new Promise((resolve17) => setTimeout(resolve17, ms));
4415
4588
  }
@@ -4538,7 +4711,7 @@ async function runControlPlaneInit(context, options) {
4538
4711
  if (token) {
4539
4712
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
4540
4713
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
4541
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4714
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4542
4715
  if (serverKind === "remote") {
4543
4716
  writeRemoteGitHubAuthState(projectRoot, {
4544
4717
  source: authMethod === "gh" ? "gh" : "init-token",
@@ -4563,7 +4736,7 @@ async function runControlPlaneInit(context, options) {
4563
4736
  if (completed) {
4564
4737
  const apiSessionToken = apiSessionTokenFrom(completed);
4565
4738
  if (apiSessionToken) {
4566
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
4739
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
4567
4740
  if (serverKind === "remote") {
4568
4741
  writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
4569
4742
  }
@@ -4587,7 +4760,7 @@ async function runControlPlaneInit(context, options) {
4587
4760
  if (serverKind === "remote" && checkoutPath && token) {
4588
4761
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
4589
4762
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
4590
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
4763
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
4591
4764
  writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
4592
4765
  }
4593
4766
  const registered = await registerProjectViaServer(context, {
@@ -4596,6 +4769,12 @@ async function runControlPlaneInit(context, options) {
4596
4769
  });
4597
4770
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
4598
4771
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
4772
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
4773
+ ok: false,
4774
+ ready: false,
4775
+ labelsReady: false,
4776
+ error: error instanceof Error ? error.message : String(error)
4777
+ }));
4599
4778
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
4600
4779
  remote: true,
4601
4780
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -4626,6 +4805,7 @@ async function runControlPlaneInit(context, options) {
4626
4805
  githubAuth,
4627
4806
  deviceAuth,
4628
4807
  githubAuthWarning: remoteGhTokenWarning,
4808
+ labelSetup,
4629
4809
  pi,
4630
4810
  doctor
4631
4811
  };
@@ -4780,24 +4960,17 @@ async function runInteractiveControlPlaneInit(context, prompts) {
4780
4960
  throw new CliError2("Remote gh-token import cancelled.", 1);
4781
4961
  }
4782
4962
  }
4783
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
4784
- const projectChoice = await promptSelect(prompts, {
4785
- message: "GitHub Projects status sync",
4786
- options: [
4787
- { value: "off", label: "Off" },
4788
- { value: "configure", label: "Configure ProjectV2 status field" }
4789
- ]
4790
- });
4791
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
4792
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
4963
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
4964
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken);
4793
4965
  const result = await runControlPlaneInit(context, {
4794
4966
  server: serverChoice,
4795
4967
  remoteUrl,
4796
4968
  repoSlug,
4797
4969
  githubToken,
4798
4970
  githubAuthMethod: authMethod,
4799
- githubProject,
4800
- githubProjectStatusField,
4971
+ githubProject: projectConfig.githubProject,
4972
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
4973
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
4801
4974
  remoteCheckout,
4802
4975
  repair,
4803
4976
  privateStateOnly
@@ -4896,7 +5069,7 @@ Usage: rig connect <list|add|use|status>`, 1);
4896
5069
  }
4897
5070
 
4898
5071
  // packages/cli/src/commands/github.ts
4899
- import { spawnSync as spawnSync3 } from "child_process";
5072
+ import { spawnSync as spawnSync2 } from "child_process";
4900
5073
  function printPayload(context, payload, fallback) {
4901
5074
  if (context.outputMode === "json")
4902
5075
  console.log(JSON.stringify(payload, null, 2));
@@ -4904,7 +5077,7 @@ function printPayload(context, payload, fallback) {
4904
5077
  console.log(fallback);
4905
5078
  }
4906
5079
  function readGhToken() {
4907
- const result = spawnSync3("gh", ["auth", "token"], { encoding: "utf8" });
5080
+ const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
4908
5081
  if (result.status !== 0) {
4909
5082
  const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
4910
5083
  throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
@@ -5936,14 +6109,10 @@ async function executeRemote(context, args) {
5936
6109
  }
5937
6110
 
5938
6111
  // packages/cli/src/commands/run.ts
5939
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
5940
- import { resolve as resolve19 } from "path";
5941
6112
  import { createInterface as createInterface2 } from "readline/promises";
5942
6113
  import {
5943
6114
  listAuthorityRuns as listAuthorityRuns3,
5944
- readAuthorityRun as readAuthorityRun4,
5945
- readJsonlFile as readJsonlFile4,
5946
- resolveAuthorityRunDir as resolveAuthorityRunDir5
6115
+ readAuthorityRun as readAuthorityRun4
5947
6116
  } from "@rig/runtime/control-plane/authority-files";
5948
6117
  import {
5949
6118
  cleanupRunState,
@@ -5959,9 +6128,9 @@ import {
5959
6128
  } from "@rig/runtime/control-plane/native/run-ops";
5960
6129
  import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
5961
6130
 
5962
- // packages/cli/src/commands/_operator-view.ts
6131
+ // packages/cli/src/commands/_operator-surface.ts
5963
6132
  import { createInterface } from "readline";
5964
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6133
+ import { createInterface as createPromptInterface } from "readline/promises";
5965
6134
  var CANONICAL_STAGES = [
5966
6135
  "Connect",
5967
6136
  "GitHub/task sync",
@@ -5976,18 +6145,141 @@ var CANONICAL_STAGES = [
5976
6145
  "Merge",
5977
6146
  "Complete"
5978
6147
  ];
6148
+ function logDetail(log3) {
6149
+ return typeof log3.detail === "string" ? log3.detail.trim() : "";
6150
+ }
6151
+ function entryId(entry, fallback) {
6152
+ return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
6153
+ }
5979
6154
  function renderOperatorSnapshot(snapshot) {
5980
6155
  const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
5981
6156
  const runId = String(run.runId ?? run.id ?? "run");
5982
6157
  const status = String(run.status ?? "unknown");
5983
6158
  const logs = snapshot.logs ?? [];
6159
+ const latestByStage = new Map;
6160
+ for (const log3 of logs) {
6161
+ const title = String(log3.title ?? "").toLowerCase();
6162
+ const stageName = String(log3.stage ?? "").toLowerCase();
6163
+ const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6164
+ if (stage)
6165
+ latestByStage.set(stage, log3);
6166
+ }
5984
6167
  const stageLines = CANONICAL_STAGES.flatMap((stage) => {
5985
- const match = logs.find((log3) => String(log3.title ?? "").toLowerCase() === stage.toLowerCase() || String(log3.stage ?? "").toLowerCase() === stage.toLowerCase());
5986
- return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
6168
+ const match = latestByStage.get(stage);
6169
+ return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
5987
6170
  });
5988
6171
  return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
5989
6172
  `);
5990
6173
  }
6174
+ function createPiRunStreamRenderer(output = process.stdout) {
6175
+ let lastSnapshot = "";
6176
+ const assistantTextById = new Map;
6177
+ const seenTimeline = new Set;
6178
+ const seenLogs = new Set;
6179
+ const writeLine = (line) => output.write(`${line}
6180
+ `);
6181
+ return {
6182
+ renderSnapshot(snapshot) {
6183
+ const rendered = renderOperatorSnapshot(snapshot);
6184
+ if (rendered && rendered !== lastSnapshot) {
6185
+ writeLine(rendered);
6186
+ lastSnapshot = rendered;
6187
+ }
6188
+ },
6189
+ renderTimeline(entries) {
6190
+ for (const [index, entry] of entries.entries()) {
6191
+ const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
6192
+ if (entry.type === "assistant_message" && typeof entry.text === "string") {
6193
+ const text2 = entry.text;
6194
+ const previousText = assistantTextById.get(id) ?? "";
6195
+ if (text2.startsWith(previousText)) {
6196
+ const delta = text2.slice(previousText.length);
6197
+ if (delta)
6198
+ output.write(delta);
6199
+ } else if (text2.trim() && text2 !== previousText) {
6200
+ writeLine(`
6201
+ [Pi assistant]`);
6202
+ output.write(text2);
6203
+ }
6204
+ assistantTextById.set(id, text2);
6205
+ continue;
6206
+ }
6207
+ if (seenTimeline.has(id))
6208
+ continue;
6209
+ seenTimeline.add(id);
6210
+ if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
6211
+ writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
6212
+ continue;
6213
+ }
6214
+ if (entry.type === "timeline_warning") {
6215
+ writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
6216
+ }
6217
+ }
6218
+ },
6219
+ renderLogs(entries) {
6220
+ for (const [index, entry] of entries.entries()) {
6221
+ const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
6222
+ if (seenLogs.has(id))
6223
+ continue;
6224
+ seenLogs.add(id);
6225
+ const title = String(entry.title ?? "");
6226
+ if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
6227
+ continue;
6228
+ const detail = logDetail(entry);
6229
+ if (!detail)
6230
+ continue;
6231
+ writeLine(`[${title || "Rig log"}] ${detail}`);
6232
+ }
6233
+ }
6234
+ };
6235
+ }
6236
+ function createOperatorSurface(options = {}) {
6237
+ const input = options.input ?? process.stdin;
6238
+ const output = options.output ?? process.stdout;
6239
+ const errorOutput = options.errorOutput ?? process.stderr;
6240
+ const renderer = createPiRunStreamRenderer(output);
6241
+ const writeLine = (line) => output.write(`${line}
6242
+ `);
6243
+ return {
6244
+ mode: "pi-compatible-text",
6245
+ ...renderer,
6246
+ info: writeLine,
6247
+ error: (message2) => errorOutput.write(`${message2}
6248
+ `),
6249
+ attachCommandInput(handler) {
6250
+ if (options.interactive === false || !input.isTTY)
6251
+ return null;
6252
+ const rl = createInterface({ input, output: process.stdout, terminal: false });
6253
+ rl.on("line", (line) => {
6254
+ Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6255
+ });
6256
+ return { close: () => rl.close() };
6257
+ }
6258
+ };
6259
+ }
6260
+ function taskId(task) {
6261
+ return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6262
+ }
6263
+ function taskTitle(task) {
6264
+ return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6265
+ }
6266
+ function taskStatus(task) {
6267
+ return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6268
+ }
6269
+ function renderTaskPickerRows(tasks) {
6270
+ return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6271
+ }
6272
+ async function promptForTaskSelection(question) {
6273
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
6274
+ try {
6275
+ return await rl.question(question);
6276
+ } finally {
6277
+ rl.close();
6278
+ }
6279
+ }
6280
+
6281
+ // packages/cli/src/commands/_operator-view.ts
6282
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
5991
6283
  function runStatusFromPayload(payload) {
5992
6284
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
5993
6285
  return String(run.status ?? "unknown").toLowerCase();
@@ -6009,11 +6301,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
6009
6301
  await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
6010
6302
  return { action: "continue", message: "Steering message queued." };
6011
6303
  }
6012
- async function readOperatorSnapshot(context, runId) {
6304
+ async function readOperatorSnapshot(context, runId, options = {}) {
6013
6305
  const run = await getRunDetailsViaServer(context, runId);
6014
6306
  const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
6015
- const entries = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6016
- return { run, logs: entries, rendered: renderOperatorSnapshot({ run, logs: entries }) };
6307
+ const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
6308
+ entries: [{
6309
+ id: `timeline-unavailable:${runId}`,
6310
+ type: "timeline_warning",
6311
+ detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
6312
+ createdAt: new Date().toISOString()
6313
+ }],
6314
+ nextCursor: options.timelineCursor ?? null
6315
+ }));
6316
+ const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
6317
+ const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6318
+ const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6319
+ return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6017
6320
  }
6018
6321
  async function attachRunOperatorView(context, input) {
6019
6322
  let steered = false;
@@ -6021,40 +6324,41 @@ async function attachRunOperatorView(context, input) {
6021
6324
  await steerRunViaServer(context, input.runId, input.message.trim());
6022
6325
  steered = true;
6023
6326
  }
6327
+ const surface = createOperatorSurface({ interactive: input.interactive !== false });
6024
6328
  let snapshot = await readOperatorSnapshot(context, input.runId);
6025
6329
  if (context.outputMode === "text") {
6026
- console.log(snapshot.rendered);
6330
+ surface.renderSnapshot(snapshot);
6331
+ surface.renderTimeline(snapshot.timeline);
6332
+ surface.renderLogs(snapshot.logs);
6027
6333
  if (steered)
6028
- console.log("Steering message queued.");
6334
+ surface.info("Steering message queued.");
6029
6335
  }
6030
6336
  let detached = false;
6031
- let rl = null;
6337
+ let commandInput = null;
6032
6338
  if (input.follow && !input.once && context.outputMode === "text") {
6033
6339
  if (input.interactive !== false && process.stdin.isTTY) {
6034
- console.log("Controls: /user <message>, /stop, /detach");
6035
- rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
6036
- rl.on("line", (line) => {
6037
- applyOperatorCommand(context, { runId: input.runId, line }).then((result) => {
6038
- if (result.message)
6039
- console.log(result.message);
6040
- if (result.action === "detach" || result.action === "stopped") {
6041
- detached = true;
6042
- rl?.close();
6043
- }
6044
- }).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
6340
+ surface.info("Controls: /user <message>, /stop, /detach");
6341
+ commandInput = surface.attachCommandInput(async (line) => {
6342
+ const result = await applyOperatorCommand(context, { runId: input.runId, line });
6343
+ if (result.message)
6344
+ surface.info(result.message);
6345
+ if (result.action === "detach" || result.action === "stopped") {
6346
+ detached = true;
6347
+ commandInput?.close();
6348
+ }
6045
6349
  });
6046
6350
  }
6047
- let lastRendered = snapshot.rendered;
6048
6351
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6352
+ let timelineCursor = snapshot.timelineCursor;
6049
6353
  while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
6050
6354
  await Bun.sleep(pollMs);
6051
- snapshot = await readOperatorSnapshot(context, input.runId);
6052
- if (snapshot.rendered !== lastRendered) {
6053
- console.log(snapshot.rendered);
6054
- lastRendered = snapshot.rendered;
6055
- }
6355
+ snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6356
+ timelineCursor = snapshot.timelineCursor;
6357
+ surface.renderSnapshot(snapshot);
6358
+ surface.renderTimeline(snapshot.timeline);
6359
+ surface.renderLogs(snapshot.logs);
6056
6360
  }
6057
- rl?.close();
6361
+ commandInput?.close();
6058
6362
  }
6059
6363
  return { ...snapshot, steered, detached };
6060
6364
  }
@@ -6228,34 +6532,24 @@ async function executeRun(context, args) {
6228
6532
  if (!run.value) {
6229
6533
  throw new CliError2("run timeline requires --run <id>.");
6230
6534
  }
6231
- const timelinePath = resolve19(resolveAuthorityRunDir5(context.projectRoot, run.value), "timeline.jsonl");
6232
- const printEvents = () => {
6233
- const events2 = readJsonlFile4(timelinePath);
6234
- if (context.outputMode === "text") {
6235
- for (const event of events2) {
6236
- console.log(JSON.stringify(event));
6237
- }
6238
- }
6239
- return events2;
6240
- };
6241
- const events = printEvents();
6535
+ const renderer = createPiRunStreamRenderer();
6536
+ let cursor = null;
6537
+ const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
6538
+ const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6539
+ cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
6540
+ if (context.outputMode === "text") {
6541
+ renderer.renderTimeline(events);
6542
+ }
6242
6543
  if (follow.value && context.outputMode === "text") {
6243
- let lastLength = existsSync11(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
6244
6544
  while (true) {
6245
6545
  await Bun.sleep(1000);
6246
- if (!existsSync11(timelinePath))
6247
- continue;
6248
- const next = readFileSync9(timelinePath, "utf8");
6249
- if (next.length <= lastLength)
6250
- continue;
6251
- const delta = next.slice(lastLength);
6252
- lastLength = next.length;
6253
- for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
6254
- console.log(line);
6255
- }
6546
+ const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
6547
+ const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
6548
+ cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
6549
+ renderer.renderTimeline(nextEvents);
6256
6550
  }
6257
6551
  }
6258
- return { ok: true, group: "run", command, details: { runId: run.value, events } };
6552
+ return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
6259
6553
  }
6260
6554
  case "attach": {
6261
6555
  let pending = rest;
@@ -6545,10 +6839,10 @@ async function executeServer(context, args, options) {
6545
6839
  }
6546
6840
 
6547
6841
  // packages/cli/src/commands/task.ts
6548
- import { readFileSync as readFileSync10 } from "fs";
6549
- import { spawnSync as spawnSync4 } from "child_process";
6550
- import { createInterface as createInterface4 } from "readline/promises";
6551
- import { resolve as resolve20 } from "path";
6842
+ import { readFileSync as readFileSync9 } from "fs";
6843
+ import { spawnSync as spawnSync3 } from "child_process";
6844
+ import { createInterface as createInterface3 } from "readline/promises";
6845
+ import { resolve as resolve19 } from "path";
6552
6846
  import {
6553
6847
  taskArtifactDir,
6554
6848
  taskArtifacts,
@@ -6566,19 +6860,9 @@ import {
6566
6860
  } from "@rig/runtime/control-plane/native/task-ops";
6567
6861
 
6568
6862
  // packages/cli/src/commands/_task-picker.ts
6569
- import { createInterface as createInterface3 } from "readline/promises";
6570
- function taskId(task) {
6863
+ function taskId2(task) {
6571
6864
  return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
6572
6865
  }
6573
- function taskTitle(task) {
6574
- return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
6575
- }
6576
- function taskStatus(task) {
6577
- return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
6578
- }
6579
- function renderTaskPickerRows(tasks) {
6580
- return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
6581
- }
6582
6866
  async function selectTaskWithTextPicker(tasks, io = {}) {
6583
6867
  if (tasks.length === 0)
6584
6868
  return null;
@@ -6588,17 +6872,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
6588
6872
  if (!isTty) {
6589
6873
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
6590
6874
  }
6591
- const prompt = io.prompt ?? (async (question) => {
6592
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
6593
- try {
6594
- return await rl.question(question);
6595
- } finally {
6596
- rl.close();
6597
- }
6598
- });
6599
- console.log("Select Rig task:");
6875
+ const prompt = io.prompt ?? promptForTaskSelection;
6876
+ const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
6877
+ `) };
6878
+ renderer.writeLine("Select Rig task:");
6600
6879
  for (const row of renderTaskPickerRows(tasks))
6601
- console.log(` ${row}`);
6880
+ renderer.writeLine(` ${row}`);
6602
6881
  const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
6603
6882
  if (!answer)
6604
6883
  return null;
@@ -6606,7 +6885,7 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
6606
6885
  const index = Number.parseInt(answer, 10) - 1;
6607
6886
  return tasks[index] ?? null;
6608
6887
  }
6609
- return tasks.find((task) => taskId(task) === answer) ?? null;
6888
+ return tasks.find((task) => taskId2(task) === answer) ?? null;
6610
6889
  }
6611
6890
 
6612
6891
  // packages/cli/src/commands/task.ts
@@ -6684,7 +6963,7 @@ function normalizePrMode(value) {
6684
6963
  throw new CliError2("--pr must be auto, ask, or off.", 2);
6685
6964
  }
6686
6965
  function detectLocalDirtyState(projectRoot) {
6687
- const result = spawnSync4("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
6966
+ const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
6688
6967
  if (result.status !== 0)
6689
6968
  return { dirty: false, modified: 0, untracked: 0, lines: [] };
6690
6969
  const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -6718,7 +6997,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
6718
6997
  if (explicit)
6719
6998
  return { mode: explicit, state };
6720
6999
  if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
6721
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
7000
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
6722
7001
  try {
6723
7002
  const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
6724
7003
  return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
@@ -6803,12 +7082,12 @@ async function executeTask(context, args, options) {
6803
7082
  const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
6804
7083
  const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
6805
7084
  requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
6806
- const taskId2 = normalizeTaskRunTaskId(taskOption.value ?? positional);
6807
- if (!taskId2)
7085
+ const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
7086
+ if (!taskId3)
6808
7087
  throw new CliError2("task show requires a task id.", 2);
6809
- const task = await getWorkspaceTaskViaServer(context, taskId2);
7088
+ const task = await getWorkspaceTaskViaServer(context, taskId3);
6810
7089
  if (!task)
6811
- throw new CliError2(`Task not found: ${taskId2}`, 3);
7090
+ throw new CliError2(`Task not found: ${taskId3}`, 3);
6812
7091
  const summary = summarizeTask(task, { raw: true });
6813
7092
  if (context.outputMode === "text")
6814
7093
  console.log(JSON.stringify(summary, null, 2));
@@ -6879,7 +7158,7 @@ async function executeTask(context, args, options) {
6879
7158
  const fileFlag = takeOption(rest.slice(1), "--file");
6880
7159
  let content;
6881
7160
  if (fileFlag.value) {
6882
- content = readFileSync10(resolve20(context.projectRoot, fileFlag.value), "utf-8");
7161
+ content = readFileSync9(resolve19(context.projectRoot, fileFlag.value), "utf-8");
6883
7162
  } else {
6884
7163
  content = await readStdin();
6885
7164
  }
@@ -7100,9 +7379,9 @@ async function executeTask(context, args, options) {
7100
7379
  }
7101
7380
 
7102
7381
  // packages/cli/src/commands/task-run-driver.ts
7103
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync11, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7104
- import { resolve as resolve21 } from "path";
7105
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
7382
+ import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7383
+ import { resolve as resolve20 } from "path";
7384
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
7106
7385
  import { createInterface as createLineInterface } from "readline";
7107
7386
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7108
7387
  import {
@@ -7127,8 +7406,7 @@ import {
7127
7406
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
7128
7407
  import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
7129
7408
  import {
7130
- buildTaskRunLifecycleComment,
7131
- updateConfiguredTaskSourceTask
7409
+ buildTaskRunLifecycleComment
7132
7410
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
7133
7411
  import {
7134
7412
  closeIssueAfterMergedPr,
@@ -7166,7 +7444,7 @@ function buildPiRigBridgeEnv(input) {
7166
7444
  };
7167
7445
  }
7168
7446
  function runGitSync(cwd, args, input) {
7169
- const result = spawnSync5("git", [...args], {
7447
+ const result = spawnSync4("git", [...args], {
7170
7448
  cwd,
7171
7449
  input,
7172
7450
  encoding: "utf8",
@@ -7184,12 +7462,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
7184
7462
  return 0;
7185
7463
  let copied = 0;
7186
7464
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
7187
- const sourcePath = resolve21(sourceRoot, relativePath);
7188
- const targetPath = resolve21(targetRoot, relativePath);
7465
+ const sourcePath = resolve20(sourceRoot, relativePath);
7466
+ const targetPath = resolve20(targetRoot, relativePath);
7189
7467
  try {
7190
7468
  if (!statSync2(sourcePath).isFile())
7191
7469
  continue;
7192
- mkdirSync7(resolve21(targetPath, ".."), { recursive: true });
7470
+ mkdirSync7(resolve20(targetPath, ".."), { recursive: true });
7193
7471
  copyFileSync3(sourcePath, targetPath);
7194
7472
  copied += 1;
7195
7473
  } catch {}
@@ -7228,7 +7506,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
7228
7506
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
7229
7507
  return {
7230
7508
  RIG_BASELINE_MODE: "dirty-snapshot",
7231
- RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7509
+ RIG_DIRTY_BASELINE_READY_FILE: resolve20(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
7232
7510
  };
7233
7511
  }
7234
7512
  function positiveInt(value, fallback) {
@@ -7339,9 +7617,9 @@ function createCommandRunner(binary) {
7339
7617
  const stderrChunks = [];
7340
7618
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7341
7619
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
7342
- return await new Promise((resolve22) => {
7343
- child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
7344
- child.once("close", (code) => resolve22({
7620
+ return await new Promise((resolve21) => {
7621
+ child.once("error", (error) => resolve21({ exitCode: 1, stderr: error.message }));
7622
+ child.once("close", (code) => resolve21({
7345
7623
  exitCode: code ?? 1,
7346
7624
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
7347
7625
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -7361,12 +7639,13 @@ async function resolvePostValidationBranch(input) {
7361
7639
  return input.configuredBranch;
7362
7640
  }
7363
7641
  async function runTaskRunPostValidationLifecycle(input) {
7364
- const taskId2 = input.taskId?.trim();
7365
- if (!taskId2) {
7642
+ const taskId3 = input.taskId?.trim();
7643
+ if (!taskId3) {
7366
7644
  return { status: "skipped" };
7367
7645
  }
7368
- const config = input.config ?? {};
7369
- const prMode = config.pr?.mode ?? "auto";
7646
+ const configInput = input.config ?? null;
7647
+ const config = configInput ?? {};
7648
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
7370
7649
  if (prMode === "off" || prMode === "ask") {
7371
7650
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
7372
7651
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -7382,7 +7661,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7382
7661
  gitCommand
7383
7662
  });
7384
7663
  const prAutomation = input.prAutomation ?? runPrAutomation;
7385
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
7664
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
7386
7665
  const stage = input.appendStage ?? (() => {
7387
7666
  return;
7388
7667
  });
@@ -7400,7 +7679,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7400
7679
  stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
7401
7680
  const commit = await commitRunChanges({
7402
7681
  cwd: workspace,
7403
- message: `rig: complete task ${taskId2}`,
7682
+ message: `rig: complete task ${taskId3}`,
7404
7683
  command: gitCommand
7405
7684
  });
7406
7685
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
@@ -7408,14 +7687,15 @@ async function runTaskRunPostValidationLifecycle(input) {
7408
7687
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
7409
7688
  const pr = await prAutomation({
7410
7689
  projectRoot: workspace,
7411
- taskId: taskId2,
7690
+ taskId: taskId3,
7412
7691
  runId: input.runId,
7413
7692
  branch,
7414
7693
  config,
7415
7694
  sourceTask: input.sourceTask,
7416
7695
  uploadedSnapshot: input.uploadedSnapshot,
7417
- artifactRoot: resolve21(input.projectRoot, "artifacts", taskId2),
7696
+ artifactRoot: resolve20(input.projectRoot, "artifacts", taskId3),
7418
7697
  command: ghCommand,
7698
+ gitCommand,
7419
7699
  steerPi,
7420
7700
  lifecycle: {
7421
7701
  onPrOpened: async ({ prUrl }) => {
@@ -7423,7 +7703,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7423
7703
  try {
7424
7704
  if (shouldWriteDriverIssueUpdate(config, "under_review")) {
7425
7705
  await updateTaskSource(input.projectRoot, {
7426
- taskId: taskId2,
7706
+ taskId: taskId3,
7427
7707
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7428
7708
  update: {
7429
7709
  status: "under_review",
@@ -7453,7 +7733,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7453
7733
  `), "reviewing", "error");
7454
7734
  if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
7455
7735
  await updateTaskSource(input.projectRoot, {
7456
- taskId: taskId2,
7736
+ taskId: taskId3,
7457
7737
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7458
7738
  update: {
7459
7739
  status: "ci_fixing",
@@ -7464,8 +7744,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7464
7744
  runtimeWorkspace: input.runtimeWorkspace ?? null
7465
7745
  })
7466
7746
  }
7467
- }).catch(() => {
7468
- return;
7469
7747
  });
7470
7748
  }
7471
7749
  },
@@ -7473,7 +7751,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7473
7751
  stage("Merge", prUrl, "running", "tool");
7474
7752
  if (shouldWriteDriverIssueUpdate(config, "merging")) {
7475
7753
  await updateTaskSource(input.projectRoot, {
7476
- taskId: taskId2,
7754
+ taskId: taskId3,
7477
7755
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7478
7756
  update: {
7479
7757
  status: "merging",
@@ -7484,8 +7762,6 @@ async function runTaskRunPostValidationLifecycle(input) {
7484
7762
  runtimeWorkspace: input.runtimeWorkspace ?? null
7485
7763
  })
7486
7764
  }
7487
- }).catch(() => {
7488
- return;
7489
7765
  });
7490
7766
  }
7491
7767
  },
@@ -7502,7 +7778,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7502
7778
  stage("Needs attention", detail, "needs_attention", "error");
7503
7779
  if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
7504
7780
  await updateTaskSource(input.projectRoot, {
7505
- taskId: taskId2,
7781
+ taskId: taskId3,
7506
7782
  sourceTask: runSourceTaskIdentity(input.sourceTask),
7507
7783
  update: {
7508
7784
  status: "needs_attention",
@@ -7514,8 +7790,17 @@ async function runTaskRunPostValidationLifecycle(input) {
7514
7790
  errorText: detail
7515
7791
  })
7516
7792
  }
7517
- }).catch(() => {
7518
- return;
7793
+ }).catch((error) => {
7794
+ try {
7795
+ appendRunLog(input.projectRoot, input.runId, {
7796
+ id: `log:${input.runId}:task-source-needs-attention-update`,
7797
+ title: "Task source needs-attention update failed",
7798
+ detail: error instanceof Error ? error.message : String(error),
7799
+ tone: "error",
7800
+ status: "needs_attention",
7801
+ createdAt: new Date().toISOString()
7802
+ });
7803
+ } catch {}
7519
7804
  });
7520
7805
  }
7521
7806
  return { status: "needs_attention", pr };
@@ -7523,7 +7808,7 @@ async function runTaskRunPostValidationLifecycle(input) {
7523
7808
  if (shouldWriteDriverIssueUpdate(config, "closed")) {
7524
7809
  await closeIssueAfterMergedPr({
7525
7810
  projectRoot: input.projectRoot,
7526
- taskId: taskId2,
7811
+ taskId: taskId3,
7527
7812
  runId: input.runId,
7528
7813
  prUrl: pr.prUrl,
7529
7814
  sourceTask: input.sourceTask,
@@ -7533,12 +7818,12 @@ async function runTaskRunPostValidationLifecycle(input) {
7533
7818
  stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
7534
7819
  return { status: "completed", pr };
7535
7820
  }
7536
- function summarizeValidationFailure(projectRoot, taskId2) {
7537
- if (!taskId2) {
7821
+ function summarizeValidationFailure(projectRoot, taskId3) {
7822
+ if (!taskId3) {
7538
7823
  return null;
7539
7824
  }
7540
- for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId2)) {
7541
- const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
7825
+ for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
7826
+ const summary = readJsonFile3(resolve20(artifactDir, "validation-summary.json"), null);
7542
7827
  if (!summary || summary.status !== "fail") {
7543
7828
  continue;
7544
7829
  }
@@ -7619,9 +7904,9 @@ function readTaskRunAcceptedArtifactState(input) {
7619
7904
  if (!input.taskId || !input.workspaceDir) {
7620
7905
  return { accepted: false, reason: null };
7621
7906
  }
7622
- const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
7623
- const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
7624
- const taskResultPath = resolve21(artifactDir, "task-result.json");
7907
+ const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
7908
+ const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
7909
+ const taskResultPath = resolve20(artifactDir, "task-result.json");
7625
7910
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7626
7911
  if (reviewStatus !== "APPROVED") {
7627
7912
  return { accepted: false, reason: null };
@@ -7658,12 +7943,12 @@ function resolveTaskRunRetryContext(input) {
7658
7943
  if (!input.taskId || !input.workspaceDir) {
7659
7944
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
7660
7945
  }
7661
- const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
7662
- const reviewStatePath = resolve21(artifactDir, "review-state.json");
7663
- const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
7664
- const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
7665
- const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
7666
- const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
7946
+ const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
7947
+ const reviewStatePath = resolve20(artifactDir, "review-state.json");
7948
+ const reviewFeedbackPath = resolve20(artifactDir, "review-feedback.md");
7949
+ const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
7950
+ const failedApproachesPath = resolve20(input.workspaceDir, ".rig", "state", "failed_approaches.md");
7951
+ const validationSummaryPath = resolve20(artifactDir, "validation-summary.json");
7667
7952
  const reviewState = readJsonFile3(reviewStatePath, null);
7668
7953
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
7669
7954
  const reviewRejected = isTaskRunReviewRejected(reviewState);
@@ -7717,12 +8002,60 @@ function summarizeTaskRunReviewFailure(reviewState) {
7717
8002
  }
7718
8003
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
7719
8004
  }
8005
+ function appendAssistantTimelineFromRecord(input) {
8006
+ let nextAssistantText = input.assistantText;
8007
+ if (input.record.type === "message_update") {
8008
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
8009
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
8010
+ nextAssistantText += assistantMessageEvent.delta;
8011
+ }
8012
+ } else if (input.record.type === "stream_event") {
8013
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
8014
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
8015
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
8016
+ nextAssistantText += delta.text;
8017
+ }
8018
+ } else if (input.record.type === "assistant") {
8019
+ const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
8020
+ const content = Array.isArray(message2.content) ? message2.content : [];
8021
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
8022
+ if (fullText.length > nextAssistantText.length)
8023
+ nextAssistantText = fullText;
8024
+ }
8025
+ if (nextAssistantText !== input.assistantText) {
8026
+ appendRunTimeline(input.projectRoot, input.runId, {
8027
+ id: input.messageId,
8028
+ type: "assistant_message",
8029
+ text: nextAssistantText,
8030
+ state: "streaming",
8031
+ createdAt: new Date().toISOString()
8032
+ });
8033
+ }
8034
+ return nextAssistantText;
8035
+ }
8036
+ function appendToolTimelineFromLog(input) {
8037
+ const title = typeof input.log.title === "string" ? input.log.title : "";
8038
+ if (title !== "Tool activity")
8039
+ return;
8040
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
8041
+ 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;
8042
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
8043
+ appendRunTimeline(input.projectRoot, input.runId, {
8044
+ id: `tool:${logId}`,
8045
+ type: "tool_execution_update",
8046
+ toolName,
8047
+ status: typeof input.log.status === "string" ? input.log.status : "running",
8048
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
8049
+ payload,
8050
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
8051
+ });
8052
+ }
7720
8053
  function readTaskRunReviewStatus(reviewStatusPath) {
7721
- if (!existsSync12(reviewStatusPath)) {
8054
+ if (!existsSync11(reviewStatusPath)) {
7722
8055
  return null;
7723
8056
  }
7724
8057
  try {
7725
- const status = readFileSync11(reviewStatusPath, "utf8").trim().toUpperCase();
8058
+ const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
7726
8059
  return status === "APPROVED" || status === "REJECTED" ? status : null;
7727
8060
  } catch {
7728
8061
  return null;
@@ -7740,14 +8073,45 @@ function isTaskRunReviewRejected(reviewState) {
7740
8073
  function runSourceTaskIdentity(sourceTask) {
7741
8074
  return sourceTask;
7742
8075
  }
7743
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
7744
- if (!taskId2)
8076
+ function sourceTaskIssueNodeId(sourceTask) {
8077
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
8078
+ return null;
8079
+ const record = sourceTask;
8080
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
8081
+ if (direct?.trim())
8082
+ return direct.trim();
8083
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
8084
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
8085
+ }
8086
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
8087
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
8088
+ id: input.taskId,
8089
+ ...input.update.status ? { status: input.update.status } : {},
8090
+ ...input.update.comment ? { comment: input.update.comment } : {},
8091
+ ...input.update.title ? { title: input.update.title } : {},
8092
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
8093
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
8094
+ });
8095
+ if (serverResult.ok === false) {
8096
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
8097
+ }
8098
+ return {
8099
+ updated: serverResult.ok !== false,
8100
+ taskId: input.taskId,
8101
+ status: input.update.status,
8102
+ source: "server",
8103
+ sourceKind: "server",
8104
+ projectSync: serverResult.projectSync
8105
+ };
8106
+ };
8107
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
8108
+ if (!taskId3)
7745
8109
  return;
7746
8110
  const config = await loadTaskRunAutomationConfig(projectRoot);
7747
8111
  if (!shouldWriteDriverIssueUpdate(config, status))
7748
8112
  return;
7749
8113
  const result = await updateTaskSource(projectRoot, {
7750
- taskId: taskId2,
8114
+ taskId: taskId3,
7751
8115
  sourceTask: runSourceTaskIdentity(sourceTask),
7752
8116
  update: {
7753
8117
  status,
@@ -7766,14 +8130,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
7766
8130
  throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
7767
8131
  }
7768
8132
  }
7769
- function readRunSourceTaskContract(projectRoot, runId, taskId2) {
8133
+ function readRunSourceTaskContract(projectRoot, runId, taskId3) {
7770
8134
  const run = readAuthorityRun5(projectRoot, runId);
7771
8135
  const raw = run?.sourceTask;
7772
8136
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7773
8137
  return null;
7774
8138
  }
7775
8139
  const record = raw;
7776
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId2?.trim();
8140
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
7777
8141
  if (!id) {
7778
8142
  return null;
7779
8143
  }
@@ -7909,15 +8273,15 @@ async function executeRigOwnedTaskRun(context, input) {
7909
8273
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
7910
8274
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
7911
8275
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
7912
- const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
8276
+ const planningArtifactPath = resolve20("artifacts", runtimeTaskId, "implementation-plan.md");
7913
8277
  const persistedPlanning = {
7914
8278
  ...planningClassification,
7915
8279
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
7916
8280
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
7917
8281
  classifiedAt: new Date().toISOString()
7918
8282
  };
7919
- mkdirSync7(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
7920
- writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
8283
+ mkdirSync7(resolve20(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
8284
+ writeFileSync6(resolve20(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
7921
8285
  `, "utf8");
7922
8286
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
7923
8287
  prompt = `${prompt}
@@ -7967,7 +8331,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7967
8331
  let verificationStarted = false;
7968
8332
  let reviewStarted = false;
7969
8333
  let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
7970
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
8334
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve20(existingRunRecord.sessionPath, "..") : null;
7971
8335
  let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
7972
8336
  let latestProviderCommand = null;
7973
8337
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
@@ -8050,10 +8414,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8050
8414
  patchAuthorityRun(context.projectRoot, input.runId, {
8051
8415
  status: "running",
8052
8416
  worktreePath: latestRuntimeWorkspace,
8053
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8417
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve20(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
8054
8418
  logRoot: latestLogsDir,
8055
- sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
8056
- sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
8419
+ sessionPath: latestSessionDir ? resolve20(latestSessionDir, "session.json") : null,
8420
+ sessionLogPath: latestLogsDir ? resolve20(latestLogsDir, "agent-stdout.log") : null,
8057
8421
  branch: runtimeId
8058
8422
  });
8059
8423
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -8061,7 +8425,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8061
8425
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
8062
8426
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
8063
8427
  if (readyFile) {
8064
- mkdirSync7(resolve21(readyFile, ".."), { recursive: true });
8428
+ mkdirSync7(resolve20(readyFile, ".."), { recursive: true });
8065
8429
  writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
8066
8430
  `, "utf8");
8067
8431
  }
@@ -8210,7 +8574,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8210
8574
  if (providerLogs.length > 0) {
8211
8575
  for (const providerLog of providerLogs) {
8212
8576
  appendRunLog(context.projectRoot, input.runId, providerLog);
8577
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
8213
8578
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
8579
+ if (providerLog.title === "Tool activity")
8580
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8214
8581
  }
8215
8582
  }
8216
8583
  if (input.runtimeAdapter === "codex") {
@@ -8368,7 +8735,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8368
8735
  let acceptedArtifactObservedAt = null;
8369
8736
  let acceptedArtifactPollTimer = null;
8370
8737
  let acceptedArtifactKillTimer = null;
8371
- const attemptExit = await new Promise((resolve22) => {
8738
+ const attemptExit = await new Promise((resolve21) => {
8372
8739
  let settled = false;
8373
8740
  const settle = (result) => {
8374
8741
  if (settled)
@@ -8376,7 +8743,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8376
8743
  settled = true;
8377
8744
  if (acceptedArtifactPollTimer)
8378
8745
  clearInterval(acceptedArtifactPollTimer);
8379
- resolve22(result);
8746
+ resolve21(result);
8380
8747
  };
8381
8748
  const pollAcceptedArtifacts = () => {
8382
8749
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -8449,7 +8816,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8449
8816
  });
8450
8817
  for (const pendingLog of pendingLogs) {
8451
8818
  appendRunLog(context.projectRoot, input.runId, pendingLog);
8819
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
8452
8820
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
8821
+ if (pendingLog.title === "Tool activity")
8822
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
8453
8823
  }
8454
8824
  process.off("SIGTERM", forwardSigterm);
8455
8825
  if (attemptExit.error) {
@@ -8575,8 +8945,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8575
8945
  }
8576
8946
  if (planningClassification.planningRequired) {
8577
8947
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
8578
- const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
8579
- if (!existsSync12(expectedPlanPath)) {
8948
+ const expectedPlanPath = resolve20(planWorkspace, planningArtifactPath);
8949
+ if (!existsSync11(expectedPlanPath)) {
8580
8950
  const failedAt = new Date().toISOString();
8581
8951
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
8582
8952
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -8596,6 +8966,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8596
8966
  throw new CliError2(failureDetail, 1);
8597
8967
  }
8598
8968
  }
8969
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
8970
+ appendPiStageLog({
8971
+ projectRoot: context.projectRoot,
8972
+ runId: input.runId,
8973
+ stage: "Validate",
8974
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
8975
+ status: "completed"
8976
+ });
8977
+ if (verificationAction && !reviewStarted) {
8978
+ verificationAction.complete("Completion verification checks finished.");
8979
+ }
8980
+ if (!reviewAction) {
8981
+ promoteToReviewing("Server-owned closeout is queued.");
8982
+ }
8983
+ if (reviewAction) {
8984
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
8985
+ }
8986
+ const requestedAt = new Date().toISOString();
8987
+ patchAuthorityRun(context.projectRoot, input.runId, {
8988
+ status: "reviewing",
8989
+ completedAt: null,
8990
+ errorText: null,
8991
+ serverCloseout: {
8992
+ status: "pending",
8993
+ phase: "queued",
8994
+ requestedAt,
8995
+ updatedAt: requestedAt,
8996
+ runtimeWorkspace: latestRuntimeWorkspace,
8997
+ branch: latestRuntimeBranch,
8998
+ taskId: input.taskId ?? runtimeTaskId
8999
+ }
9000
+ });
9001
+ appendRunLog(context.projectRoot, input.runId, {
9002
+ id: `log:${input.runId}:server-closeout-requested`,
9003
+ title: "Server-owned closeout requested",
9004
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
9005
+ tone: "info",
9006
+ status: "reviewing",
9007
+ createdAt: requestedAt,
9008
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
9009
+ });
9010
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
9011
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
9012
+ await context.emitEvent("command.finished", {
9013
+ command: [
9014
+ "rig",
9015
+ "server",
9016
+ "task-run",
9017
+ ...input.taskId ? ["--task", input.taskId] : [],
9018
+ ...input.title ? ["--title", input.title] : []
9019
+ ],
9020
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
9021
+ exitCode: 0,
9022
+ durationMs: 0,
9023
+ startedAt,
9024
+ finishedAt: requestedAt
9025
+ });
9026
+ return;
9027
+ }
8599
9028
  const runPiPrFeedbackFix = async (message2) => {
8600
9029
  appendPiStageLog({
8601
9030
  projectRoot: context.projectRoot,
@@ -8623,11 +9052,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8623
9052
  child.stdin.write(message2);
8624
9053
  }
8625
9054
  child.stdin.end();
9055
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
9056
+ let feedbackAssistantText = "";
9057
+ const feedbackPendingToolUses = new Map;
8626
9058
  const stdout = createLineInterface({ input: child.stdout });
8627
9059
  stdout.on("line", (line) => {
8628
9060
  const trimmed = line.trim();
8629
9061
  if (!trimmed)
8630
9062
  return;
9063
+ try {
9064
+ const record = JSON.parse(trimmed);
9065
+ const providerLogs = buildClaudeLogsFromRecord({
9066
+ runId: input.runId,
9067
+ record,
9068
+ createdAtFallback: new Date().toISOString(),
9069
+ status: "reviewing",
9070
+ pendingToolUses: feedbackPendingToolUses
9071
+ });
9072
+ for (const providerLog of providerLogs) {
9073
+ appendRunLog(context.projectRoot, input.runId, providerLog);
9074
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
9075
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
9076
+ if (providerLog.title === "Tool activity")
9077
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9078
+ }
9079
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
9080
+ projectRoot: context.projectRoot,
9081
+ runId: input.runId,
9082
+ messageId: feedbackAssistantMessageId,
9083
+ record,
9084
+ assistantText: feedbackAssistantText
9085
+ });
9086
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
9087
+ if (hadAssistantDelta) {
9088
+ feedbackAssistantText = nextFeedbackAssistantText;
9089
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9090
+ }
9091
+ if (providerLogs.length > 0 || hadAssistantDelta)
9092
+ return;
9093
+ } catch {}
8631
9094
  appendRunLog(context.projectRoot, input.runId, {
8632
9095
  id: nextRunLogId(),
8633
9096
  title: "Pi PR feedback fix output",
@@ -8653,10 +9116,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
8653
9116
  });
8654
9117
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
8655
9118
  });
8656
- const exitCode = await new Promise((resolve22) => {
8657
- child.once("error", () => resolve22(1));
8658
- child.once("close", (code) => resolve22(code ?? 1));
9119
+ const exitCode = await new Promise((resolve21) => {
9120
+ child.once("error", () => resolve21(1));
9121
+ child.once("close", (code) => resolve21(code ?? 1));
8659
9122
  });
9123
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
9124
+ runId: input.runId,
9125
+ status: exitCode === 0 ? "completed" : "failed",
9126
+ pendingToolUses: feedbackPendingToolUses
9127
+ })) {
9128
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
9129
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
9130
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
9131
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9132
+ }
9133
+ if (feedbackAssistantText.trim()) {
9134
+ appendRunTimeline(context.projectRoot, input.runId, {
9135
+ id: feedbackAssistantMessageId,
9136
+ type: "assistant_message",
9137
+ text: feedbackAssistantText,
9138
+ state: "completed",
9139
+ createdAt: new Date().toISOString(),
9140
+ completedAt: new Date().toISOString()
9141
+ });
9142
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
9143
+ }
8660
9144
  if (exitCode !== 0) {
8661
9145
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
8662
9146
  }
@@ -8774,8 +9258,8 @@ async function executeTest(context, args) {
8774
9258
  }
8775
9259
 
8776
9260
  // packages/cli/src/commands/setup.ts
8777
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
8778
- import { resolve as resolve22 } from "path";
9261
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
9262
+ import { resolve as resolve21 } from "path";
8779
9263
  import { createPluginHost } from "@rig/core";
8780
9264
  import {
8781
9265
  isSupportedBunVersion as isSupportedBunVersion2,
@@ -8838,8 +9322,8 @@ function runSetupInit(projectRoot) {
8838
9322
  mkdirSync8(stateDir, { recursive: true });
8839
9323
  mkdirSync8(logsDir, { recursive: true });
8840
9324
  mkdirSync8(artifactsDir, { recursive: true });
8841
- const failuresPath = resolve22(stateDir, "failed_approaches.md");
8842
- if (!existsSync13(failuresPath)) {
9325
+ const failuresPath = resolve21(stateDir, "failed_approaches.md");
9326
+ if (!existsSync12(failuresPath)) {
8843
9327
  writeFileSync7(failuresPath, `# Failed Approaches
8844
9328
 
8845
9329
  `, "utf-8");
@@ -8857,18 +9341,18 @@ async function runSetupCheck(projectRoot) {
8857
9341
  }
8858
9342
  async function runSetupPreflight(projectRoot) {
8859
9343
  await runSetupCheck(projectRoot);
8860
- const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
8861
- if (existsSync13(validationRoot)) {
9344
+ const validationRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
9345
+ if (existsSync12(validationRoot)) {
8862
9346
  const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
8863
9347
  for (const validator of validators) {
8864
- const script = resolve22(validationRoot, validator.name, "validate.sh");
8865
- if (existsSync13(script)) {
9348
+ const script = resolve21(validationRoot, validator.name, "validate.sh");
9349
+ if (existsSync12(script)) {
8866
9350
  console.log(`OK: validator script ${script}`);
8867
9351
  }
8868
9352
  }
8869
9353
  }
8870
- const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
8871
- if (existsSync13(hooksRoot)) {
9354
+ const hooksRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
9355
+ if (existsSync12(hooksRoot)) {
8872
9356
  const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
8873
9357
  for (const hook of hooks) {
8874
9358
  console.log(`OK: hook ${hook}`);