@h-rig/cli 0.0.6-alpha.4 → 0.0.6-alpha.41

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 (48) hide show
  1. package/dist/bin/rig.js +3905 -1193
  2. package/dist/src/commands/_cli-format.js +369 -0
  3. package/dist/src/commands/_connection-state.js +1 -3
  4. package/dist/src/commands/_doctor-checks.js +13 -27
  5. package/dist/src/commands/_help-catalog.js +445 -0
  6. package/dist/src/commands/_operator-surface.js +220 -0
  7. package/dist/src/commands/_operator-view.js +916 -56
  8. package/dist/src/commands/_parsers.js +0 -2
  9. package/dist/src/commands/_pi-frontend.js +880 -0
  10. package/dist/src/commands/_pi-install.js +4 -3
  11. package/dist/src/commands/_pi-remote-session.js +665 -0
  12. package/dist/src/commands/_pi-worker-bridge-extension.js +676 -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 +2 -2
  16. package/dist/src/commands/_server-client.js +152 -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 +8 -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 +237 -23
  28. package/dist/src/commands/inspector.js +2 -4
  29. package/dist/src/commands/pi.js +168 -0
  30. package/dist/src/commands/plugin.js +81 -22
  31. package/dist/src/commands/profile-and-review.js +8 -10
  32. package/dist/src/commands/queue.js +2 -3
  33. package/dist/src/commands/remote.js +18 -20
  34. package/dist/src/commands/repo-git-harness.js +6 -8
  35. package/dist/src/commands/run.js +1214 -123
  36. package/dist/src/commands/server.js +222 -33
  37. package/dist/src/commands/setup.js +17 -37
  38. package/dist/src/commands/task-report-bug.js +5 -7
  39. package/dist/src/commands/task-run-driver.js +630 -71
  40. package/dist/src/commands/task.js +1653 -252
  41. package/dist/src/commands/test.js +3 -5
  42. package/dist/src/commands/workspace.js +4 -6
  43. package/dist/src/commands.js +3884 -1166
  44. package/dist/src/index.js +3898 -1189
  45. package/dist/src/launcher.js +5 -3
  46. package/dist/src/report-bug.js +3 -3
  47. package/dist/src/runner.js +5 -19
  48. 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,14 +37,14 @@ 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,
49
46
  commitRunChanges,
47
+ pushBranchSyncedWithOrigin,
50
48
  runPrAutomation
51
49
  } from "@rig/runtime/control-plane/native/pr-automation";
52
50
 
@@ -60,6 +58,7 @@ import {
60
58
  writeJsonFile as writeJsonFile2
61
59
  } from "@rig/runtime/control-plane/authority-files";
62
60
  import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
61
+ import { readPriorPrProgressPromptSection } from "@rig/runtime/control-plane/prompt-context";
63
62
  import { buildProviderTaskRunInstructionLines } from "@rig/runtime/control-plane/provider/runtime-instructions";
64
63
  import { loadRigTaskRunSkillBody } from "@rig/runtime/control-plane/provider/rig-task-run-skill";
65
64
 
@@ -317,6 +316,7 @@ ${acceptance}` : null,
317
316
  ${sourceContractLines.join(`
318
317
  `)}` : null,
319
318
  scopeText || renderSourceScopeValidation(sourceTask, sourceValidation) || null,
319
+ readPriorPrProgressPromptSection(input.projectRoot, input.taskId),
320
320
  initialPrompt ? `Additional operator guidance:
321
321
  ${initialPrompt}` : null,
322
322
  providerLines.length > 0 ? `Provider-specific runtime tooling:
@@ -388,6 +388,194 @@ function renderSourceScopeValidation(task, validation) {
388
388
  `);
389
389
  }
390
390
 
