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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task-run-driver.ts
3
- import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
4
- import { resolve as resolve4 } from "path";
3
+ import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2 } from "fs";
4
+ import { resolve as resolve6 } from "path";
5
5
  import { spawn, spawnSync } from "child_process";
6
6
  import { createInterface as createLineInterface } from "readline";
7
7
 
@@ -39,10 +39,9 @@ import {
39
39
  isCodexExecRecord
40
40
  } from "@rig/runtime/control-plane/provider/codex-exec-records";
41
41
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
42
- import { readAuthorityRun as readAuthorityRun3, readJsonFile, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
42
+ import { readAuthorityRun as readAuthorityRun3, readJsonFile as readJsonFile2, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
43
43
  import {
44
- buildTaskRunLifecycleComment,
45
- updateConfiguredTaskSourceTask
44
+ buildTaskRunLifecycleComment
46
45
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
47
46
  import {
48
47
  closeIssueAfterMergedPr,
@@ -135,6 +134,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
135
134
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
136
135
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
137
136
  const next = {
137
+ ...existing ?? {},
138
138
  runId: input.runId,
139
139
  projectRoot,
140
140
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -387,6 +387,194 @@ function renderSourceScopeValidation(task, validation) {
387
387
  `);
388
388
  }
389
389
 
390
+ // packages/cli/src/commands/_server-client.ts
391
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
392
+ import { resolve as resolve5 } from "path";
393
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
394
+
395
+ // packages/cli/src/commands/_connection-state.ts
396
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
397
+ import { homedir } from "os";
398
+ import { dirname, resolve as resolve4 } from "path";
399
+ function resolveGlobalConnectionsPath(env = process.env) {
400
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
401
+ if (explicit)
402
+ return resolve4(explicit);
403
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
404
+ if (stateDir)
405
+ return resolve4(stateDir, "connections.json");
406
+ return resolve4(homedir(), ".rig", "connections.json");
407
+ }
408
+ function resolveRepoConnectionPath(projectRoot) {
409
+ return resolve4(projectRoot, ".rig", "state", "connection.json");
410
+ }
411
+ function readJsonFile(path) {
412
+ if (!existsSync2(path))
413
+ return null;
414
+ try {
415
+ return JSON.parse(readFileSync2(path, "utf8"));
416
+ } catch (error) {
417
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
418
+ }
419
+ }
420
+ function normalizeConnection(value) {
421
+ if (!value || typeof value !== "object" || Array.isArray(value))
422
+ return null;
423
+ const record = value;
424
+ if (record.kind === "local")
425
+ return { kind: "local", mode: "auto" };
426
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
427
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
428
+ return { kind: "remote", baseUrl };
429
+ }
430
+ return null;
431
+ }
432
+ function readGlobalConnections(options = {}) {
433
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
434
+ const payload = readJsonFile(path);
435
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
436
+ return { connections: {} };
437
+ }
438
+ const rawConnections = payload.connections;
439
+ const connections = {};
440
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
441
+ for (const [alias, raw] of Object.entries(rawConnections)) {
442
+ const connection = normalizeConnection(raw);
443
+ if (connection)
444
+ connections[alias] = connection;
445
+ }
446
+ }
447
+ return { connections };
448
+ }
449
+ function readRepoConnection(projectRoot) {
450
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
451
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
452
+ return null;
453
+ const record = payload;
454
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
455
+ if (!selected)
456
+ return null;
457
+ return {
458
+ selected,
459
+ project: typeof record.project === "string" ? record.project : undefined,
460
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
461
+ };
462
+ }
463
+ function resolveSelectedConnection(projectRoot, options = {}) {
464
+ const repo = readRepoConnection(projectRoot);
465
+ if (!repo)
466
+ return null;
467
+ if (repo.selected === "local")
468
+ return { alias: "local", connection: { kind: "local", mode: "auto" } };
469
+ const global = readGlobalConnections(options);
470
+ const connection = global.connections[repo.selected];
471
+ if (!connection) {
472
+ throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
473
+ }
474
+ return { alias: repo.selected, connection };
475
+ }
476
+
477
+ // packages/cli/src/commands/_server-client.ts
478
+ var scopedGitHubBearerTokens = new Map;
479
+ function cleanToken(value) {
480
+ const trimmed = value?.trim();
481
+ return trimmed ? trimmed : null;
482
+ }
483
+ function readPrivateRemoteSessionToken(projectRoot) {
484
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
485
+ if (!existsSync3(path))
486
+ return null;
487
+ try {
488
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
489
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+ function readGitHubBearerTokenForRemote(projectRoot) {
495
+ const scopedKey = resolve5(projectRoot);
496
+ if (scopedGitHubBearerTokens.has(scopedKey))
497
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
498
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
499
+ if (privateSession)
500
+ return privateSession;
501
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
502
+ }
503
+ async function ensureServerForCli(projectRoot) {
504
+ try {
505
+ const selected = resolveSelectedConnection(projectRoot);
506
+ if (selected?.connection.kind === "remote") {
507
+ return {
508
+ baseUrl: selected.connection.baseUrl,
509
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
510
+ connectionKind: "remote"
511
+ };
512
+ }
513
+ const connection = await ensureLocalRigServerConnection(projectRoot);
514
+ return {
515
+ baseUrl: connection.baseUrl,
516
+ authToken: connection.authToken,
517
+ connectionKind: "local"
518
+ };
519
+ } catch (error) {
520
+ if (error instanceof Error) {
521
+ throw new CliError2(error.message, 1);
522
+ }
523
+ throw error;
524
+ }
525
+ }
526
+ function mergeHeaders(headers, authToken) {
527
+ const merged = new Headers(headers);
528
+ if (authToken) {
529
+ merged.set("authorization", `Bearer ${authToken}`);
530
+ }
531
+ return merged;
532
+ }
533
+ function diagnosticMessage(payload) {
534
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
535
+ return null;
536
+ const record = payload;
537
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
538
+ const messages = diagnostics.flatMap((entry) => {
539
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
540
+ return [];
541
+ const diagnostic = entry;
542
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
543
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
544
+ return message ? [`${kind}: ${message}`] : [];
545
+ });
546
+ return messages.length > 0 ? messages.join("; ") : null;
547
+ }
548
+ async function requestServerJson(context, pathname, init = {}) {
549
+ const server = await ensureServerForCli(context.projectRoot);
550
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
551
+ ...init,
552
+ headers: mergeHeaders(init.headers, server.authToken)
553
+ });
554
+ const text = await response.text();
555
+ const payload = text.trim().length > 0 ? (() => {
556
+ try {
557
+ return JSON.parse(text);
558
+ } catch {
559
+ return null;
560
+ }
561
+ })() : null;
562
+ if (!response.ok) {
563
+ const diagnostics = diagnosticMessage(payload);
564
+ const detail = diagnostics ?? (text || response.statusText);
565
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
566
+ }
567
+ return payload;
568
+ }
569
+ async function updateWorkspaceTaskViaServer(context, input) {
570
+ const payload = await requestServerJson(context, "/api/tasks/update", {
571
+ method: "POST",
572
+ headers: { "content-type": "application/json" },
573
+ body: JSON.stringify(input)
574
+ });
575
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
576
+ }
577
+
390
578
  // packages/cli/src/commands/task-run-driver.ts
391
579
  var PI_CANONICAL_RUN_STAGES = [
392
580
  "Connect",
@@ -405,7 +593,24 @@ var PI_CANONICAL_RUN_STAGES = [
405
593
  function canonicalPiRunStages() {
406
594
  return [...PI_CANONICAL_RUN_STAGES];
407
595
  }
596
+ function looksLikeGitHubToken(value) {
597
+ const token = value?.trim();
598
+ if (!token)
599
+ return false;
600
+ return /^(gh[opusr]_|github_pat_)/.test(token);
601
+ }
602
+ function githubBridgeEnv(token) {
603
+ const clean = token?.trim();
604
+ if (!clean)
605
+ return {};
606
+ return {
607
+ RIG_GITHUB_TOKEN: clean,
608
+ GITHUB_TOKEN: clean,
609
+ GH_TOKEN: clean
610
+ };
611
+ }
408
612
  function buildPiRigBridgeEnv(input) {
613
+ const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
409
614
  return {
410
615
  RIG_PROJECT_ROOT: input.projectRoot,
411
616
  PROJECT_RIG_ROOT: input.projectRoot,
@@ -415,7 +620,7 @@ function buildPiRigBridgeEnv(input) {
415
620
  RIG_RUNTIME_ADAPTER: "pi",
416
621
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
417
622
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
418
- ...input.githubToken ? { RIG_GITHUB_TOKEN: input.githubToken } : {}
623
+ ...githubBridgeEnv(githubToken)
419
624
  };
420
625
  }
421
626
  function runGitSync(cwd, args, input) {
@@ -437,12 +642,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
437
642
  return 0;
438
643
  let copied = 0;
439
644
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
440
- const sourcePath = resolve4(sourceRoot, relativePath);
441
- const targetPath = resolve4(targetRoot, relativePath);
645
+ const sourcePath = resolve6(sourceRoot, relativePath);
646
+ const targetPath = resolve6(targetRoot, relativePath);
442
647
  try {
443
648
  if (!statSync(sourcePath).isFile())
444
649
  continue;
445
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
650
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
446
651
  copyFileSync(sourcePath, targetPath);
447
652
  copied += 1;
448
653
  } catch {}
@@ -481,7 +686,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
481
686
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
482
687
  return {
483
688
  RIG_BASELINE_MODE: "dirty-snapshot",
484
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
689
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
485
690
  };
486
691
  }
487
692
  function positiveInt(value, fallback) {
@@ -489,7 +694,7 @@ function positiveInt(value, fallback) {
489
694
  }
490
695
  function resolveTaskRunAutomationLimits(config, env = process.env) {
491
696
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
492
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
697
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
493
698
  return {
494
699
  maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
495
700
  maxPrFixIterations: configuredPrFixIterations
@@ -599,9 +804,9 @@ function createCommandRunner(binary) {
599
804
  const stderrChunks = [];
600
805
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
601
806
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
602
- return await new Promise((resolve5) => {
603
- child.once("error", (error) => resolve5({ exitCode: 1, stderr: error.message }));
604
- child.once("close", (code) => resolve5({
807
+ return await new Promise((resolve7) => {
808
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
809
+ child.once("close", (code) => resolve7({
605
810
  exitCode: code ?? 1,
606
811
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
607
812
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -625,8 +830,9 @@ async function runTaskRunPostValidationLifecycle(input) {
625
830
  if (!taskId) {
626
831
  return { status: "skipped" };
627
832
  }
628
- const config = input.config ?? {};
629
- const prMode = config.pr?.mode ?? "auto";
833
+ const configInput = input.config ?? null;
834
+ const config = configInput ?? {};
835
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
630
836
  if (prMode === "off" || prMode === "ask") {
631
837
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
632
838
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -642,7 +848,7 @@ async function runTaskRunPostValidationLifecycle(input) {
642
848
  gitCommand
643
849
  });
644
850
  const prAutomation = input.prAutomation ?? runPrAutomation;
645
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
851
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
646
852
  const stage = input.appendStage ?? (() => {
647
853
  return;
648
854
  });
@@ -674,7 +880,9 @@ async function runTaskRunPostValidationLifecycle(input) {
674
880
  config,
675
881
  sourceTask: input.sourceTask,
676
882
  uploadedSnapshot: input.uploadedSnapshot,
883
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
677
884
  command: ghCommand,
885
+ gitCommand,
678
886
  steerPi,
679
887
  lifecycle: {
680
888
  onPrOpened: async ({ prUrl }) => {
@@ -723,8 +931,6 @@ async function runTaskRunPostValidationLifecycle(input) {
723
931
  runtimeWorkspace: input.runtimeWorkspace ?? null
724
932
  })
725
933
  }
726
- }).catch(() => {
727
- return;
728
934
  });
729
935
  }
730
936
  },
@@ -743,8 +949,6 @@ async function runTaskRunPostValidationLifecycle(input) {
743
949
  runtimeWorkspace: input.runtimeWorkspace ?? null
744
950
  })
745
951
  }
746
- }).catch(() => {
747
- return;
748
952
  });
749
953
  }
750
954
  },
@@ -773,8 +977,17 @@ async function runTaskRunPostValidationLifecycle(input) {
773
977
  errorText: detail
774
978
  })
775
979
  }
776
- }).catch(() => {
777
- return;
980
+ }).catch((error) => {
981
+ try {
982
+ appendRunLog(input.projectRoot, input.runId, {
983
+ id: `log:${input.runId}:task-source-needs-attention-update`,
984
+ title: "Task source needs-attention update failed",
985
+ detail: error instanceof Error ? error.message : String(error),
986
+ tone: "error",
987
+ status: "needs_attention",
988
+ createdAt: new Date().toISOString()
989
+ });
990
+ } catch {}
778
991
  });
779
992
  }
780
993
  return { status: "needs_attention", pr };
@@ -797,7 +1010,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
797
1010
  return null;
798
1011
  }
799
1012
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
800
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1013
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
801
1014
  if (!summary || summary.status !== "fail") {
802
1015
  continue;
803
1016
  }
@@ -878,14 +1091,14 @@ function readTaskRunAcceptedArtifactState(input) {
878
1091
  if (!input.taskId || !input.workspaceDir) {
879
1092
  return { accepted: false, reason: null };
880
1093
  }
881
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
882
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
883
- const taskResultPath = resolve4(artifactDir, "task-result.json");
1094
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1095
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1096
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
884
1097
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
885
1098
  if (reviewStatus !== "APPROVED") {
886
1099
  return { accepted: false, reason: null };
887
1100
  }
888
- const taskResult = readJsonFile(taskResultPath, null);
1101
+ const taskResult = readJsonFile2(taskResultPath, null);
889
1102
  if (taskResult?.status !== "completed") {
890
1103
  return { accepted: false, reason: null };
891
1104
  }
@@ -917,13 +1130,13 @@ function resolveTaskRunRetryContext(input) {
917
1130
  if (!input.taskId || !input.workspaceDir) {
918
1131
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
919
1132
  }
920
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
921
- const reviewStatePath = resolve4(artifactDir, "review-state.json");
922
- const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
923
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
924
- const failedApproachesPath = resolve4(input.workspaceDir, ".rig", "state", "failed_approaches.md");
925
- const validationSummaryPath = resolve4(artifactDir, "validation-summary.json");
926
- const reviewState = readJsonFile(reviewStatePath, null);
1133
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1134
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1135
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1136
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1137
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1138
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1139
+ const reviewState = readJsonFile2(reviewStatePath, null);
927
1140
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
928
1141
  const reviewRejected = isTaskRunReviewRejected(reviewState);
929
1142
  if (reviewStatus === "APPROVED") {
@@ -976,12 +1189,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
976
1189
  }
977
1190
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
978
1191
  }
1192
+ function appendAssistantTimelineFromRecord(input) {
1193
+ let nextAssistantText = input.assistantText;
1194
+ if (input.record.type === "message_update") {
1195
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1196
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1197
+ nextAssistantText += assistantMessageEvent.delta;
1198
+ }
1199
+ } else if (input.record.type === "stream_event") {
1200
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1201
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1202
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1203
+ nextAssistantText += delta.text;
1204
+ }
1205
+ } else if (input.record.type === "assistant") {
1206
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1207
+ const content = Array.isArray(message.content) ? message.content : [];
1208
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1209
+ if (fullText.length > nextAssistantText.length)
1210
+ nextAssistantText = fullText;
1211
+ }
1212
+ if (nextAssistantText !== input.assistantText) {
1213
+ appendRunTimeline(input.projectRoot, input.runId, {
1214
+ id: input.messageId,
1215
+ type: "assistant_message",
1216
+ text: nextAssistantText,
1217
+ state: "streaming",
1218
+ createdAt: new Date().toISOString()
1219
+ });
1220
+ }
1221
+ return nextAssistantText;
1222
+ }
1223
+ function appendPiToolTimelineFromRecord(input) {
1224
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1225
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1226
+ return false;
1227
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1228
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1229
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1230
+ appendRunTimeline(input.projectRoot, input.runId, {
1231
+ id: `tool:${toolCallId}:${type}`,
1232
+ type,
1233
+ toolName,
1234
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1235
+ createdAt: new Date().toISOString()
1236
+ });
1237
+ return true;
1238
+ }
1239
+ function isNonRenderablePiProtocolRecord(record) {
1240
+ const type = typeof record.type === "string" ? record.type : "";
1241
+ 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");
1242
+ }
1243
+ function appendToolTimelineFromLog(input) {
1244
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1245
+ if (title !== "Tool activity")
1246
+ return;
1247
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1248
+ 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;
1249
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1250
+ appendRunTimeline(input.projectRoot, input.runId, {
1251
+ id: `tool:${logId}`,
1252
+ type: "tool_execution_update",
1253
+ toolName,
1254
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1255
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1256
+ payload,
1257
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1258
+ });
1259
+ }
979
1260
  function readTaskRunReviewStatus(reviewStatusPath) {
980
- if (!existsSync2(reviewStatusPath)) {
1261
+ if (!existsSync4(reviewStatusPath)) {
981
1262
  return null;
982
1263
  }
983
1264
  try {
984
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1265
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
985
1266
  return status === "APPROVED" || status === "REJECTED" ? status : null;
986
1267
  } catch {
987
1268
  return null;
@@ -999,7 +1280,38 @@ function isTaskRunReviewRejected(reviewState) {
999
1280
  function runSourceTaskIdentity(sourceTask) {
1000
1281
  return sourceTask;
1001
1282
  }
1002
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1283
+ function sourceTaskIssueNodeId(sourceTask) {
1284
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1285
+ return null;
1286
+ const record = sourceTask;
1287
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1288
+ if (direct?.trim())
1289
+ return direct.trim();
1290
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1291
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1292
+ }
1293
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1294
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1295
+ id: input.taskId,
1296
+ ...input.update.status ? { status: input.update.status } : {},
1297
+ ...input.update.comment ? { comment: input.update.comment } : {},
1298
+ ...input.update.title ? { title: input.update.title } : {},
1299
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1300
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1301
+ });
1302
+ if (serverResult.ok === false) {
1303
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1304
+ }
1305
+ return {
1306
+ updated: serverResult.ok !== false,
1307
+ taskId: input.taskId,
1308
+ status: input.update.status,
1309
+ source: "server",
1310
+ sourceKind: "server",
1311
+ projectSync: serverResult.projectSync
1312
+ };
1313
+ };
1314
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1003
1315
  if (!taskId)
1004
1316
  return;
1005
1317
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1064,6 +1376,9 @@ function stringArrayField(record, key) {
1064
1376
  }
1065
1377
  async function executeRigOwnedTaskRun(context, input) {
1066
1378
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1379
+ const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1380
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
1381
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1067
1382
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1068
1383
  let prompt = buildRunPrompt({
1069
1384
  projectRoot: context.projectRoot,
@@ -1119,14 +1434,14 @@ async function executeRigOwnedTaskRun(context, input) {
1119
1434
  taskId: runtimeTaskId,
1120
1435
  createdAt: startedAt,
1121
1436
  runtimeAdapter: input.runtimeAdapter,
1122
- status: "created"
1437
+ status: resumeMode ? "preparing" : "created"
1123
1438
  });
1124
1439
  patchAuthorityRun(context.projectRoot, input.runId, {
1125
1440
  status: "preparing",
1126
1441
  startedAt,
1127
1442
  completedAt: null,
1128
1443
  errorText: null,
1129
- artifactRoot: null,
1444
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
1130
1445
  runtimeAdapter: input.runtimeAdapter,
1131
1446
  runtimeMode: input.runtimeMode,
1132
1447
  interactionMode: input.interactionMode,
@@ -1142,9 +1457,9 @@ async function executeRigOwnedTaskRun(context, input) {
1142
1457
  detail: input.taskId ?? input.title ?? runtimeTaskId
1143
1458
  });
1144
1459
  appendRunLog(context.projectRoot, input.runId, {
1145
- id: `log:${input.runId}:start`,
1146
- title: "Rig task run started",
1147
- detail: input.taskId ?? input.title ?? runtimeTaskId,
1460
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
1461
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
1462
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
1148
1463
  tone: "info",
1149
1464
  status: "preparing",
1150
1465
  createdAt: startedAt
@@ -1165,15 +1480,15 @@ async function executeRigOwnedTaskRun(context, input) {
1165
1480
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1166
1481
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1167
1482
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1168
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1483
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1169
1484
  const persistedPlanning = {
1170
1485
  ...planningClassification,
1171
1486
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1172
1487
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1173
1488
  classifiedAt: new Date().toISOString()
1174
1489
  };
1175
- mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1176
- writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1490
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1491
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1177
1492
  `, "utf8");
1178
1493
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1179
1494
  prompt = `${prompt}
@@ -1222,11 +1537,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1222
1537
  let reviewAction;
1223
1538
  let verificationStarted = false;
1224
1539
  let reviewStarted = false;
1225
- let latestRuntimeWorkspace = null;
1226
- let latestSessionDir = null;
1227
- let latestLogsDir = null;
1540
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1541
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1542
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1228
1543
  let latestProviderCommand = null;
1229
- let latestRuntimeBranch = null;
1544
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1230
1545
  let snapshotSidecarPromise = null;
1231
1546
  let dirtyBaselineApplied = false;
1232
1547
  const childEnv = {
@@ -1244,7 +1559,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1244
1559
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
1245
1560
  RIG_SERVER_RUN_ID: input.runId
1246
1561
  },
1247
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1562
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
1563
+ ...resumeMode ? {
1564
+ RIG_RUN_RESUME: "1",
1565
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1566
+ } : {}
1248
1567
  };
1249
1568
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1250
1569
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -1302,10 +1621,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1302
1621
  patchAuthorityRun(context.projectRoot, input.runId, {
1303
1622
  status: "running",
1304
1623
  worktreePath: latestRuntimeWorkspace,
1305
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1624
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1306
1625
  logRoot: latestLogsDir,
1307
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1308
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1626
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1627
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1309
1628
  branch: runtimeId
1310
1629
  });
1311
1630
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1313,8 +1632,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1313
1632
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1314
1633
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1315
1634
  if (readyFile) {
1316
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1317
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1635
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1636
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1318
1637
  `, "utf8");
1319
1638
  }
1320
1639
  appendRunLog(context.projectRoot, input.runId, {
@@ -1446,6 +1765,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1446
1765
  try {
1447
1766
  const record = JSON.parse(trimmed);
1448
1767
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
1768
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
1769
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1770
+ return;
1771
+ }
1449
1772
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
1450
1773
  runId: input.runId,
1451
1774
  record,
@@ -1462,7 +1785,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1462
1785
  if (providerLogs.length > 0) {
1463
1786
  for (const providerLog of providerLogs) {
1464
1787
  appendRunLog(context.projectRoot, input.runId, providerLog);
1788
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1465
1789
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1790
+ if (providerLog.title === "Tool activity")
1791
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1466
1792
  }
1467
1793
  }
1468
1794
  if (input.runtimeAdapter === "codex") {
@@ -1514,6 +1840,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1514
1840
  return;
1515
1841
  }
1516
1842
  }
1843
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
1844
+ return;
1845
+ }
1517
1846
  if (record.type === "assistant") {
1518
1847
  const message = record.message && typeof record.message === "object" ? record.message : record;
1519
1848
  const content = Array.isArray(message.content) ? message.content : [];
@@ -1550,7 +1879,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1550
1879
  let reviewFailureDetail = null;
1551
1880
  const stderrLines = [];
1552
1881
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1553
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
1882
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
1883
+ appendRunLog(context.projectRoot, input.runId, {
1884
+ id: `log:${input.runId}:resume-closeout-phase`,
1885
+ title: "Resume continuing from closeout phase",
1886
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
1887
+ tone: "info",
1888
+ status: resumePreviousStatus,
1889
+ createdAt: new Date().toISOString()
1890
+ });
1891
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
1892
+ exit = { code: 0, signal: null };
1893
+ } else if (resumeMode && latestRuntimeWorkspace) {
1894
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
1895
+ taskId: input.taskId ?? runtimeTaskId,
1896
+ workspaceDir: latestRuntimeWorkspace
1897
+ });
1898
+ if (acceptedArtifactState.accepted) {
1899
+ appendRunLog(context.projectRoot, input.runId, {
1900
+ id: `log:${input.runId}:resume-accepted-artifacts`,
1901
+ title: "Resume found accepted artifacts; continuing closeout",
1902
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
1903
+ tone: "info",
1904
+ status: "validating",
1905
+ createdAt: new Date().toISOString()
1906
+ });
1907
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
1908
+ exit = { code: 0, signal: null };
1909
+ }
1910
+ }
1911
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
1554
1912
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
1555
1913
  const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
1556
1914
  cwd: context.projectRoot,
@@ -1591,7 +1949,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1591
1949
  let acceptedArtifactObservedAt = null;
1592
1950
  let acceptedArtifactPollTimer = null;
1593
1951
  let acceptedArtifactKillTimer = null;
1594
- const attemptExit = await new Promise((resolve5) => {
1952
+ const attemptExit = await new Promise((resolve7) => {
1595
1953
  let settled = false;
1596
1954
  const settle = (result) => {
1597
1955
  if (settled)
@@ -1599,7 +1957,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1599
1957
  settled = true;
1600
1958
  if (acceptedArtifactPollTimer)
1601
1959
  clearInterval(acceptedArtifactPollTimer);
1602
- resolve5(result);
1960
+ resolve7(result);
1603
1961
  };
1604
1962
  const pollAcceptedArtifacts = () => {
1605
1963
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1672,7 +2030,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1672
2030
  });
1673
2031
  for (const pendingLog of pendingLogs) {
1674
2032
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2033
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1675
2034
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2035
+ if (pendingLog.title === "Tool activity")
2036
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1676
2037
  }
1677
2038
  process.off("SIGTERM", forwardSigterm);
1678
2039
  if (attemptExit.error) {
@@ -1798,8 +2159,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1798
2159
  }
1799
2160
  if (planningClassification.planningRequired) {
1800
2161
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1801
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1802
- if (!existsSync2(expectedPlanPath)) {
2162
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2163
+ if (!existsSync4(expectedPlanPath)) {
1803
2164
  const failedAt = new Date().toISOString();
1804
2165
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1805
2166
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1819,6 +2180,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1819
2180
  throw new CliError2(failureDetail, 1);
1820
2181
  }
1821
2182
  }
2183
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2184
+ appendPiStageLog({
2185
+ projectRoot: context.projectRoot,
2186
+ runId: input.runId,
2187
+ stage: "Validate",
2188
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2189
+ status: "completed"
2190
+ });
2191
+ if (verificationAction && !reviewStarted) {
2192
+ verificationAction.complete("Completion verification checks finished.");
2193
+ }
2194
+ if (!reviewAction) {
2195
+ promoteToReviewing("Server-owned closeout is queued.");
2196
+ }
2197
+ if (reviewAction) {
2198
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2199
+ }
2200
+ const requestedAt = new Date().toISOString();
2201
+ patchAuthorityRun(context.projectRoot, input.runId, {
2202
+ status: "reviewing",
2203
+ completedAt: null,
2204
+ errorText: null,
2205
+ serverCloseout: {
2206
+ status: "pending",
2207
+ phase: "queued",
2208
+ requestedAt,
2209
+ updatedAt: requestedAt,
2210
+ runtimeWorkspace: latestRuntimeWorkspace,
2211
+ branch: latestRuntimeBranch,
2212
+ taskId: input.taskId ?? runtimeTaskId
2213
+ }
2214
+ });
2215
+ appendRunLog(context.projectRoot, input.runId, {
2216
+ id: `log:${input.runId}:server-closeout-requested`,
2217
+ title: "Server-owned closeout requested",
2218
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2219
+ tone: "info",
2220
+ status: "reviewing",
2221
+ createdAt: requestedAt,
2222
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2223
+ });
2224
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2225
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2226
+ await context.emitEvent("command.finished", {
2227
+ command: [
2228
+ "rig",
2229
+ "server",
2230
+ "task-run",
2231
+ ...input.taskId ? ["--task", input.taskId] : [],
2232
+ ...input.title ? ["--title", input.title] : []
2233
+ ],
2234
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2235
+ exitCode: 0,
2236
+ durationMs: 0,
2237
+ startedAt,
2238
+ finishedAt: requestedAt
2239
+ });
2240
+ return;
2241
+ }
1822
2242
  const runPiPrFeedbackFix = async (message) => {
1823
2243
  appendPiStageLog({
1824
2244
  projectRoot: context.projectRoot,
@@ -1846,11 +2266,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1846
2266
  child.stdin.write(message);
1847
2267
  }
1848
2268
  child.stdin.end();
2269
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2270
+ let feedbackAssistantText = "";
2271
+ const feedbackPendingToolUses = new Map;
1849
2272
  const stdout = createLineInterface({ input: child.stdout });
1850
2273
  stdout.on("line", (line) => {
1851
2274
  const trimmed = line.trim();
1852
2275
  if (!trimmed)
1853
2276
  return;
2277
+ try {
2278
+ const record = JSON.parse(trimmed);
2279
+ const providerLogs = buildClaudeLogsFromRecord({
2280
+ runId: input.runId,
2281
+ record,
2282
+ createdAtFallback: new Date().toISOString(),
2283
+ status: "reviewing",
2284
+ pendingToolUses: feedbackPendingToolUses
2285
+ });
2286
+ for (const providerLog of providerLogs) {
2287
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2288
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2289
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2290
+ if (providerLog.title === "Tool activity")
2291
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2292
+ }
2293
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2294
+ projectRoot: context.projectRoot,
2295
+ runId: input.runId,
2296
+ messageId: feedbackAssistantMessageId,
2297
+ record,
2298
+ assistantText: feedbackAssistantText
2299
+ });
2300
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2301
+ if (hadAssistantDelta) {
2302
+ feedbackAssistantText = nextFeedbackAssistantText;
2303
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2304
+ }
2305
+ if (providerLogs.length > 0 || hadAssistantDelta)
2306
+ return;
2307
+ } catch {}
1854
2308
  appendRunLog(context.projectRoot, input.runId, {
1855
2309
  id: nextRunLogId(),
1856
2310
  title: "Pi PR feedback fix output",
@@ -1876,10 +2330,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1876
2330
  });
1877
2331
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1878
2332
  });
1879
- const exitCode = await new Promise((resolve5) => {
1880
- child.once("error", () => resolve5(1));
1881
- child.once("close", (code) => resolve5(code ?? 1));
2333
+ const exitCode = await new Promise((resolve7) => {
2334
+ child.once("error", () => resolve7(1));
2335
+ child.once("close", (code) => resolve7(code ?? 1));
1882
2336
  });
2337
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2338
+ runId: input.runId,
2339
+ status: exitCode === 0 ? "completed" : "failed",
2340
+ pendingToolUses: feedbackPendingToolUses
2341
+ })) {
2342
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2343
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2344
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2345
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2346
+ }
2347
+ if (feedbackAssistantText.trim()) {
2348
+ appendRunTimeline(context.projectRoot, input.runId, {
2349
+ id: feedbackAssistantMessageId,
2350
+ type: "assistant_message",
2351
+ text: feedbackAssistantText,
2352
+ state: "completed",
2353
+ createdAt: new Date().toISOString(),
2354
+ completedAt: new Date().toISOString()
2355
+ });
2356
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2357
+ }
1883
2358
  if (exitCode !== 0) {
1884
2359
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1885
2360
  }