@h-rig/cli 0.0.6-alpha.3 → 0.0.6-alpha.31

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.
Files changed (47) hide show
  1. package/dist/bin/rig.js +3562 -1125
  2. package/dist/src/commands/_authority-runs.js +1 -0
  3. package/dist/src/commands/_cli-format.js +369 -0
  4. package/dist/src/commands/_connection-state.js +1 -3
  5. package/dist/src/commands/_doctor-checks.js +13 -27
  6. package/dist/src/commands/_help-catalog.js +388 -0
  7. package/dist/src/commands/_operator-surface.js +204 -0
  8. package/dist/src/commands/_operator-view.js +861 -56
  9. package/dist/src/commands/_parsers.js +0 -2
  10. package/dist/src/commands/_pi-frontend.js +841 -0
  11. package/dist/src/commands/_pi-install.js +4 -3
  12. package/dist/src/commands/_pi-worker-bridge-extension.js +759 -0
  13. package/dist/src/commands/_policy.js +0 -2
  14. package/dist/src/commands/_preflight.js +32 -109
  15. package/dist/src/commands/_run-driver-helpers.js +0 -2
  16. package/dist/src/commands/_server-client.js +161 -31
  17. package/dist/src/commands/_snapshot-upload.js +8 -23
  18. package/dist/src/commands/_task-picker.js +44 -16
  19. package/dist/src/commands/agent.js +9 -9
  20. package/dist/src/commands/browser.js +4 -6
  21. package/dist/src/commands/connect.js +132 -25
  22. package/dist/src/commands/dist.js +4 -6
  23. package/dist/src/commands/doctor.js +13 -27
  24. package/dist/src/commands/github.js +10 -25
  25. package/dist/src/commands/inbox.js +351 -31
  26. package/dist/src/commands/init.js +298 -71
  27. package/dist/src/commands/inspect.js +10 -12
  28. package/dist/src/commands/inspector.js +2 -4
  29. package/dist/src/commands/plugin.js +81 -22
  30. package/dist/src/commands/profile-and-review.js +8 -10
  31. package/dist/src/commands/queue.js +2 -3
  32. package/dist/src/commands/remote.js +18 -20
  33. package/dist/src/commands/repo-git-harness.js +6 -8
  34. package/dist/src/commands/run.js +1157 -122
  35. package/dist/src/commands/server.js +217 -33
  36. package/dist/src/commands/setup.js +17 -37
  37. package/dist/src/commands/task-report-bug.js +5 -7
  38. package/dist/src/commands/task-run-driver.js +660 -73
  39. package/dist/src/commands/task.js +1542 -252
  40. package/dist/src/commands/test.js +3 -5
  41. package/dist/src/commands/workspace.js +4 -6
  42. package/dist/src/commands.js +3548 -1105
  43. package/dist/src/index.js +3562 -1128
  44. package/dist/src/launcher.js +5 -3
  45. package/dist/src/report-bug.js +3 -3
  46. package/dist/src/runner.js +5 -19
  47. package/package.json +6 -4
@@ -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
 
@@ -9,8 +9,6 @@ import { createInterface as createLineInterface } from "readline";
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
10
10
  import { CliError } from "@rig/runtime/control-plane/errors";
11
11
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
12
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
13
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
14
12
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
15
13
  import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