391
+ // packages/cli/src/commands/_server-client.ts
392
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
393
+ import { resolve as resolve5 } from "path";
394
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
395
+
396
+ // packages/cli/src/commands/_connection-state.ts
397
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
398
+ import { homedir } from "os";
399
+ import { dirname, resolve as resolve4 } from "path";
400
+ function resolveGlobalConnectionsPath(env = process.env) {
401
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
402
+ if (explicit)
403
+ return resolve4(explicit);
404
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
405
+ if (stateDir)
406
+ return resolve4(stateDir, "connections.json");
407
+ return resolve4(homedir(), ".rig", "connections.json");
408
+ }
409
+ function resolveRepoConnectionPath(projectRoot) {
410
+ return resolve4(projectRoot, ".rig", "state", "connection.json");
411
+ }
412
+ function readJsonFile(path) {
413
+ if (!existsSync2(path))
414
+ return null;
415
+ try {
416
+ return JSON.parse(readFileSync2(path, "utf8"));
417
+ } catch (error) {
418
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
419
+ }
420
+ }
421
+ function normalizeConnection(value) {
422
+ if (!value || typeof value !== "object" || Array.isArray(value))
423
+ return null;
424
+ const record = value;
425
+ if (record.kind === "local")
426
+ return { kind: "local", mode: "auto" };
427
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
428
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
429
+ return { kind: "remote", baseUrl };
430
+ }
431
+ return null;
432
+ }
433
+ function readGlobalConnections(options = {}) {
434
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
435
+ const payload = readJsonFile(path);
436
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
437
+ return { connections: {} };
438
+ }
439
+ const rawConnections = payload.connections;
440
+ const connections = {};
441
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
442
+ for (const [alias, raw] of Object.entries(rawConnections)) {
443
+ const connection = normalizeConnection(raw);
444
+ if (connection)
445
+ connections[alias] = connection;
446
+ }
447
+ }
448
+ return { connections };
449
+ }
450
+ function readRepoConnection(projectRoot) {
451
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
452
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
453
+ return null;
454
+ const record = payload;
455
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
456
+ if (!selected)
457
+ return null;
458
+ return {
459
+ selected,
460
+ project: typeof record.project === "string" ? record.project : undefined,
461
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
462
+ };
463
+ }
464
+ function resolveSelectedConnection(projectRoot, options = {}) {
465
+ const repo = readRepoConnection(projectRoot);
466
+ if (!repo)
467
+ return null;
468
+ if (repo.selected === "local")
469
+ return { alias: "local", connection: { kind: "local", mode: "auto" } };
470
+ const global = readGlobalConnections(options);
471
+ const connection = global.connections[repo.selected];
472
+ if (!connection) {
473
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
474
+ }
475
+ return { alias: repo.selected, connection };
476
+ }
477
+
478
+ // packages/cli/src/commands/_server-client.ts
479
+ var scopedGitHubBearerTokens = new Map;
480
+ function cleanToken(value) {
481
+ const trimmed = value?.trim();
482
+ return trimmed ? trimmed : null;
483
+ }
484
+ function readPrivateRemoteSessionToken(projectRoot) {
485
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
486
+ if (!existsSync3(path))
487
+ return null;
488
+ try {
489
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
490
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
491
+ } catch {
492
+ return null;
493
+ }
494
+ }
495
+ function readGitHubBearerTokenForRemote(projectRoot) {
496
+ const scopedKey = resolve5(projectRoot);
497
+ if (scopedGitHubBearerTokens.has(scopedKey))
498
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
499
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
500
+ if (privateSession)
501
+ return privateSession;
502
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
503
+ }
504
+ async function ensureServerForCli(projectRoot) {
505
+ try {
506
+ const selected = resolveSelectedConnection(projectRoot);
507
+ if (selected?.connection.kind === "remote") {
508
+ return {
509
+ baseUrl: selected.connection.baseUrl,
510
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
511
+ connectionKind: "remote"
512
+ };
513
+ }
514
+ const connection = await ensureLocalRigServerConnection(projectRoot);
515
+ return {
516
+ baseUrl: connection.baseUrl,
517
+ authToken: connection.authToken,
518
+ connectionKind: "local"
519
+ };
520
+ } catch (error) {
521
+ if (error instanceof Error) {
522
+ throw new CliError2(error.message, 1);
523
+ }
524
+ throw error;
525
+ }
526
+ }
527
+ function mergeHeaders(headers, authToken) {
528
+ const merged = new Headers(headers);
529
+ if (authToken) {
530
+ merged.set("authorization", `Bearer ${authToken}`);
531
+ }
532
+ return merged;
533
+ }
534
+ function diagnosticMessage(payload) {
535
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
536
+ return null;
537
+ const record = payload;
538
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
539
+ const messages = diagnostics.flatMap((entry) => {
540
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
541
+ return [];
542
+ const diagnostic = entry;
543
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
544
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
545
+ return message ? [`${kind}: ${message}`] : [];
546
+ });
547
+ return messages.length > 0 ? messages.join("; ") : null;
548
+ }
549
+ async function requestServerJson(context, pathname, init = {}) {
550
+ const server = await ensureServerForCli(context.projectRoot);
551
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
552
+ ...init,
553
+ headers: mergeHeaders(init.headers, server.authToken)
554
+ });
555
+ const text = await response.text();
556
+ const payload = text.trim().length > 0 ? (() => {
557
+ try {
558
+ return JSON.parse(text);
559
+ } catch {
560
+ return null;
561
+ }
562
+ })() : null;
563
+ if (!response.ok) {
564
+ const diagnostics = diagnosticMessage(payload);
565
+ const detail = diagnostics ?? (text || response.statusText);
566
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
567
+ }
568
+ return payload;
569
+ }
570
+ async function updateWorkspaceTaskViaServer(context, input) {
571
+ const payload = await requestServerJson(context, "/api/tasks/update", {
572
+ method: "POST",
573
+ headers: { "content-type": "application/json" },
574
+ body: JSON.stringify(input)
575
+ });
576
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
577
+ }
578
+
391
579
  // packages/cli/src/commands/task-run-driver.ts