16
14
  function formatCommand(parts) {
@@ -39,10 +37,9 @@ import {
39
37
  isCodexExecRecord
40
38
  } from "@rig/runtime/control-plane/provider/codex-exec-records";
41
39
  import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
42
- import { readAuthorityRun as readAuthorityRun3, readJsonFile, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
40
+ import { readAuthorityRun as readAuthorityRun3, readJsonFile as readJsonFile2, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
43
41
  import {
44
- buildTaskRunLifecycleComment,
45
- updateConfiguredTaskSourceTask
42
+ buildTaskRunLifecycleComment
46
43
  } from "@rig/runtime/control-plane/tasks/source-lifecycle";
47
44
  import {
48
45
  closeIssueAfterMergedPr,
@@ -135,6 +132,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
135
132
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
136
133
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
137
134
  const next = {
135
+ ...existing ?? {},
138
136
  runId: input.runId,
139
137
  projectRoot,
140
138
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -387,6 +385,194 @@ function renderSourceScopeValidation(task, validation) {
387
385
  `);
388
386
  }
389
387
 
388
+ // packages/cli/src/commands/_server-client.ts
389
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
390
+ import { resolve as resolve5 } from "path";
391
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
392
+
393
+ // packages/cli/src/commands/_connection-state.ts
394
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
395
+ import { homedir } from "os";
396
+ import { dirname, resolve as resolve4 } from "path";
397
+ function resolveGlobalConnectionsPath(env = process.env) {
398
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
399
+ if (explicit)
400
+ return resolve4(explicit);
401
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
402
+ if (stateDir)
403
+ return resolve4(stateDir, "connections.json");
404
+ return resolve4(homedir(), ".rig", "connections.json");
405
+ }
406
+ function resolveRepoConnectionPath(projectRoot) {
407
+ return resolve4(projectRoot, ".rig", "state", "connection.json");
408
+ }
409
+ function readJsonFile(path) {
410
+ if (!existsSync2(path))
411
+ return null;
412
+ try {
413
+ return JSON.parse(readFileSync2(path, "utf8"));
414
+ } catch (error) {
415
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
416
+ }
417
+ }
418
+ function normalizeConnection(value) {
419
+ if (!value || typeof value !== "object" || Array.isArray(value))
420
+ return null;
421
+ const record = value;
422
+ if (record.kind === "local")
423
+ return { kind: "local", mode: "auto" };
424
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
425
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
426
+ return { kind: "remote", baseUrl };
427
+ }
428
+ return null;
429
+ }
430
+ function readGlobalConnections(options = {}) {
431
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
432
+ const payload = readJsonFile(path);
433
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
434
+ return { connections: {} };
435
+ }
436
+ const rawConnections = payload.connections;
437
+ const connections = {};
438
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
439
+ for (const [alias, raw] of Object.entries(rawConnections)) {
440
+ const connection = normalizeConnection(raw);
441
+ if (connection)
442
+ connections[alias] = connection;
443
+ }
444
+ }
445
+ return { connections };
446
+ }
447
+ function readRepoConnection(projectRoot) {
448
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
449
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
450
+ return null;
451
+ const record = payload;
452
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
453
+ if (!selected)
454
+ return null;
455
+ return {
456
+ selected,
457
+ project: typeof record.project === "string" ? record.project : undefined,
458
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
459
+ };
460
+ }
461
+ function resolveSelectedConnection(projectRoot, options = {}) {
462
+ const repo = readRepoConnection(projectRoot);
463
+ if (!repo)
464
+ return null;
465
+ if (repo.selected === "local")
466
+ return { alias: "local", connection: { kind: "local", mode: "auto" } };
467
+ const global = readGlobalConnections(options);
468
+ const connection = global.connections[repo.selected];
469
+ if (!connection) {
470
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
471
+ }
472
+ return { alias: repo.selected, connection };
473
+ }
474
+
475
+ // packages/cli/src/commands/_server-client.ts
476
+ var scopedGitHubBearerTokens = new Map;
477
+ function cleanToken(value) {
478
+ const trimmed = value?.trim();
479
+ return trimmed ? trimmed : null;
480
+ }
481
+ function readPrivateRemoteSessionToken(projectRoot) {
482
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
483
+ if (!existsSync3(path))
484
+ return null;
485
+ try {
486
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
487
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
488
+ } catch {
489
+ return null;
490
+ }
491
+ }
492
+ function readGitHubBearerTokenForRemote(projectRoot) {
493
+ const scopedKey = resolve5(projectRoot);
494
+ if (scopedGitHubBearerTokens.has(scopedKey))
495
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
496
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
497
+ if (privateSession)
498
+ return privateSession;
499
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
500
+ }
501
+ async function ensureServerForCli(projectRoot) {
502
+ try {
503
+ const selected = resolveSelectedConnection(projectRoot);
504
+ if (selected?.connection.kind === "remote") {
505
+ return {
506
+ baseUrl: selected.connection.baseUrl,
507
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
508
+ connectionKind: "remote"
509
+ };
510
+ }
511
+ const connection = await ensureLocalRigServerConnection(projectRoot);
512
+ return {
513
+ baseUrl: connection.baseUrl,
514
+ authToken: connection.authToken,
515
+ connectionKind: "local"
516
+ };
517
+ } catch (error) {
518
+ if (error instanceof Error) {
519
+ throw new CliError2(error.message, 1);
520
+ }
521
+ throw error;
522
+ }
523
+ }
524
+ function mergeHeaders(headers, authToken) {
525
+ const merged = new Headers(headers);
526
+ if (authToken) {
527
+ merged.set("authorization", `Bearer ${authToken}`);
528
+ }
529
+ return merged;
530
+ }
531
+ function diagnosticMessage(payload) {
532
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
533
+ return null;
534
+ const record = payload;
535
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
536
+ const messages = diagnostics.flatMap((entry) => {
537
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
538
+ return [];
539
+ const diagnostic = entry;
540
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
541
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
542
+ return message ? [`${kind}: ${message}`] : [];
543
+ });
544
+ return messages.length > 0 ? messages.join("; ") : null;
545
+ }
546
+ async function requestServerJson(context, pathname, init = {}) {
547
+ const server = await ensureServerForCli(context.projectRoot);
548
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
549
+ ...init,
550
+ headers: mergeHeaders(init.headers, server.authToken)
551
+ });
552
+ const text = await response.text();
553
+ const payload = text.trim().length > 0 ? (() => {
554
+ try {
555
+ return JSON.parse(text);
556
+ } catch {
557
+ return null;
558
+ }
559
+ })() : null;
560
+ if (!response.ok) {
561
+ const diagnostics = diagnosticMessage(payload);
562
+ const detail = diagnostics ?? (text || response.statusText);
563
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
564
+ }
565
+ return payload;
566
+ }
567
+ async function updateWorkspaceTaskViaServer(context, input) {
568
+ const payload = await requestServerJson(context, "/api/tasks/update", {
569
+ method: "POST",
570
+ headers: { "content-type": "application/json" },
571
+ body: JSON.stringify(input)
572
+ });
573
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
574
+ }
575
+
390
576
  // packages/cli/src/commands/task-run-driver.ts
391
577
  var PI_CANONICAL_RUN_STAGES = [
392
578
  "Connect",
@@ -430,6 +616,7 @@ function buildPiRigBridgeEnv(input) {
430
616
  RIG_SERVER_RUN_ID: input.runId,
431
617
  RIG_TASK_ID: input.taskId,
432
618
  RIG_RUNTIME_ADAPTER: "pi",
619
+ RIG_STEERING_POLL_MS: "0",
433
620
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
434
621
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
435
622
  ...githubBridgeEnv(githubToken)
@@ -454,12 +641,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
454
641
  return 0;
455
642
  let copied = 0;
456
643
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
457
- const sourcePath = resolve4(sourceRoot, relativePath);
458
- const targetPath = resolve4(targetRoot, relativePath);
644
+ const sourcePath = resolve6(sourceRoot, relativePath);
645
+ const targetPath = resolve6(targetRoot, relativePath);
459
646
  try {
460
647
  if (!statSync(sourcePath).isFile())
461
648
  continue;
462
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
649
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
463
650
  copyFileSync(sourcePath, targetPath);
464
651
  copied += 1;
465
652
  } catch {}
@@ -498,7 +685,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
498
685
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
499
686
  return {
500
687
  RIG_BASELINE_MODE: "dirty-snapshot",
501
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
688
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
502
689
  };
503
690
  }
504
691
  function positiveInt(value, fallback) {
@@ -506,7 +693,7 @@ function positiveInt(value, fallback) {
506
693
  }
507
694
  function resolveTaskRunAutomationLimits(config, env = process.env) {
508
695
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
509
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
696
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
510
697
  return {
511
698
  maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
512
699
  maxPrFixIterations: configuredPrFixIterations
@@ -616,9 +803,9 @@ function createCommandRunner(binary) {
616
803
  const stderrChunks = [];
617
804
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
618
805
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
619
- return await new Promise((resolve5) => {
620
- child.once("error", (error) => resolve5({ exitCode: 1, stderr: error.message }));
621
- child.once("close", (code) => resolve5({
806
+ return await new Promise((resolve7) => {
807
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
808
+ child.once("close", (code) => resolve7({
622
809
  exitCode: code ?? 1,
623
810
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
624
811
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -642,8 +829,9 @@ async function runTaskRunPostValidationLifecycle(input) {
642
829
  if (!taskId) {
643
830
  return { status: "skipped" };
644
831
  }
645
- const config = input.config ?? {};
646
- const prMode = config.pr?.mode ?? "auto";
832
+ const configInput = input.config ?? null;
833
+ const config = configInput ?? {};
834
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
647
835
  if (prMode === "off" || prMode === "ask") {
648
836
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
649
837
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -659,7 +847,7 @@ async function runTaskRunPostValidationLifecycle(input) {
659
847
  gitCommand
660
848
  });
661
849
  const prAutomation = input.prAutomation ?? runPrAutomation;
662
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
850
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
663
851
  const stage = input.appendStage ?? (() => {
664
852
  return;
665
853
  });
@@ -691,7 +879,9 @@ async function runTaskRunPostValidationLifecycle(input) {
691
879
  config,
692
880
  sourceTask: input.sourceTask,
693
881
  uploadedSnapshot: input.uploadedSnapshot,
882
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
694
883
  command: ghCommand,
884
+ gitCommand,
695
885
  steerPi,
696
886
  lifecycle: {
697
887
  onPrOpened: async ({ prUrl }) => {
@@ -740,8 +930,6 @@ async function runTaskRunPostValidationLifecycle(input) {
740
930
  runtimeWorkspace: input.runtimeWorkspace ?? null
741
931
  })
742
932
  }
743
- }).catch(() => {
744
- return;
745
933
  });
746
934
  }
747
935
  },
@@ -760,8 +948,6 @@ async function runTaskRunPostValidationLifecycle(input) {
760
948
  runtimeWorkspace: input.runtimeWorkspace ?? null
761
949
  })
762
950
  }
763
- }).catch(() => {
764
- return;
765
951
  });
766
952
  }
767
953
  },
@@ -790,8 +976,17 @@ async function runTaskRunPostValidationLifecycle(input) {
790
976
  errorText: detail
791
977
  })
792
978
  }
793
- }).catch(() => {
794
- return;
979
+ }).catch((error) => {
980
+ try {
981
+ appendRunLog(input.projectRoot, input.runId, {
982
+ id: `log:${input.runId}:task-source-needs-attention-update`,
983
+ title: "Task source needs-attention update failed",
984
+ detail: error instanceof Error ? error.message : String(error),
985
+ tone: "error",
986
+ status: "needs_attention",
987
+ createdAt: new Date().toISOString()
988
+ });
989
+ } catch {}
795
990
  });
796
991
  }
797
992
  return { status: "needs_attention", pr };
@@ -814,7 +1009,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
814
1009
  return null;
815
1010
  }
816
1011
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
817
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1012
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
818
1013
  if (!summary || summary.status !== "fail") {
819
1014
  continue;
820
1015
  }
@@ -895,14 +1090,14 @@ function readTaskRunAcceptedArtifactState(input) {
895
1090
  if (!input.taskId || !input.workspaceDir) {
896
1091
  return { accepted: false, reason: null };
897
1092
  }
898
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
899
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
900
- const taskResultPath = resolve4(artifactDir, "task-result.json");
1093
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1094
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1095
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
901
1096
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
902
1097
  if (reviewStatus !== "APPROVED") {
903
1098
  return { accepted: false, reason: null };
904
1099
  }
905
- const taskResult = readJsonFile(taskResultPath, null);
1100
+ const taskResult = readJsonFile2(taskResultPath, null);
906
1101
  if (taskResult?.status !== "completed") {
907
1102
  return { accepted: false, reason: null };
908
1103
  }
@@ -934,13 +1129,13 @@ function resolveTaskRunRetryContext(input) {
934
1129
  if (!input.taskId || !input.workspaceDir) {
935
1130
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
936
1131
  }
937
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
938
- const reviewStatePath = resolve4(artifactDir, "review-state.json");
939
- const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
940
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
941
- const failedApproachesPath = resolve4(input.workspaceDir, ".rig", "state", "failed_approaches.md");
942
- const validationSummaryPath = resolve4(artifactDir, "validation-summary.json");
943
- const reviewState = readJsonFile(reviewStatePath, null);
1132
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1133
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1134
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1135
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1136
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1137
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1138
+ const reviewState = readJsonFile2(reviewStatePath, null);
944
1139
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
945
1140
  const reviewRejected = isTaskRunReviewRejected(reviewState);
946
1141
  if (reviewStatus === "APPROVED") {
@@ -993,12 +1188,143 @@ function summarizeTaskRunReviewFailure(reviewState) {
993
1188
  }
994
1189
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
995
1190
  }
1191
+ function appendAssistantTimelineFromRecord(input) {
1192
+ let nextAssistantText = input.assistantText;
1193
+ if (input.record.type === "message_update") {
1194
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1195
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1196
+ nextAssistantText += assistantMessageEvent.delta;
1197
+ }
1198
+ } else if (input.record.type === "stream_event") {
1199
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1200
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1201
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1202
+ nextAssistantText += delta.text;
1203
+ }
1204
+ } else if (input.record.type === "assistant") {
1205
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1206
+ const content = Array.isArray(message.content) ? message.content : [];
1207
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1208
+ if (fullText.length > nextAssistantText.length)
1209
+ nextAssistantText = fullText;
1210
+ }
1211
+ if (nextAssistantText !== input.assistantText) {
1212
+ appendRunTimeline(input.projectRoot, input.runId, {
1213
+ id: input.messageId,
1214
+ type: "assistant_message",
1215
+ text: nextAssistantText,
1216
+ state: "streaming",
1217
+ createdAt: new Date().toISOString()
1218
+ });
1219
+ }
1220
+ return nextAssistantText;
1221
+ }
1222
+ function appendPiRpcProtocolLogFromRecord(input) {
1223
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1224
+ if (type === "response") {
1225
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
1226
+ const success = input.record.success !== false;
1227
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
1228
+ return true;
1229
+ }
1230
+ appendRunLog(input.projectRoot, input.runId, {
1231
+ id: input.nextRunLogId(),
1232
+ title: success ? "Pi RPC response" : "Pi RPC error",
1233
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
1234
+ tone: success ? "tool" : "error",
1235
+ status: input.status,
1236
+ payload: input.record,
1237
+ createdAt: new Date().toISOString()
1238
+ });
1239
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
1240
+ return true;
1241
+ }
1242
+ if (type !== "extension_ui_request")
1243
+ return false;
1244
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
1245
+ let title = "Pi UI event";
1246
+ let detail = method;
1247
+ let tone = "info";
1248
+ if (method === "notify") {
1249
+ title = "Pi notification";
1250
+ detail = String(input.record.message ?? "");
1251
+ tone = input.record.notifyType === "error" ? "error" : "info";
1252
+ } else if (method === "setStatus") {
1253
+ title = "Pi UI status";
1254
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
1255
+ tone = "tool";
1256
+ } else if (method === "setWidget") {
1257
+ title = "Pi UI widget";
1258
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
1259
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
1260
+ tone = "tool";
1261
+ } else if (method === "setTitle") {
1262
+ title = "Pi UI title";
1263
+ detail = String(input.record.title ?? "");
1264
+ tone = "tool";
1265
+ } else if (method === "set_editor_text") {
1266
+ title = "Pi editor update";
1267
+ detail = String(input.record.text ?? "").slice(0, 500);
1268
+ tone = "tool";
1269
+ } else {
1270
+ title = "Pi UI request";
1271
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
1272
+ }
1273
+ appendRunLog(input.projectRoot, input.runId, {
1274
+ id: input.nextRunLogId(),
1275
+ title,
1276
+ detail,
1277
+ tone,
1278
+ status: input.status,
1279
+ payload: input.record,
1280
+ createdAt: new Date().toISOString()
1281
+ });
1282
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1283
+ return true;
1284
+ }
1285
+ function appendPiToolTimelineFromRecord(input) {
1286
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1287
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1288
+ return false;
1289
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1290
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1291
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1292
+ appendRunTimeline(input.projectRoot, input.runId, {
1293
+ id: `tool:${toolCallId}:${type}`,
1294
+ type,
1295
+ toolName,
1296
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1297
+ createdAt: new Date().toISOString()
1298
+ });
1299
+ return true;
1300
+ }
1301
+ function isNonRenderablePiProtocolRecord(record) {
1302
+ const type = typeof record.type === "string" ? record.type : "";
1303
+ return type === "agent_start" || type === "agent_end" || 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");
1304
+ }
1305
+ function appendToolTimelineFromLog(input) {
1306
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1307
+ if (title !== "Tool activity")
1308
+ return;
1309
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1310
+ 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;
1311
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1312
+ appendRunTimeline(input.projectRoot, input.runId, {
1313
+ id: `tool:${logId}`,
1314
+ type: "tool_execution_update",
1315
+ toolName,
1316
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1317
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1318
+ payload,
1319
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1320
+ });
1321
+ }
996
1322
  function readTaskRunReviewStatus(reviewStatusPath) {
997
- if (!existsSync2(reviewStatusPath)) {
1323
+ if (!existsSync4(reviewStatusPath)) {
998
1324
  return null;
999
1325
  }
1000
1326
  try {
1001
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1327
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1002
1328
  return status === "APPROVED" || status === "REJECTED" ? status : null;
1003
1329
  } catch {
1004
1330
  return null;
@@ -1016,7 +1342,38 @@ function isTaskRunReviewRejected(reviewState) {
1016
1342
  function runSourceTaskIdentity(sourceTask) {
1017
1343
  return sourceTask;
1018
1344
  }
1019
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1345
+ function sourceTaskIssueNodeId(sourceTask) {
1346
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1347
+ return null;
1348
+ const record = sourceTask;
1349
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1350
+ if (direct?.trim())
1351
+ return direct.trim();
1352
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1353
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1354
+ }
1355
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1356
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1357
+ id: input.taskId,
1358
+ ...input.update.status ? { status: input.update.status } : {},
1359
+ ...input.update.comment ? { comment: input.update.comment } : {},
1360
+ ...input.update.title ? { title: input.update.title } : {},
1361
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1362
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1363
+ });
1364
+ if (serverResult.ok === false) {
1365
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1366
+ }
1367
+ return {
1368
+ updated: serverResult.ok !== false,
1369
+ taskId: input.taskId,
1370
+ status: input.update.status,
1371
+ source: "server",
1372
+ sourceKind: "server",
1373
+ projectSync: serverResult.projectSync
1374
+ };
1375
+ };
1376
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1020
1377
  if (!taskId)
1021
1378
  return;
1022
1379
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1081,6 +1438,9 @@ function stringArrayField(record, key) {
1081
1438
  }
1082
1439
  async function executeRigOwnedTaskRun(context, input) {
1083
1440
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1441
+ const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1442
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
1443
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1084
1444
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1085
1445
  let prompt = buildRunPrompt({
1086
1446
  projectRoot: context.projectRoot,
@@ -1103,11 +1463,7 @@ async function executeRigOwnedTaskRun(context, input) {
1103
1463
  ...input.model ? ["--model", input.model] : [],
1104
1464
  "--prompt"
1105
1465
  ] : input.runtimeAdapter === "pi" ? [
1106
- "--print",
1107
- "--verbose",
1108
- "--mode",
1109
- "json",
1110
- "--no-session",
1466
+ "__rig_pi_session_daemon__",
1111
1467
  ...input.model ? ["--model", input.model] : []
1112
1468
  ] : [
1113
1469
  "--print",
@@ -1136,14 +1492,14 @@ async function executeRigOwnedTaskRun(context, input) {
1136
1492
  taskId: runtimeTaskId,
1137
1493
  createdAt: startedAt,
1138
1494
  runtimeAdapter: input.runtimeAdapter,
1139
- status: "created"
1495
+ status: resumeMode ? "preparing" : "created"
1140
1496
  });
1141
1497
  patchAuthorityRun(context.projectRoot, input.runId, {
1142
1498
  status: "preparing",
1143
1499
  startedAt,
1144
1500
  completedAt: null,
1145
1501
  errorText: null,
1146
- artifactRoot: null,
1502
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
1147
1503
  runtimeAdapter: input.runtimeAdapter,
1148
1504
  runtimeMode: input.runtimeMode,
1149
1505
  interactionMode: input.interactionMode,
@@ -1159,9 +1515,9 @@ async function executeRigOwnedTaskRun(context, input) {
1159
1515
  detail: input.taskId ?? input.title ?? runtimeTaskId
1160
1516
  });
1161
1517
  appendRunLog(context.projectRoot, input.runId, {
1162
- id: `log:${input.runId}:start`,
1163
- title: "Rig task run started",
1164
- detail: input.taskId ?? input.title ?? runtimeTaskId,
1518
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
1519
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
1520
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
1165
1521
  tone: "info",
1166
1522
  status: "preparing",
1167
1523
  createdAt: startedAt
@@ -1182,15 +1538,15 @@ async function executeRigOwnedTaskRun(context, input) {
1182
1538
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1183
1539
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1184
1540
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1185
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1541
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1186
1542
  const persistedPlanning = {
1187
1543
  ...planningClassification,
1188
1544
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1189
1545
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1190
1546
  classifiedAt: new Date().toISOString()
1191
1547
  };
1192
- mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1193
- writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1548
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1549
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1194
1550
  `, "utf8");
1195
1551
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1196
1552
  prompt = `${prompt}
@@ -1204,7 +1560,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1204
1560
  projectRoot: context.projectRoot,
1205
1561
  runId: input.runId,
1206
1562
  stage,
1207
- detail: stage === "Launch Pi" ? "Pi runtime bridge starting with pi-rig environment." : stage === "Plan" ? `${planningClassification.planningRequired ? "recorded" : "skipped"} (${planningClassification.reason}; size=${planningClassification.size}; risk=${planningClassification.risk})` : stage === "Implement" ? "Pi implementation pass is running." : null,
1563
+ detail: stage === "Launch Pi" ? "Worker Pi SDK session daemon starting; local frontend will attach over Rig-proxied WebSocket." : stage === "Plan" ? `${planningClassification.planningRequired ? "recorded" : "skipped"} (${planningClassification.reason}; size=${planningClassification.size}; risk=${planningClassification.risk})` : stage === "Implement" ? "Pi implementation pass is running in the worker runtime." : null,
1208
1564
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
1209
1565
  });
1210
1566
  }
@@ -1239,12 +1595,13 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1239
1595
  let reviewAction;
1240
1596
  let verificationStarted = false;
1241
1597
  let reviewStarted = false;
1242
- let latestRuntimeWorkspace = null;
1243
- let latestSessionDir = null;
1244
- let latestLogsDir = null;
1598
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1599
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1600
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1245
1601
  let latestProviderCommand = null;
1246
- let latestRuntimeBranch = null;
1602
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1247
1603
  let snapshotSidecarPromise = null;
1604
+ let wrapperManagesRuntimeSnapshot = false;
1248
1605
  let dirtyBaselineApplied = false;
1249
1606
  const childEnv = {
1250
1607
  ...process.env,
@@ -1261,7 +1618,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1261
1618
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
1262
1619
  RIG_SERVER_RUN_ID: input.runId
1263
1620
  },
1264
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1621
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
1622
+ ...resumeMode ? {
1623
+ RIG_RUN_RESUME: "1",
1624
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1625
+ } : {}
1265
1626
  };
1266
1627
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1267
1628
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -1293,6 +1654,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1293
1654
  detail: detail ?? "Verifier review is running."
1294
1655
  });
1295
1656
  };
1657
+ const nextRunLogId = createRunLogIdFactory(input.runId);
1296
1658
  const handleWrapperEvent = (rawPayload) => {
1297
1659
  let event = null;
1298
1660
  try {
@@ -1308,6 +1670,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1308
1670
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
1309
1671
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
1310
1672
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
1673
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
1311
1674
  latestRuntimeBranch = runtimeId;
1312
1675
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
1313
1676
  runtimeId: runtimeId ?? null,
@@ -1319,10 +1682,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1319
1682
  patchAuthorityRun(context.projectRoot, input.runId, {
1320
1683
  status: "running",
1321
1684
  worktreePath: latestRuntimeWorkspace,
1322
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1685
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1323
1686
  logRoot: latestLogsDir,
1324
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1325
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1687
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1688
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1326
1689
  branch: runtimeId
1327
1690
  });
1328
1691
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1330,8 +1693,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1330
1693
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1331
1694
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1332
1695
  if (readyFile) {
1333
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1334
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1696
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1697
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1335
1698
  `, "utf8");
1336
1699
  }
1337
1700
  appendRunLog(context.projectRoot, input.runId, {
@@ -1345,7 +1708,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1345
1708
  });
1346
1709
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1347
1710
  }
1348
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1711
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1349
1712
  snapshotSidecarPromise = (async () => {
1350
1713
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
1351
1714
  projectRoot: context.projectRoot,
@@ -1427,9 +1790,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1427
1790
  }
1428
1791
  return true;
1429
1792
  }
1793
+ if (event.type === "pi.sessiond.starting" || event.type === "pi.sessiond.ready" || event.type === "pi.session.event_stream.connected" || event.type === "pi.prompt.sent" || event.type === "pi.prompt.waiting" || event.type === "pi.session.agent_end" || event.type === "pi.session.error") {
1794
+ const title = event.type === "pi.sessiond.starting" ? "Starting worker Pi session daemon" : event.type === "pi.sessiond.ready" ? "Worker Pi daemon ready" : event.type === "pi.session.event_stream.connected" ? "Worker Pi event stream connected" : event.type === "pi.prompt.sent" ? "Delivered initial prompt to worker Pi" : event.type === "pi.prompt.waiting" ? "Worker Pi prompt waiting" : event.type === "pi.session.agent_end" ? "Worker Pi turn complete" : "Worker Pi session error";
1795
+ const detail = event.type === "pi.sessiond.ready" ? "Daemon accepted control connection; waiting for SDK session metadata." : event.type === "pi.sessiond.starting" ? String(payload.workspaceDir ?? "worker runtime") : event.type === "pi.prompt.sent" ? `${String(payload.bytes ?? "unknown")} prompt bytes sent.` : event.type === "pi.prompt.waiting" ? String(payload.reason ?? "empty prompt") : event.type === "pi.session.error" ? String(payload.message ?? "session error") : String(payload.sessionId ?? payload.runId ?? "worker Pi session");
1796
+ appendRunLog(context.projectRoot, input.runId, {
1797
+ id: nextRunLogId(),
1798
+ title,
1799
+ detail,
1800
+ tone: event.type === "pi.session.error" ? "error" : "info",
1801
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1802
+ payload: {
1803
+ eventType: event.type,
1804
+ runId: typeof payload.runId === "string" ? payload.runId : null,
1805
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
1806
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
1807
+ },
1808
+ createdAt: new Date().toISOString()
1809
+ });
1810
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1811
+ return true;
1812
+ }
1813
+ if (event.type === "pi.session.ready") {
1814
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
1815
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
1816
+ if (privateMetadata) {
1817
+ patchAuthorityRun(context.projectRoot, input.runId, {
1818
+ piSession: publicMetadata,
1819
+ piSessionPrivate: privateMetadata
1820
+ });
1821
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
1822
+ appendRunLog(context.projectRoot, input.runId, {
1823
+ id: `log:${input.runId}:pi-session-ready`,
1824
+ title: "Worker Pi session ready",
1825
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
1826
+ tone: "info",
1827
+ status: "running",
1828
+ payload: {
1829
+ sessionId: sessionId || null,
1830
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
1831
+ },
1832
+ createdAt: new Date().toISOString()
1833
+ });
1834
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
1835
+ }
1836
+ return true;
1837
+ }
1838
+ if (event.type === "pi.rpc.prompt.sent" || event.type === "pi.rpc.steering.delivered" || event.type === "pi.rpc.steering.poll.failed" || event.type === "pi.rpc.extension_ui.cancelled") {
1839
+ const title = event.type === "pi.rpc.prompt.sent" ? "Delivered initial prompt to worker Pi" : event.type === "pi.rpc.steering.delivered" ? "Delivered steering to worker Pi" : event.type === "pi.rpc.steering.poll.failed" ? "Worker Pi steering poll failed" : "Pi RPC UI request auto-cancelled";
1840
+ const detail = event.type === "pi.rpc.prompt.sent" ? `${String(payload.kind ?? "prompt")} prompt (${String(payload.bytes ?? "unknown")} bytes)` : event.type === "pi.rpc.steering.delivered" ? `${String(payload.actor ?? "operator")}: ${String(payload.message ?? "")}`.slice(0, 500) : event.type === "pi.rpc.steering.poll.failed" ? String(payload.error ?? "steering poll failed") : `${String(payload.method ?? "ui")}: ${String(payload.reason ?? "noninteractive worker session")}`;
1841
+ appendRunLog(context.projectRoot, input.runId, {
1842
+ id: nextRunLogId(),
1843
+ title,
1844
+ detail,
1845
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
1846
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1847
+ payload,
1848
+ createdAt: new Date().toISOString()
1849
+ });
1850
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1851
+ return true;
1852
+ }
1430
1853
  return false;
1431
1854
  };
1432
- const nextRunLogId = createRunLogIdFactory(input.runId);
1433
1855
  const handleAgentStdoutLine = (line) => {
1434
1856
  const trimmed = line.trim();
1435
1857
  if (!trimmed)
@@ -1463,6 +1885,19 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1463
1885
  try {
1464
1886
  const record = JSON.parse(trimmed);
1465
1887
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
1888
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
1889
+ projectRoot: context.projectRoot,
1890
+ runId: input.runId,
1891
+ record,
1892
+ status: liveLogStatus,
1893
+ nextRunLogId
1894
+ })) {
1895
+ return;
1896
+ }
1897
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
1898
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1899
+ return;
1900
+ }
1466
1901
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
1467
1902
  runId: input.runId,
1468
1903
  record,
@@ -1479,7 +1914,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1479
1914
  if (providerLogs.length > 0) {
1480
1915
  for (const providerLog of providerLogs) {
1481
1916
  appendRunLog(context.projectRoot, input.runId, providerLog);
1917
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1482
1918
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1919
+ if (providerLog.title === "Tool activity")
1920
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1483
1921
  }
1484
1922
  }
1485
1923
  if (input.runtimeAdapter === "codex") {
@@ -1531,6 +1969,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1531
1969
  return;
1532
1970
  }
1533
1971
  }
1972
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
1973
+ return;
1974
+ }
1534
1975
  if (record.type === "assistant") {
1535
1976
  const message = record.message && typeof record.message === "object" ? record.message : record;
1536
1977
  const content = Array.isArray(message.content) ? message.content : [];
@@ -1567,7 +2008,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1567
2008
  let reviewFailureDetail = null;
1568
2009
  const stderrLines = [];
1569
2010
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1570
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
2011
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
2012
+ appendRunLog(context.projectRoot, input.runId, {
2013
+ id: `log:${input.runId}:resume-closeout-phase`,
2014
+ title: "Resume continuing from closeout phase",
2015
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
2016
+ tone: "info",
2017
+ status: resumePreviousStatus,
2018
+ createdAt: new Date().toISOString()
2019
+ });
2020
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
2021
+ exit = { code: 0, signal: null };
2022
+ } else if (resumeMode && latestRuntimeWorkspace) {
2023
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
2024
+ taskId: input.taskId ?? runtimeTaskId,
2025
+ workspaceDir: latestRuntimeWorkspace
2026
+ });
2027
+ if (acceptedArtifactState.accepted) {
2028
+ appendRunLog(context.projectRoot, input.runId, {
2029
+ id: `log:${input.runId}:resume-accepted-artifacts`,
2030
+ title: "Resume found accepted artifacts; continuing closeout",
2031
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
2032
+ tone: "info",
2033
+ status: "validating",
2034
+ createdAt: new Date().toISOString()
2035
+ });
2036
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
2037
+ exit = { code: 0, signal: null };
2038
+ }
2039
+ }
2040
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
1571
2041
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
1572
2042
  const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