392
580
  var PI_CANONICAL_RUN_STAGES = [
393
581
  "Connect",
@@ -431,6 +619,7 @@ function buildPiRigBridgeEnv(input) {
431
619
  RIG_SERVER_RUN_ID: input.runId,
432
620
  RIG_TASK_ID: input.taskId,
433
621
  RIG_RUNTIME_ADAPTER: "pi",
622
+ RIG_STEERING_POLL_MS: "0",
434
623
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
435
624
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
436
625
  ...githubBridgeEnv(githubToken)
@@ -455,12 +644,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
455
644
  return 0;
456
645
  let copied = 0;
457
646
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
458
- const sourcePath = resolve4(sourceRoot, relativePath);
459
- const targetPath = resolve4(targetRoot, relativePath);
647
+ const sourcePath = resolve6(sourceRoot, relativePath);
648
+ const targetPath = resolve6(targetRoot, relativePath);
460
649
  try {
461
650
  if (!statSync(sourcePath).isFile())
462
651
  continue;
463
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
652
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
464
653
  copyFileSync(sourcePath, targetPath);
465
654
  copied += 1;
466
655
  } catch {}
@@ -499,7 +688,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
499
688
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
500
689
  return {
501
690
  RIG_BASELINE_MODE: "dirty-snapshot",
502
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
691
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
503
692
  };
504
693
  }
505
694
  function positiveInt(value, fallback) {
@@ -507,7 +696,7 @@ function positiveInt(value, fallback) {
507
696
  }
508
697
  function resolveTaskRunAutomationLimits(config, env = process.env) {
509
698
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
510
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
699
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
511
700
  return {
512
701
  maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
513
702
  maxPrFixIterations: configuredPrFixIterations
@@ -601,12 +790,6 @@ function appendPiStageLog(input) {
601
790
  });
602
791
  emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
603
792
  }
604
- async function runCheckedCommand(command, args, cwd, label = "git") {
605
- const result = await command(args, { cwd });
606
- if (result.exitCode !== 0) {
607
- throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
608
- }
609
- }
610
793
  function createCommandRunner(binary) {
611
794
  return async (args, options) => {
612
795
  const child = spawn(binary, [...args], {
@@ -617,9 +800,9 @@ function createCommandRunner(binary) {
617
800
  const stderrChunks = [];
618
801
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
619
802
  child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
620
- return await new Promise((resolve5) => {
621
- child.once("error", (error) => resolve5({ exitCode: 1, stderr: error.message }));
622
- child.once("close", (code) => resolve5({
803
+ return await new Promise((resolve7) => {
804
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
805
+ child.once("close", (code) => resolve7({
623
806
  exitCode: code ?? 1,
624
807
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
625
808
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -643,8 +826,9 @@ async function runTaskRunPostValidationLifecycle(input) {
643
826
  if (!taskId) {
644
827
  return { status: "skipped" };
645
828
  }
646
- const config = input.config ?? {};
647
- const prMode = config.pr?.mode ?? "auto";
829
+ const configInput = input.config ?? null;
830
+ const config = configInput ?? {};
831
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
648
832
  if (prMode === "off" || prMode === "ask") {
649
833
  input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
650
834
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -660,7 +844,7 @@ async function runTaskRunPostValidationLifecycle(input) {
660
844
  gitCommand
661
845
  });
662
846
  const prAutomation = input.prAutomation ?? runPrAutomation;
663
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
847
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
664
848
  const stage = input.appendStage ?? (() => {
665
849
  return;
666
850
  });
@@ -682,7 +866,7 @@ async function runTaskRunPostValidationLifecycle(input) {
682
866
  command: gitCommand
683
867
  });
684
868
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
685
- await runCheckedCommand(gitCommand, ["push", "--set-upstream", "origin", branch], workspace, "git");
869
+ await pushBranchSyncedWithOrigin({ projectRoot: workspace, branch, gitCommand });
686
870
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
687
871
  const pr = await prAutomation({
688
872
  projectRoot: workspace,
@@ -692,7 +876,9 @@ async function runTaskRunPostValidationLifecycle(input) {
692
876
  config,
693
877
  sourceTask: input.sourceTask,
694
878
  uploadedSnapshot: input.uploadedSnapshot,
879
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
695
880
  command: ghCommand,
881
+ gitCommand,
696
882
  steerPi,
697
883
  lifecycle: {
698
884
  onPrOpened: async ({ prUrl }) => {
@@ -741,8 +927,6 @@ async function runTaskRunPostValidationLifecycle(input) {
741
927
  runtimeWorkspace: input.runtimeWorkspace ?? null
742
928
  })
743
929
  }
744
- }).catch(() => {
745
- return;
746
930
  });
747
931
  }
748
932
  },
@@ -761,8 +945,6 @@ async function runTaskRunPostValidationLifecycle(input) {
761
945
  runtimeWorkspace: input.runtimeWorkspace ?? null
762
946
  })
763
947
  }
764
- }).catch(() => {
765
- return;
766
948
  });
767
949
  }
768
950
  },
@@ -791,8 +973,17 @@ async function runTaskRunPostValidationLifecycle(input) {
791
973
  errorText: detail
792
974
  })
793
975
  }
794
- }).catch(() => {
795
- return;
976
+ }).catch((error) => {
977
+ try {
978
+ appendRunLog(input.projectRoot, input.runId, {
979
+ id: `log:${input.runId}:task-source-needs-attention-update`,
980
+ title: "Task source needs-attention update failed",
981
+ detail: error instanceof Error ? error.message : String(error),
982
+ tone: "error",
983
+ status: "needs_attention",
984
+ createdAt: new Date().toISOString()
985
+ });
986
+ } catch {}
796
987
  });
797
988
  }
798
989
  return { status: "needs_attention", pr };
@@ -815,7 +1006,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
815
1006
  return null;
816
1007
  }
817
1008
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
818
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1009
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
819
1010
  if (!summary || summary.status !== "fail") {
820
1011
  continue;
821
1012
  }
@@ -896,14 +1087,14 @@ function readTaskRunAcceptedArtifactState(input) {
896
1087
  if (!input.taskId || !input.workspaceDir) {
897
1088
  return { accepted: false, reason: null };
898
1089
  }
899
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
900
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
901
- const taskResultPath = resolve4(artifactDir, "task-result.json");
1090
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1091
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1092
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
902
1093
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
903
1094
  if (reviewStatus !== "APPROVED") {
904
1095
  return { accepted: false, reason: null };
905
1096
  }
906
- const taskResult = readJsonFile(taskResultPath, null);
1097
+ const taskResult = readJsonFile2(taskResultPath, null);
907
1098
  if (taskResult?.status !== "completed") {
908
1099
  return { accepted: false, reason: null };
909
1100
  }
@@ -935,13 +1126,13 @@ function resolveTaskRunRetryContext(input) {
935
1126
  if (!input.taskId || !input.workspaceDir) {
936
1127
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
937
1128
  }
938
- const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
939
- const reviewStatePath = resolve4(artifactDir, "review-state.json");
940
- const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
941
- const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
942
- const failedApproachesPath = resolve4(input.workspaceDir, ".rig", "state", "failed_approaches.md");
943
- const validationSummaryPath = resolve4(artifactDir, "validation-summary.json");
944
- const reviewState = readJsonFile(reviewStatePath, null);
1129
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1130
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1131
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1132
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1133
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1134
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1135
+ const reviewState = readJsonFile2(reviewStatePath, null);
945
1136
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
946
1137
  const reviewRejected = isTaskRunReviewRejected(reviewState);
947
1138
  if (reviewStatus === "APPROVED") {
@@ -994,12 +1185,143 @@ function summarizeTaskRunReviewFailure(reviewState) {
994
1185
  }
995
1186
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
996
1187
  }
1188
+ function appendAssistantTimelineFromRecord(input) {
1189
+ let nextAssistantText = input.assistantText;
1190
+ if (input.record.type === "message_update") {
1191
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1192
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1193
+ nextAssistantText += assistantMessageEvent.delta;
1194
+ }
1195
+ } else if (input.record.type === "stream_event") {
1196
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1197
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1198
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1199
+ nextAssistantText += delta.text;
1200
+ }
1201
+ } else if (input.record.type === "assistant") {
1202
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1203
+ const content = Array.isArray(message.content) ? message.content : [];
1204
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1205
+ if (fullText.length > nextAssistantText.length)
1206
+ nextAssistantText = fullText;
1207
+ }
1208
+ if (nextAssistantText !== input.assistantText) {
1209
+ appendRunTimeline(input.projectRoot, input.runId, {
1210
+ id: input.messageId,
1211
+ type: "assistant_message",
1212
+ text: nextAssistantText,
1213
+ state: "streaming",
1214
+ createdAt: new Date().toISOString()
1215
+ });
1216
+ }
1217
+ return nextAssistantText;
1218
+ }
1219
+ function appendPiRpcProtocolLogFromRecord(input) {
1220
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1221
+ if (type === "response") {
1222
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
1223
+ const success = input.record.success !== false;
1224
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
1225
+ return true;
1226
+ }
1227
+ appendRunLog(input.projectRoot, input.runId, {
1228
+ id: input.nextRunLogId(),
1229
+ title: success ? "Pi RPC response" : "Pi RPC error",
1230
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
1231
+ tone: success ? "tool" : "error",
1232
+ status: input.status,
1233
+ payload: input.record,
1234
+ createdAt: new Date().toISOString()
1235
+ });
1236
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
1237
+ return true;
1238
+ }
1239
+ if (type !== "extension_ui_request")
1240
+ return false;
1241
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
1242
+ let title = "Pi UI event";
1243
+ let detail = method;
1244
+ let tone = "info";
1245
+ if (method === "notify") {
1246
+ title = "Pi notification";
1247
+ detail = String(input.record.message ?? "");
1248
+ tone = input.record.notifyType === "error" ? "error" : "info";
1249
+ } else if (method === "setStatus") {
1250
+ title = "Pi UI status";
1251
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
1252
+ tone = "tool";
1253
+ } else if (method === "setWidget") {
1254
+ title = "Pi UI widget";
1255
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
1256
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
1257
+ tone = "tool";
1258
+ } else if (method === "setTitle") {
1259
+ title = "Pi UI title";
1260
+ detail = String(input.record.title ?? "");
1261
+ tone = "tool";
1262
+ } else if (method === "set_editor_text") {
1263
+ title = "Pi editor update";
1264
+ detail = String(input.record.text ?? "").slice(0, 500);
1265
+ tone = "tool";
1266
+ } else {
1267
+ title = "Pi UI request";
1268
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
1269
+ }
1270
+ appendRunLog(input.projectRoot, input.runId, {
1271
+ id: input.nextRunLogId(),
1272
+ title,
1273
+ detail,
1274
+ tone,
1275
+ status: input.status,
1276
+ payload: input.record,
1277
+ createdAt: new Date().toISOString()
1278
+ });
1279
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1280
+ return true;
1281
+ }
1282
+ function appendPiToolTimelineFromRecord(input) {
1283
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1284
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1285
+ return false;
1286
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1287
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1288
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1289
+ appendRunTimeline(input.projectRoot, input.runId, {
1290
+ id: `tool:${toolCallId}:${type}`,
1291
+ type,
1292
+ toolName,
1293
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1294
+ createdAt: new Date().toISOString()
1295
+ });
1296
+ return true;
1297
+ }
1298
+ function isNonRenderablePiProtocolRecord(record) {
1299
+ const type = typeof record.type === "string" ? record.type : "";
1300
+ 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");
1301
+ }
1302
+ function appendToolTimelineFromLog(input) {
1303
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1304
+ if (title !== "Tool activity")
1305
+ return;
1306
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1307
+ 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;
1308
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1309
+ appendRunTimeline(input.projectRoot, input.runId, {
1310
+ id: `tool:${logId}`,
1311
+ type: "tool_execution_update",
1312
+ toolName,
1313
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1314
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1315
+ payload,
1316
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1317
+ });
1318
+ }
997
1319
  function readTaskRunReviewStatus(reviewStatusPath) {
998
- if (!existsSync2(reviewStatusPath)) {
1320
+ if (!existsSync4(reviewStatusPath)) {
999
1321
  return null;
1000
1322
  }
1001
1323
  try {
1002
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1324
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1003
1325
  return status === "APPROVED" || status === "REJECTED" ? status : null;
1004
1326
  } catch {
1005
1327
  return null;
@@ -1017,7 +1339,38 @@ function isTaskRunReviewRejected(reviewState) {
1017
1339
  function runSourceTaskIdentity(sourceTask) {
1018
1340
  return sourceTask;
1019
1341
  }
1020
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1342
+ function sourceTaskIssueNodeId(sourceTask) {
1343
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1344
+ return null;
1345
+ const record = sourceTask;
1346
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1347
+ if (direct?.trim())
1348
+ return direct.trim();
1349
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1350
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1351
+ }
1352
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1353
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1354
+ id: input.taskId,
1355
+ ...input.update.status ? { status: input.update.status } : {},
1356
+ ...input.update.comment ? { comment: input.update.comment } : {},
1357
+ ...input.update.title ? { title: input.update.title } : {},
1358
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1359
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1360
+ });
1361
+ if (serverResult.ok === false) {
1362
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1363
+ }
1364
+ return {
1365
+ updated: serverResult.ok !== false,
1366
+ taskId: input.taskId,
1367
+ status: input.update.status,
1368
+ source: "server",
1369
+ sourceKind: "server",
1370
+ projectSync: serverResult.projectSync
1371
+ };
1372
+ };
1373
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1021
1374
  if (!taskId)
1022
1375
  return;
1023
1376
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1084,6 +1437,7 @@ async function executeRigOwnedTaskRun(context, input) {
1084
1437
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1085
1438
  const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1086
1439
  const resumeMode = process.env.RIG_RUN_RESUME === "1";
1440
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1087
1441
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1088
1442
  let prompt = buildRunPrompt({
1089
1443
  projectRoot: context.projectRoot,
@@ -1106,11 +1460,7 @@ async function executeRigOwnedTaskRun(context, input) {
1106
1460
  ...input.model ? ["--model", input.model] : [],
1107
1461
  "--prompt"
1108
1462
  ] : input.runtimeAdapter === "pi" ? [
1109
- "--print",
1110
- "--verbose",
1111
- "--mode",
1112
- "json",
1113
- "--no-session",
1463
+ "__rig_pi_session_daemon__",
1114
1464
  ...input.model ? ["--model", input.model] : []
1115
1465
  ] : [
1116
1466
  "--print",
@@ -1185,15 +1535,15 @@ async function executeRigOwnedTaskRun(context, input) {
1185
1535
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1186
1536
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1187
1537
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1188
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1538
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1189
1539
  const persistedPlanning = {
1190
1540
  ...planningClassification,
1191
1541
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1192
1542
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1193
1543
  classifiedAt: new Date().toISOString()
1194
1544
  };
1195
- mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1196
- writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1545
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1546
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1197
1547
  `, "utf8");
1198
1548
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1199
1549
  prompt = `${prompt}
@@ -1207,7 +1557,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1207
1557
  projectRoot: context.projectRoot,
1208
1558
  runId: input.runId,
1209
1559
  stage,
1210
- 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,
1560
+ 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,
1211
1561
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
1212
1562
  });
1213
1563
  }
@@ -1243,11 +1593,12 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1243
1593
  let verificationStarted = false;
1244
1594
  let reviewStarted = false;
1245
1595
  let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1246
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1596
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1247
1597
  let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1248
1598
  let latestProviderCommand = null;
1249
1599
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1250
1600
  let snapshotSidecarPromise = null;
1601
+ let wrapperManagesRuntimeSnapshot = false;
1251
1602
  let dirtyBaselineApplied = false;
1252
1603
  const childEnv = {
1253
1604
  ...process.env,
@@ -1300,6 +1651,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1300
1651
  detail: detail ?? "Verifier review is running."
1301
1652
  });
1302
1653
  };
1654
+ const nextRunLogId = createRunLogIdFactory(input.runId);
1303
1655
  const handleWrapperEvent = (rawPayload) => {
1304
1656
  let event = null;
1305
1657
  try {
@@ -1315,6 +1667,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1315
1667
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
1316
1668
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
1317
1669
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
1670
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
1318
1671
  latestRuntimeBranch = runtimeId;
1319
1672
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
1320
1673
  runtimeId: runtimeId ?? null,
@@ -1326,10 +1679,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1326
1679
  patchAuthorityRun(context.projectRoot, input.runId, {
1327
1680
  status: "running",
1328
1681
  worktreePath: latestRuntimeWorkspace,
1329
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1682
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1330
1683
  logRoot: latestLogsDir,
1331
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1332
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1684
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1685
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1333
1686
  branch: runtimeId
1334
1687
  });
1335
1688
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1337,8 +1690,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1337
1690
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1338
1691
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1339
1692
  if (readyFile) {
1340
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1341
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1693
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1694
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1342
1695
  `, "utf8");
1343
1696
  }
1344
1697
  appendRunLog(context.projectRoot, input.runId, {
@@ -1352,7 +1705,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1352
1705
  });
1353
1706
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1354
1707
  }
1355
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1708
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1356
1709
  snapshotSidecarPromise = (async () => {
1357
1710
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
1358
1711
  projectRoot: context.projectRoot,
@@ -1434,9 +1787,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1434
1787
  }
1435
1788
  return true;
1436
1789
  }
1790
+ 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") {
1791
+ 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";
1792
+ 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");
1793
+ appendRunLog(context.projectRoot, input.runId, {
1794
+ id: nextRunLogId(),
1795
+ title,
1796
+ detail,
1797
+ tone: event.type === "pi.session.error" ? "error" : "info",
1798
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1799
+ payload: {
1800
+ eventType: event.type,
1801
+ runId: typeof payload.runId === "string" ? payload.runId : null,
1802
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
1803
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
1804
+ },
1805
+ createdAt: new Date().toISOString()
1806
+ });
1807
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1808
+ return true;
1809
+ }
1810
+ if (event.type === "pi.session.ready") {
1811
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
1812
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
1813
+ if (privateMetadata) {
1814
+ patchAuthorityRun(context.projectRoot, input.runId, {
1815
+ piSession: publicMetadata,
1816
+ piSessionPrivate: privateMetadata
1817
+ });
1818
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
1819
+ appendRunLog(context.projectRoot, input.runId, {
1820
+ id: `log:${input.runId}:pi-session-ready`,
1821
+ title: "Worker Pi session ready",
1822
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
1823
+ tone: "info",
1824
+ status: "running",
1825
+ payload: {
1826
+ sessionId: sessionId || null,
1827
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
1828
+ },
1829
+ createdAt: new Date().toISOString()
1830
+ });
1831
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
1832
+ }
1833
+ return true;
1834
+ }
1835
+ 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") {
1836
+ 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";
1837
+ 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")}`;
1838
+ appendRunLog(context.projectRoot, input.runId, {
1839
+ id: nextRunLogId(),
1840
+ title,
1841
+ detail,
1842
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
1843
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1844
+ payload,
1845
+ createdAt: new Date().toISOString()
1846
+ });
1847
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1848
+ return true;
1849
+ }
1437
1850
  return false;
1438
1851
  };
1439
- const nextRunLogId = createRunLogIdFactory(input.runId);
1440
1852
  const handleAgentStdoutLine = (line) => {
1441
1853
  const trimmed = line.trim();
1442
1854
  if (!trimmed)
@@ -1470,6 +1882,19 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1470
1882
  try {
1471
1883
  const record = JSON.parse(trimmed);
1472
1884
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
1885
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
1886
+ projectRoot: context.projectRoot,
1887
+ runId: input.runId,
1888
+ record,
1889
+ status: liveLogStatus,
1890
+ nextRunLogId
1891
+ })) {
1892
+ return;
1893
+ }
1894
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
1895
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1896
+ return;
1897
+ }
1473
1898
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
1474
1899
  runId: input.runId,
1475
1900
  record,
@@ -1486,7 +1911,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1486
1911
  if (providerLogs.length > 0) {
1487
1912
  for (const providerLog of providerLogs) {
1488
1913
  appendRunLog(context.projectRoot, input.runId, providerLog);
1914
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1489
1915
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1916
+ if (providerLog.title === "Tool activity")
1917
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1490
1918
  }
1491
1919
  }
1492
1920
  if (input.runtimeAdapter === "codex") {
@@ -1538,6 +1966,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1538
1966
  return;
1539
1967
  }
1540
1968
  }
1969
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
1970
+ return;
1971
+ }
1541
1972
  if (record.type === "assistant") {
1542
1973
  const message = record.message && typeof record.message === "object" ? record.message : record;
1543
1974
  const content = Array.isArray(message.content) ? message.content : [];
@@ -1574,7 +2005,18 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1574
2005
  let reviewFailureDetail = null;
1575
2006
  const stderrLines = [];
1576
2007
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1577
- if (resumeMode && latestRuntimeWorkspace) {
2008
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
2009
+ appendRunLog(context.projectRoot, input.runId, {
2010
+ id: `log:${input.runId}:resume-closeout-phase`,
2011
+ title: "Resume continuing from closeout phase",
2012
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
2013
+ tone: "info",
2014
+ status: resumePreviousStatus,
2015
+ createdAt: new Date().toISOString()
2016
+ });
2017
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
2018
+ exit = { code: 0, signal: null };
2019
+ } else if (resumeMode && latestRuntimeWorkspace) {
1578
2020
  const acceptedArtifactState = readTaskRunAcceptedArtifactState({
1579
2021
  taskId: input.taskId ?? runtimeTaskId,
1580
2022
  workspaceDir: latestRuntimeWorkspace
@@ -1633,7 +2075,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1633
2075
  let acceptedArtifactObservedAt = null;
1634
2076
  let acceptedArtifactPollTimer = null;
1635
2077
  let acceptedArtifactKillTimer = null;
1636
- const attemptExit = await new Promise((resolve5) => {
2078
+ const attemptExit = await new Promise((resolve7) => {
1637
2079
  let settled = false;
1638
2080
  const settle = (result) => {
1639
2081
  if (settled)
@@ -1641,7 +2083,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1641
2083
  settled = true;
1642
2084
  if (acceptedArtifactPollTimer)
1643
2085
  clearInterval(acceptedArtifactPollTimer);
1644
- resolve5(result);
2086
+ resolve7(result);
1645
2087
  };
1646
2088
  const pollAcceptedArtifacts = () => {
1647
2089
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1714,7 +2156,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1714
2156
  });
1715
2157
  for (const pendingLog of pendingLogs) {
1716
2158
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2159
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1717
2160
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2161
+ if (pendingLog.title === "Tool activity")
2162
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1718
2163
  }
1719
2164
  process.off("SIGTERM", forwardSigterm);
1720
2165
  if (attemptExit.error) {
@@ -1840,8 +2285,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1840
2285
  }
1841
2286
  if (planningClassification.planningRequired) {
1842
2287
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1843
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1844
- if (!existsSync2(expectedPlanPath)) {
2288
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2289
+ if (!existsSync4(expectedPlanPath)) {
1845
2290
  const failedAt = new Date().toISOString();
1846
2291
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1847
2292
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1861,6 +2306,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1861
2306
  throw new CliError2(failureDetail, 1);
1862
2307
  }
1863
2308
  }
2309
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2310
+ appendPiStageLog({
2311
+ projectRoot: context.projectRoot,
2312
+ runId: input.runId,
2313
+ stage: "Validate",
2314
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2315
+ status: "completed"
2316
+ });
2317
+ if (verificationAction && !reviewStarted) {
2318
+ verificationAction.complete("Completion verification checks finished.");
2319
+ }
2320
+ if (!reviewAction) {
2321
+ promoteToReviewing("Server-owned closeout is queued.");
2322
+ }
2323
+ if (reviewAction) {
2324
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2325
+ }
2326
+ const requestedAt = new Date().toISOString();
2327
+ patchAuthorityRun(context.projectRoot, input.runId, {
2328
+ status: "reviewing",
2329
+ completedAt: null,
2330
+ errorText: null,
2331
+ serverCloseout: {
2332
+ status: "pending",
2333
+ phase: "queued",
2334
+ requestedAt,
2335
+ updatedAt: requestedAt,
2336
+ runtimeWorkspace: latestRuntimeWorkspace,
2337
+ branch: latestRuntimeBranch,
2338
+ taskId: input.taskId ?? runtimeTaskId
2339
+ }
2340
+ });
2341
+ appendRunLog(context.projectRoot, input.runId, {
2342
+ id: `log:${input.runId}:server-closeout-requested`,
2343
+ title: "Server-owned closeout requested",
2344
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2345
+ tone: "info",
2346
+ status: "reviewing",
2347
+ createdAt: requestedAt,
2348
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2349
+ });
2350
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2351
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2352
+ await context.emitEvent("command.finished", {
2353
+ command: [
2354
+ "rig",
2355
+ "server",
2356
+ "task-run",
2357
+ ...input.taskId ? ["--task", input.taskId] : [],
2358
+ ...input.title ? ["--title", input.title] : []
2359
+ ],
2360
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2361
+ exitCode: 0,
2362
+ durationMs: 0,
2363
+ startedAt,
2364
+ finishedAt: requestedAt
2365
+ });
2366
+ process.exit(0);
2367
+ }
1864
2368
  const runPiPrFeedbackFix = async (message) => {
1865
2369
  appendPiStageLog({
1866
2370
  projectRoot: context.projectRoot,
@@ -1888,11 +2392,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1888
2392
  child.stdin.write(message);
1889
2393
  }
1890
2394
  child.stdin.end();
2395
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2396
+ let feedbackAssistantText = "";
2397
+ const feedbackPendingToolUses = new Map;
1891
2398
  const stdout = createLineInterface({ input: child.stdout });
1892
2399
  stdout.on("line", (line) => {
1893
2400
  const trimmed = line.trim();
1894
2401
  if (!trimmed)
1895
2402
  return;
2403
+ try {
2404
+ const record = JSON.parse(trimmed);
2405
+ const providerLogs = buildClaudeLogsFromRecord({
2406
+ runId: input.runId,
2407
+ record,
2408
+ createdAtFallback: new Date().toISOString(),
2409
+ status: "reviewing",
2410
+ pendingToolUses: feedbackPendingToolUses
2411
+ });
2412
+ for (const providerLog of providerLogs) {
2413
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2414
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2415
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2416
+ if (providerLog.title === "Tool activity")
2417
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2418
+ }
2419
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2420
+ projectRoot: context.projectRoot,
2421
+ runId: input.runId,
2422
+ messageId: feedbackAssistantMessageId,
2423
+ record,
2424
+ assistantText: feedbackAssistantText
2425
+ });
2426
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2427
+ if (hadAssistantDelta) {
2428
+ feedbackAssistantText = nextFeedbackAssistantText;
2429
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2430
+ }
2431
+ if (providerLogs.length > 0 || hadAssistantDelta)
2432
+ return;
2433
+ } catch {}
1896
2434
  appendRunLog(context.projectRoot, input.runId, {
1897
2435
  id: nextRunLogId(),
1898
2436
  title: "Pi PR feedback fix output",
@@ -1918,10 +2456,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1918
2456
  });
1919
2457
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1920
2458
  });
1921
- const exitCode = await new Promise((resolve5) => {
1922
- child.once("error", () => resolve5(1));
1923
- child.once("close", (code) => resolve5(code ?? 1));
2459
+ const exitCode = await new Promise((resolve7) => {
2460
+ child.once("error", () => resolve7(1));
2461
+ child.once("close", (code) => resolve7(code ?? 1));
1924
2462
  });
2463
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2464
+ runId: input.runId,
2465
+ status: exitCode === 0 ? "completed" : "failed",
2466
+ pendingToolUses: feedbackPendingToolUses
2467
+ })) {
2468
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2469
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2470
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2471
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2472
+ }
2473
+ if (feedbackAssistantText.trim()) {
2474
+ appendRunTimeline(context.projectRoot, input.runId, {
2475
+ id: feedbackAssistantMessageId,
2476
+ type: "assistant_message",
2477
+ text: feedbackAssistantText,
2478
+ state: "completed",
2479
+ createdAt: new Date().toISOString(),
2480
+ completedAt: new Date().toISOString()
2481
+ });
2482
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2483
+ }
1925
2484
  if (exitCode !== 0) {
1926
2485
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1927
2486
  }