1573
2043
  cwd: context.projectRoot,
@@ -1608,7 +2078,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1608
2078
  let acceptedArtifactObservedAt = null;
1609
2079
  let acceptedArtifactPollTimer = null;
1610
2080
  let acceptedArtifactKillTimer = null;
1611
- const attemptExit = await new Promise((resolve5) => {
2081
+ const attemptExit = await new Promise((resolve7) => {
1612
2082
  let settled = false;
1613
2083
  const settle = (result) => {
1614
2084
  if (settled)
@@ -1616,7 +2086,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1616
2086
  settled = true;
1617
2087
  if (acceptedArtifactPollTimer)
1618
2088
  clearInterval(acceptedArtifactPollTimer);
1619
- resolve5(result);
2089
+ resolve7(result);
1620
2090
  };
1621
2091
  const pollAcceptedArtifacts = () => {
1622
2092
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1689,7 +2159,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1689
2159
  });
1690
2160
  for (const pendingLog of pendingLogs) {
1691
2161
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2162
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1692
2163
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2164
+ if (pendingLog.title === "Tool activity")
2165
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1693
2166
  }
1694
2167
  process.off("SIGTERM", forwardSigterm);
1695
2168
  if (attemptExit.error) {
@@ -1815,8 +2288,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1815
2288
  }
1816
2289
  if (planningClassification.planningRequired) {
1817
2290
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1818
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1819
- if (!existsSync2(expectedPlanPath)) {
2291
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2292
+ if (!existsSync4(expectedPlanPath)) {
1820
2293
  const failedAt = new Date().toISOString();
1821
2294
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1822
2295
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1836,6 +2309,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1836
2309
  throw new CliError2(failureDetail, 1);
1837
2310
  }
1838
2311
  }
2312
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2313
+ appendPiStageLog({
2314
+ projectRoot: context.projectRoot,
2315
+ runId: input.runId,
2316
+ stage: "Validate",
2317
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2318
+ status: "completed"
2319
+ });
2320
+ if (verificationAction && !reviewStarted) {
2321
+ verificationAction.complete("Completion verification checks finished.");
2322
+ }
2323
+ if (!reviewAction) {
2324
+ promoteToReviewing("Server-owned closeout is queued.");
2325
+ }
2326
+ if (reviewAction) {
2327
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2328
+ }
2329
+ const requestedAt = new Date().toISOString();
2330
+ patchAuthorityRun(context.projectRoot, input.runId, {
2331
+ status: "reviewing",
2332
+ completedAt: null,
2333
+ errorText: null,
2334
+ serverCloseout: {
2335
+ status: "pending",
2336
+ phase: "queued",
2337
+ requestedAt,
2338
+ updatedAt: requestedAt,
2339
+ runtimeWorkspace: latestRuntimeWorkspace,
2340
+ branch: latestRuntimeBranch,
2341
+ taskId: input.taskId ?? runtimeTaskId
2342
+ }
2343
+ });
2344
+ appendRunLog(context.projectRoot, input.runId, {
2345
+ id: `log:${input.runId}:server-closeout-requested`,
2346
+ title: "Server-owned closeout requested",
2347
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2348
+ tone: "info",
2349
+ status: "reviewing",
2350
+ createdAt: requestedAt,
2351
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2352
+ });
2353
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2354
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2355
+ await context.emitEvent("command.finished", {
2356
+ command: [
2357
+ "rig",
2358
+ "server",
2359
+ "task-run",
2360
+ ...input.taskId ? ["--task", input.taskId] : [],
2361
+ ...input.title ? ["--title", input.title] : []
2362
+ ],
2363
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2364
+ exitCode: 0,
2365
+ durationMs: 0,
2366
+ startedAt,
2367
+ finishedAt: requestedAt
2368
+ });
2369
+ process.exit(0);
2370
+ }
1839
2371
  const runPiPrFeedbackFix = async (message) => {
1840
2372
  appendPiStageLog({
1841
2373
  projectRoot: context.projectRoot,
@@ -1863,11 +2395,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1863
2395
  child.stdin.write(message);
1864
2396
  }
1865
2397
  child.stdin.end();
2398
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2399
+ let feedbackAssistantText = "";
2400
+ const feedbackPendingToolUses = new Map;
1866
2401
  const stdout = createLineInterface({ input: child.stdout });
1867
2402
  stdout.on("line", (line) => {
1868
2403
  const trimmed = line.trim();
1869
2404
  if (!trimmed)
1870
2405
  return;
2406
+ try {
2407
+ const record = JSON.parse(trimmed);
2408
+ const providerLogs = buildClaudeLogsFromRecord({
2409
+ runId: input.runId,
2410
+ record,
2411
+ createdAtFallback: new Date().toISOString(),
2412
+ status: "reviewing",
2413
+ pendingToolUses: feedbackPendingToolUses
2414
+ });
2415
+ for (const providerLog of providerLogs) {
2416
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2417
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2418
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2419
+ if (providerLog.title === "Tool activity")
2420
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2421
+ }
2422
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2423
+ projectRoot: context.projectRoot,
2424
+ runId: input.runId,
2425
+ messageId: feedbackAssistantMessageId,
2426
+ record,
2427
+ assistantText: feedbackAssistantText
2428
+ });
2429
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2430
+ if (hadAssistantDelta) {
2431
+ feedbackAssistantText = nextFeedbackAssistantText;
2432
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2433
+ }
2434
+ if (providerLogs.length > 0 || hadAssistantDelta)
2435
+ return;
2436
+ } catch {}
1871
2437
  appendRunLog(context.projectRoot, input.runId, {
1872
2438
  id: nextRunLogId(),
1873
2439
  title: "Pi PR feedback fix output",
@@ -1893,10 +2459,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1893
2459
  });
1894
2460
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1895
2461
  });
1896
- const exitCode = await new Promise((resolve5) => {
1897
- child.once("error", () => resolve5(1));
1898
- child.once("close", (code) => resolve5(code ?? 1));
2462
+ const exitCode = await new Promise((resolve7) => {
2463
+ child.once("error", () => resolve7(1));
2464
+ child.once("close", (code) => resolve7(code ?? 1));
1899
2465
  });
2466
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2467
+ runId: input.runId,
2468
+ status: exitCode === 0 ? "completed" : "failed",
2469
+ pendingToolUses: feedbackPendingToolUses
2470
+ })) {
2471
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2472
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2473
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2474
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2475
+ }
2476
+ if (feedbackAssistantText.trim()) {
2477
+ appendRunTimeline(context.projectRoot, input.runId, {
2478
+ id: feedbackAssistantMessageId,
2479
+ type: "assistant_message",
2480
+ text: feedbackAssistantText,
2481
+ state: "completed",
2482
+ createdAt: new Date().toISOString(),
2483
+ completedAt: new Date().toISOString()
2484
+ });
2485
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2486
+ }
1900
2487
  if (exitCode !== 0) {
1901
2488
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1902
2489
  }