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

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 +3947 -1190
  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 +942 -56
  8. package/dist/src/commands/_parsers.js +0 -2
  9. package/dist/src/commands/_pi-frontend.js +906 -0
  10. package/dist/src/commands/_pi-install.js +4 -3
  11. package/dist/src/commands/_pi-worker-bridge-extension.js +826 -0
  12. package/dist/src/commands/_policy.js +0 -2
  13. package/dist/src/commands/_preflight.js +32 -109
  14. package/dist/src/commands/_run-driver-helpers.js +21 -2
  15. package/dist/src/commands/_server-client.js +152 -31
  16. package/dist/src/commands/_snapshot-upload.js +8 -23
  17. package/dist/src/commands/_task-picker.js +44 -16
  18. package/dist/src/commands/agent.js +8 -9
  19. package/dist/src/commands/browser.js +4 -6
  20. package/dist/src/commands/connect.js +132 -25
  21. package/dist/src/commands/dist.js +4 -6
  22. package/dist/src/commands/doctor.js +13 -27
  23. package/dist/src/commands/github.js +10 -25
  24. package/dist/src/commands/inbox.js +351 -31
  25. package/dist/src/commands/init.js +298 -71
  26. package/dist/src/commands/inspect.js +237 -23
  27. package/dist/src/commands/inspector.js +2 -4
  28. package/dist/src/commands/pi.js +168 -0
  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 +1240 -123
  35. package/dist/src/commands/server.js +222 -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 +649 -71
  39. package/dist/src/commands/task.js +1679 -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 +3927 -1164
  43. package/dist/src/index.js +3940 -1186
  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,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
 
@@ -317,6 +315,7 @@ ${acceptance}` : null,
317
315
  ${sourceContractLines.join(`
318
316
  `)}` : null,
319
317
  scopeText || renderSourceScopeValidation(sourceTask, sourceValidation) || null,
318
+ readPriorPrProgressForPrompt(input.projectRoot, input.taskId),
320
319
  initialPrompt ? `Additional operator guidance:
321
320
  ${initialPrompt}` : null,
322
321
  providerLines.length > 0 ? `Provider-specific runtime tooling:
@@ -326,6 +325,26 @@ ${providerLines.join(" ")}` : null,
326
325
 
327
326
  `);
328
327
  }
328
+ function readPriorPrProgressForPrompt(projectRoot, taskId) {
329
+ for (const candidate of [
330
+ resolve3(projectRoot, ".worktrees", taskId, "artifacts", taskId, "pr-state.json"),
331
+ resolve3(projectRoot, "artifacts", taskId, "pr-state.json")
332
+ ]) {
333
+ try {
334
+ const raw = JSON.parse(readFileSync(candidate, "utf8"));
335
+ const entries = Array.isArray(raw) ? raw : [raw];
336
+ const first = entries.find((entry) => entry && typeof entry === "object" && typeof entry.url === "string");
337
+ if (!first)
338
+ continue;
339
+ return [
340
+ `Prior progress exists for this task: PR ${first.url}${first.branch ? ` (branch ${first.branch})` : ""} was opened by an earlier run.`,
341
+ "Check its current state first (diff, checks, review). If the work is already complete and checks are green,",
342
+ "run `rig-agent completion-verification` to merge and close instead of re-implementing anything."
343
+ ].join(" ");
344
+ } catch {}
345
+ }
346
+ return null;
347
+ }
329
348
  function firstPromptString(...values) {
330
349
  for (const value of values) {
331
350
  const trimmed = typeof value === "string" ? value.trim() : "";
@@ -388,6 +407,194 @@ function renderSourceScopeValidation(task, validation) {
388
407
  `);
389
408
  }
390
409
 
410
+ // packages/cli/src/commands/_server-client.ts
411
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
412
+ import { resolve as resolve5 } from "path";
413
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
414
+
415
+ // packages/cli/src/commands/_connection-state.ts
416
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
417
+ import { homedir } from "os";
418
+ import { dirname, resolve as resolve4 } from "path";
419
+ function resolveGlobalConnectionsPath(env = process.env) {
420
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
421
+ if (explicit)
422
+ return resolve4(explicit);
423
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
424
+ if (stateDir)
425
+ return resolve4(stateDir, "connections.json");
426
+ return resolve4(homedir(), ".rig", "connections.json");
427
+ }
428
+ function resolveRepoConnectionPath(projectRoot) {
429
+ return resolve4(projectRoot, ".rig", "state", "connection.json");
430
+ }
431
+ function readJsonFile(path) {
432
+ if (!existsSync2(path))
433
+ return null;
434
+ try {
435
+ return JSON.parse(readFileSync2(path, "utf8"));
436
+ } catch (error) {
437
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
438
+ }
439
+ }
440
+ function normalizeConnection(value) {
441
+ if (!value || typeof value !== "object" || Array.isArray(value))
442
+ return null;
443
+ const record = value;
444
+ if (record.kind === "local")
445
+ return { kind: "local", mode: "auto" };
446
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
447
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
448
+ return { kind: "remote", baseUrl };
449
+ }
450
+ return null;
451
+ }
452
+ function readGlobalConnections(options = {}) {
453
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
454
+ const payload = readJsonFile(path);
455
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
456
+ return { connections: {} };
457
+ }
458
+ const rawConnections = payload.connections;
459
+ const connections = {};
460
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
461
+ for (const [alias, raw] of Object.entries(rawConnections)) {
462
+ const connection = normalizeConnection(raw);
463
+ if (connection)
464
+ connections[alias] = connection;
465
+ }
466
+ }
467
+ return { connections };
468
+ }
469
+ function readRepoConnection(projectRoot) {
470
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
471
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
472
+ return null;
473
+ const record = payload;
474
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
475
+ if (!selected)
476
+ return null;
477
+ return {
478
+ selected,
479
+ project: typeof record.project === "string" ? record.project : undefined,
480
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
481
+ };
482
+ }
483
+ function resolveSelectedConnection(projectRoot, options = {}) {
484
+ const repo = readRepoConnection(projectRoot);
485
+ if (!repo)
486
+ return null;
487
+ if (repo.selected === "local")
488
+ return { alias: "local", connection: { kind: "local", mode: "auto" } };
489
+ const global = readGlobalConnections(options);
490
+ const connection = global.connections[repo.selected];
491
+ if (!connection) {
492
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
493
+ }
494
+ return { alias: repo.selected, connection };
495
+ }
496
+
497
+ // packages/cli/src/commands/_server-client.ts
498
+ var scopedGitHubBearerTokens = new Map;
499
+ function cleanToken(value) {
500
+ const trimmed = value?.trim();
501
+ return trimmed ? trimmed : null;
502
+ }
503
+ function readPrivateRemoteSessionToken(projectRoot) {
504
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
505
+ if (!existsSync3(path))
506
+ return null;
507
+ try {
508
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
509
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
510
+ } catch {
511
+ return null;
512
+ }
513
+ }
514
+ function readGitHubBearerTokenForRemote(projectRoot) {
515
+ const scopedKey = resolve5(projectRoot);
516
+ if (scopedGitHubBearerTokens.has(scopedKey))
517
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
518
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
519
+ if (privateSession)
520
+ return privateSession;
521
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
522
+ }
523
+ async function ensureServerForCli(projectRoot) {
524
+ try {
525
+ const selected = resolveSelectedConnection(projectRoot);
526
+ if (selected?.connection.kind === "remote") {
527
+ return {
528
+ baseUrl: selected.connection.baseUrl,
529
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
530
+ connectionKind: "remote"
531
+ };
532
+ }
533
+ const connection = await ensureLocalRigServerConnection(projectRoot);
534
+ return {
535
+ baseUrl: connection.baseUrl,
536
+ authToken: connection.authToken,
537
+ connectionKind: "local"
538
+ };
539
+ } catch (error) {
540
+ if (error instanceof Error) {
541
+ throw new CliError2(error.message, 1);
542
+ }
543
+ throw error;
544
+ }
545
+ }
546
+ function mergeHeaders(headers, authToken) {
547
+ const merged = new Headers(headers);
548
+ if (authToken) {
549
+ merged.set("authorization", `Bearer ${authToken}`);
550
+ }
551
+ return merged;
552
+ }
553
+ function diagnosticMessage(payload) {
554
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
555
+ return null;
556
+ const record = payload;
557
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
558
+ const messages = diagnostics.flatMap((entry) => {
559
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
560
+ return [];
561
+ const diagnostic = entry;
562
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
563
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
564
+ return message ? [`${kind}: ${message}`] : [];
565
+ });
566
+ return messages.length > 0 ? messages.join("; ") : null;
567
+ }
568
+ async function requestServerJson(context, pathname, init = {}) {
569
+ const server = await ensureServerForCli(context.projectRoot);
570
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
571
+ ...init,
572
+ headers: mergeHeaders(init.headers, server.authToken)
573
+ });
574
+ const text = await response.text();
575
+ const payload = text.trim().length > 0 ? (() => {
576
+ try {
577
+ return JSON.parse(text);
578
+ } catch {
579
+ return null;
580
+ }
581
+ })() : null;
582
+ if (!response.ok) {
583
+ const diagnostics = diagnosticMessage(payload);
584
+ const detail = diagnostics ?? (text || response.statusText);
585
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
586
+ }
587
+ return payload;
588
+ }
589
+ async function updateWorkspaceTaskViaServer(context, input) {
590
+ const payload = await requestServerJson(context, "/api/tasks/update", {
591
+ method: "POST",
592
+ headers: { "content-type": "application/json" },
593
+ body: JSON.stringify(input)
594
+ });
595
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
596
+ }
597
+
391
598
  // packages/cli/src/commands/task-run-driver.ts
392
599
  var PI_CANONICAL_RUN_STAGES = [
393
600
  "Connect",
@@ -431,6 +638,7 @@ function buildPiRigBridgeEnv(input) {
431
638
  RIG_SERVER_RUN_ID: input.runId,
432
639
  RIG_TASK_ID: input.taskId,
433
640
  RIG_RUNTIME_ADAPTER: "pi",
641
+ RIG_STEERING_POLL_MS: "0",
434
642
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
435
643
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
436
644
  ...githubBridgeEnv(githubToken)
@@ -455,12 +663,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
455
663
  return 0;
456
664
  let copied = 0;
457
665
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
458
- const sourcePath = resolve4(sourceRoot, relativePath);
459
- const targetPath = resolve4(targetRoot, relativePath);
666
+ const sourcePath = resolve6(sourceRoot, relativePath);
667
+ const targetPath = resolve6(targetRoot, relativePath);
460
668
  try {
461
669
  if (!statSync(sourcePath).isFile())
462
670
  continue;
463
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
671
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
464
672
  copyFileSync(sourcePath, targetPath);
465
673
  copied += 1;
466
674
  } catch {}
@@ -499,7 +707,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
499
707
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
500
708
  return {
501
709
  RIG_BASELINE_MODE: "dirty-snapshot",
502
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
710
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
503
711
  };
504
712
  }
505
713
  function positiveInt(value, fallback) {
@@ -507,7 +715,7 @@ function positiveInt(value, fallback) {
507
715
  }
508
716
  function resolveTaskRunAutomationLimits(config, env = process.env) {
509
717
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
510
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
718
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
511
719
  return {
512
720
  maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
513
721
  maxPrFixIterations: configuredPrFixIterations
@@ -601,12 +809,6 @@ function appendPiStageLog(input) {
601
809
  });
602
810
  emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
603
811
  }
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
812
  function createCommandRunner(binary) {
611
813
  return async (args, options) => {
612
814
  const child = spawn(binary, [...args], {
@@ -617,9 +819,9 @@ function createCommandRunner(binary) {
617
819
  const stderrChunks = [];
618
820
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
619
821
  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({
822
+ return await new Promise((resolve7) => {
823
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
824
+ child.once("close", (code) => resolve7({
623
825
  exitCode: code ?? 1,
624
826
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
625
827
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -643,8 +845,9 @@ async function runTaskRunPostValidationLifecycle(input) {
643
845
  if (!taskId) {
644
846
  return { status: "skipped" };
645
847
  }
646
- const config = input.config ?? {};
647
- const prMode = config.pr?.mode ?? "auto";
848
+ const configInput = input.config ?? null;
849
+ const config = configInput ?? {};
850
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
648
851
  if (prMode === "off" || prMode === "ask") {
649
852
  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
853
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -660,7 +863,7 @@ async function runTaskRunPostValidationLifecycle(input) {
660
863
  gitCommand
661
864
  });
662
865
  const prAutomation = input.prAutomation ?? runPrAutomation;
663
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
866
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
664
867
  const stage = input.appendStage ?? (() => {
665
868
  return;
666
869
  });
@@ -682,7 +885,7 @@ async function runTaskRunPostValidationLifecycle(input) {
682
885
  command: gitCommand
683
886
  });
684
887
  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");
888
+ await pushBranchSyncedWithOrigin({ projectRoot: workspace, branch, gitCommand });
686
889
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
687
890
  const pr = await prAutomation({
688
891
  projectRoot: workspace,
@@ -692,7 +895,9 @@ async function runTaskRunPostValidationLifecycle(input) {
692
895
  config,
693
896
  sourceTask: input.sourceTask,
694
897
  uploadedSnapshot: input.uploadedSnapshot,
898
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
695
899
  command: ghCommand,
900
+ gitCommand,
696
901
  steerPi,
697
902
  lifecycle: {
698
903
  onPrOpened: async ({ prUrl }) => {
@@ -741,8 +946,6 @@ async function runTaskRunPostValidationLifecycle(input) {
741
946
  runtimeWorkspace: input.runtimeWorkspace ?? null
742
947
  })
743
948
  }
744
- }).catch(() => {
745
- return;
746
949
  });
747
950
  }
748
951
  },
@@ -761,8 +964,6 @@ async function runTaskRunPostValidationLifecycle(input) {
761
964
  runtimeWorkspace: input.runtimeWorkspace ?? null
762
965
  })
763
966
  }
764
- }).catch(() => {
765
- return;
766
967
  });
767
968
  }
768
969
  },
@@ -791,8 +992,17 @@ async function runTaskRunPostValidationLifecycle(input) {
791
992
  errorText: detail
792
993
  })
793
994
  }
794
- }).catch(() => {
795
- return;
995
+ }).catch((error) => {
996
+ try {
997
+ appendRunLog(input.projectRoot, input.runId, {
998
+ id: `log:${input.runId}:task-source-needs-attention-update`,
999
+ title: "Task source needs-attention update failed",
1000
+ detail: error instanceof Error ? error.message : String(error),
1001
+ tone: "error",
1002
+ status: "needs_attention",
1003
+ createdAt: new Date().toISOString()
1004
+ });
1005
+ } catch {}
796
1006
  });
797
1007
  }
798
1008
  return { status: "needs_attention", pr };
@@ -815,7 +1025,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
815
1025
  return null;
816
1026
  }
817
1027
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
818
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1028
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
819
1029
  if (!summary || summary.status !== "fail") {
820
1030
  continue;
821
1031
  }
@@ -896,14 +1106,14 @@ function readTaskRunAcceptedArtifactState(input) {
896
1106
  if (!input.taskId || !input.workspaceDir) {
897
1107
  return { accepted: false, reason: null };
898
1108
  }
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");
1109
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1110
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1111
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
902
1112
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
903
1113
  if (reviewStatus !== "APPROVED") {
904
1114
  return { accepted: false, reason: null };
905
1115
  }
906
- const taskResult = readJsonFile(taskResultPath, null);
1116
+ const taskResult = readJsonFile2(taskResultPath, null);
907
1117
  if (taskResult?.status !== "completed") {
908
1118
  return { accepted: false, reason: null };
909
1119
  }
@@ -935,13 +1145,13 @@ function resolveTaskRunRetryContext(input) {
935
1145
  if (!input.taskId || !input.workspaceDir) {
936
1146
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
937
1147
  }
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);
1148
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1149
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1150
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1151
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1152
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1153
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1154
+ const reviewState = readJsonFile2(reviewStatePath, null);
945
1155
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
946
1156
  const reviewRejected = isTaskRunReviewRejected(reviewState);
947
1157
  if (reviewStatus === "APPROVED") {
@@ -994,12 +1204,143 @@ function summarizeTaskRunReviewFailure(reviewState) {
994
1204
  }
995
1205
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
996
1206
  }
1207
+ function appendAssistantTimelineFromRecord(input) {
1208
+ let nextAssistantText = input.assistantText;
1209
+ if (input.record.type === "message_update") {
1210
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1211
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1212
+ nextAssistantText += assistantMessageEvent.delta;
1213
+ }
1214
+ } else if (input.record.type === "stream_event") {
1215
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1216
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1217
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1218
+ nextAssistantText += delta.text;
1219
+ }
1220
+ } else if (input.record.type === "assistant") {
1221
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1222
+ const content = Array.isArray(message.content) ? message.content : [];
1223
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1224
+ if (fullText.length > nextAssistantText.length)
1225
+ nextAssistantText = fullText;
1226
+ }
1227
+ if (nextAssistantText !== input.assistantText) {
1228
+ appendRunTimeline(input.projectRoot, input.runId, {
1229
+ id: input.messageId,
1230
+ type: "assistant_message",
1231
+ text: nextAssistantText,
1232
+ state: "streaming",
1233
+ createdAt: new Date().toISOString()
1234
+ });
1235
+ }
1236
+ return nextAssistantText;
1237
+ }
1238
+ function appendPiRpcProtocolLogFromRecord(input) {
1239
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1240
+ if (type === "response") {
1241
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
1242
+ const success = input.record.success !== false;
1243
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
1244
+ return true;
1245
+ }
1246
+ appendRunLog(input.projectRoot, input.runId, {
1247
+ id: input.nextRunLogId(),
1248
+ title: success ? "Pi RPC response" : "Pi RPC error",
1249
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
1250
+ tone: success ? "tool" : "error",
1251
+ status: input.status,
1252
+ payload: input.record,
1253
+ createdAt: new Date().toISOString()
1254
+ });
1255
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
1256
+ return true;
1257
+ }
1258
+ if (type !== "extension_ui_request")
1259
+ return false;
1260
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
1261
+ let title = "Pi UI event";
1262
+ let detail = method;
1263
+ let tone = "info";
1264
+ if (method === "notify") {
1265
+ title = "Pi notification";
1266
+ detail = String(input.record.message ?? "");
1267
+ tone = input.record.notifyType === "error" ? "error" : "info";
1268
+ } else if (method === "setStatus") {
1269
+ title = "Pi UI status";
1270
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
1271
+ tone = "tool";
1272
+ } else if (method === "setWidget") {
1273
+ title = "Pi UI widget";
1274
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
1275
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
1276
+ tone = "tool";
1277
+ } else if (method === "setTitle") {
1278
+ title = "Pi UI title";
1279
+ detail = String(input.record.title ?? "");
1280
+ tone = "tool";
1281
+ } else if (method === "set_editor_text") {
1282
+ title = "Pi editor update";
1283
+ detail = String(input.record.text ?? "").slice(0, 500);
1284
+ tone = "tool";
1285
+ } else {
1286
+ title = "Pi UI request";
1287
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
1288
+ }
1289
+ appendRunLog(input.projectRoot, input.runId, {
1290
+ id: input.nextRunLogId(),
1291
+ title,
1292
+ detail,
1293
+ tone,
1294
+ status: input.status,
1295
+ payload: input.record,
1296
+ createdAt: new Date().toISOString()
1297
+ });
1298
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1299
+ return true;
1300
+ }
1301
+ function appendPiToolTimelineFromRecord(input) {
1302
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1303
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1304
+ return false;
1305
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1306
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1307
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1308
+ appendRunTimeline(input.projectRoot, input.runId, {
1309
+ id: `tool:${toolCallId}:${type}`,
1310
+ type,
1311
+ toolName,
1312
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1313
+ createdAt: new Date().toISOString()
1314
+ });
1315
+ return true;
1316
+ }
1317
+ function isNonRenderablePiProtocolRecord(record) {
1318
+ const type = typeof record.type === "string" ? record.type : "";
1319
+ 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");
1320
+ }
1321
+ function appendToolTimelineFromLog(input) {
1322
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1323
+ if (title !== "Tool activity")
1324
+ return;
1325
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1326
+ 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;
1327
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1328
+ appendRunTimeline(input.projectRoot, input.runId, {
1329
+ id: `tool:${logId}`,
1330
+ type: "tool_execution_update",
1331
+ toolName,
1332
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1333
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1334
+ payload,
1335
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1336
+ });
1337
+ }
997
1338
  function readTaskRunReviewStatus(reviewStatusPath) {
998
- if (!existsSync2(reviewStatusPath)) {
1339
+ if (!existsSync4(reviewStatusPath)) {
999
1340
  return null;
1000
1341
  }
1001
1342
  try {
1002
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1343
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1003
1344
  return status === "APPROVED" || status === "REJECTED" ? status : null;
1004
1345
  } catch {
1005
1346
  return null;
@@ -1017,7 +1358,38 @@ function isTaskRunReviewRejected(reviewState) {
1017
1358
  function runSourceTaskIdentity(sourceTask) {
1018
1359
  return sourceTask;
1019
1360
  }
1020
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1361
+ function sourceTaskIssueNodeId(sourceTask) {
1362
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1363
+ return null;
1364
+ const record = sourceTask;
1365
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1366
+ if (direct?.trim())
1367
+ return direct.trim();
1368
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1369
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1370
+ }
1371
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1372
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1373
+ id: input.taskId,
1374
+ ...input.update.status ? { status: input.update.status } : {},
1375
+ ...input.update.comment ? { comment: input.update.comment } : {},
1376
+ ...input.update.title ? { title: input.update.title } : {},
1377
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1378
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1379
+ });
1380
+ if (serverResult.ok === false) {
1381
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1382
+ }
1383
+ return {
1384
+ updated: serverResult.ok !== false,
1385
+ taskId: input.taskId,
1386
+ status: input.update.status,
1387
+ source: "server",
1388
+ sourceKind: "server",
1389
+ projectSync: serverResult.projectSync
1390
+ };
1391
+ };
1392
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1021
1393
  if (!taskId)
1022
1394
  return;
1023
1395
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1084,6 +1456,7 @@ async function executeRigOwnedTaskRun(context, input) {
1084
1456
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1085
1457
  const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1086
1458
  const resumeMode = process.env.RIG_RUN_RESUME === "1";
1459
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1087
1460
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1088
1461
  let prompt = buildRunPrompt({
1089
1462
  projectRoot: context.projectRoot,
@@ -1106,11 +1479,7 @@ async function executeRigOwnedTaskRun(context, input) {
1106
1479
  ...input.model ? ["--model", input.model] : [],
1107
1480
  "--prompt"
1108
1481
  ] : input.runtimeAdapter === "pi" ? [
1109
- "--print",
1110
- "--verbose",
1111
- "--mode",
1112
- "json",
1113
- "--no-session",
1482
+ "__rig_pi_session_daemon__",
1114
1483
  ...input.model ? ["--model", input.model] : []
1115
1484
  ] : [
1116
1485
  "--print",
@@ -1185,15 +1554,15 @@ async function executeRigOwnedTaskRun(context, input) {
1185
1554
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1186
1555
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1187
1556
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1188
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1557
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1189
1558
  const persistedPlanning = {
1190
1559
  ...planningClassification,
1191
1560
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1192
1561
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1193
1562
  classifiedAt: new Date().toISOString()
1194
1563
  };
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)}
1564
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1565
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1197
1566
  `, "utf8");
1198
1567
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1199
1568
  prompt = `${prompt}
@@ -1207,7 +1576,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1207
1576
  projectRoot: context.projectRoot,
1208
1577
  runId: input.runId,
1209
1578
  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,
1579
+ 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
1580
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
1212
1581
  });
1213
1582
  }
@@ -1243,11 +1612,12 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1243
1612
  let verificationStarted = false;
1244
1613
  let reviewStarted = false;
1245
1614
  let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1246
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1615
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1247
1616
  let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1248
1617
  let latestProviderCommand = null;
1249
1618
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1250
1619
  let snapshotSidecarPromise = null;
1620
+ let wrapperManagesRuntimeSnapshot = false;
1251
1621
  let dirtyBaselineApplied = false;
1252
1622
  const childEnv = {
1253
1623
  ...process.env,
@@ -1300,6 +1670,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1300
1670
  detail: detail ?? "Verifier review is running."
1301
1671
  });
1302
1672
  };
1673
+ const nextRunLogId = createRunLogIdFactory(input.runId);
1303
1674
  const handleWrapperEvent = (rawPayload) => {
1304
1675
  let event = null;
1305
1676
  try {
@@ -1315,6 +1686,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1315
1686
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
1316
1687
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
1317
1688
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
1689
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
1318
1690
  latestRuntimeBranch = runtimeId;
1319
1691
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
1320
1692
  runtimeId: runtimeId ?? null,
@@ -1326,10 +1698,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1326
1698
  patchAuthorityRun(context.projectRoot, input.runId, {
1327
1699
  status: "running",
1328
1700
  worktreePath: latestRuntimeWorkspace,
1329
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1701
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1330
1702
  logRoot: latestLogsDir,
1331
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1332
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1703
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1704
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1333
1705
  branch: runtimeId
1334
1706
  });
1335
1707
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1337,8 +1709,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1337
1709
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1338
1710
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1339
1711
  if (readyFile) {
1340
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1341
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1712
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1713
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1342
1714
  `, "utf8");
1343
1715
  }
1344
1716
  appendRunLog(context.projectRoot, input.runId, {
@@ -1352,7 +1724,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1352
1724
  });
1353
1725
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1354
1726
  }
1355
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1727
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1356
1728
  snapshotSidecarPromise = (async () => {
1357
1729
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
1358
1730
  projectRoot: context.projectRoot,
@@ -1434,9 +1806,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1434
1806
  }
1435
1807
  return true;
1436
1808
  }
1809
+ 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") {
1810
+ 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";
1811
+ 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");
1812
+ appendRunLog(context.projectRoot, input.runId, {
1813
+ id: nextRunLogId(),
1814
+ title,
1815
+ detail,
1816
+ tone: event.type === "pi.session.error" ? "error" : "info",
1817
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1818
+ payload: {
1819
+ eventType: event.type,
1820
+ runId: typeof payload.runId === "string" ? payload.runId : null,
1821
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
1822
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
1823
+ },
1824
+ createdAt: new Date().toISOString()
1825
+ });
1826
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1827
+ return true;
1828
+ }
1829
+ if (event.type === "pi.session.ready") {
1830
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
1831
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
1832
+ if (privateMetadata) {
1833
+ patchAuthorityRun(context.projectRoot, input.runId, {
1834
+ piSession: publicMetadata,
1835
+ piSessionPrivate: privateMetadata
1836
+ });
1837
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
1838
+ appendRunLog(context.projectRoot, input.runId, {
1839
+ id: `log:${input.runId}:pi-session-ready`,
1840
+ title: "Worker Pi session ready",
1841
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
1842
+ tone: "info",
1843
+ status: "running",
1844
+ payload: {
1845
+ sessionId: sessionId || null,
1846
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
1847
+ },
1848
+ createdAt: new Date().toISOString()
1849
+ });
1850
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
1851
+ }
1852
+ return true;
1853
+ }
1854
+ 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") {
1855
+ 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";
1856
+ 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")}`;
1857
+ appendRunLog(context.projectRoot, input.runId, {
1858
+ id: nextRunLogId(),
1859
+ title,
1860
+ detail,
1861
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
1862
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1863
+ payload,
1864
+ createdAt: new Date().toISOString()
1865
+ });
1866
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1867
+ return true;
1868
+ }
1437
1869
  return false;
1438
1870
  };
1439
- const nextRunLogId = createRunLogIdFactory(input.runId);
1440
1871
  const handleAgentStdoutLine = (line) => {
1441
1872
  const trimmed = line.trim();
1442
1873
  if (!trimmed)
@@ -1470,6 +1901,19 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1470
1901
  try {
1471
1902
  const record = JSON.parse(trimmed);
1472
1903
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
1904
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
1905
+ projectRoot: context.projectRoot,
1906
+ runId: input.runId,
1907
+ record,
1908
+ status: liveLogStatus,
1909
+ nextRunLogId
1910
+ })) {
1911
+ return;
1912
+ }
1913
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
1914
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1915
+ return;
1916
+ }
1473
1917
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
1474
1918
  runId: input.runId,
1475
1919
  record,
@@ -1486,7 +1930,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1486
1930
  if (providerLogs.length > 0) {
1487
1931
  for (const providerLog of providerLogs) {
1488
1932
  appendRunLog(context.projectRoot, input.runId, providerLog);
1933
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1489
1934
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1935
+ if (providerLog.title === "Tool activity")
1936
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1490
1937
  }
1491
1938
  }
1492
1939
  if (input.runtimeAdapter === "codex") {
@@ -1538,6 +1985,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1538
1985
  return;
1539
1986
  }
1540
1987
  }
1988
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
1989
+ return;
1990
+ }
1541
1991
  if (record.type === "assistant") {
1542
1992
  const message = record.message && typeof record.message === "object" ? record.message : record;
1543
1993
  const content = Array.isArray(message.content) ? message.content : [];
@@ -1574,7 +2024,18 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1574
2024
  let reviewFailureDetail = null;
1575
2025
  const stderrLines = [];
1576
2026
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1577
- if (resumeMode && latestRuntimeWorkspace) {
2027
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
2028
+ appendRunLog(context.projectRoot, input.runId, {
2029
+ id: `log:${input.runId}:resume-closeout-phase`,
2030
+ title: "Resume continuing from closeout phase",
2031
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
2032
+ tone: "info",
2033
+ status: resumePreviousStatus,
2034
+ createdAt: new Date().toISOString()
2035
+ });
2036
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
2037
+ exit = { code: 0, signal: null };
2038
+ } else if (resumeMode && latestRuntimeWorkspace) {
1578
2039
  const acceptedArtifactState = readTaskRunAcceptedArtifactState({
1579
2040
  taskId: input.taskId ?? runtimeTaskId,
1580
2041
  workspaceDir: latestRuntimeWorkspace
@@ -1633,7 +2094,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1633
2094
  let acceptedArtifactObservedAt = null;
1634
2095
  let acceptedArtifactPollTimer = null;
1635
2096
  let acceptedArtifactKillTimer = null;
1636
- const attemptExit = await new Promise((resolve5) => {
2097
+ const attemptExit = await new Promise((resolve7) => {
1637
2098
  let settled = false;
1638
2099
  const settle = (result) => {
1639
2100
  if (settled)
@@ -1641,7 +2102,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1641
2102
  settled = true;
1642
2103
  if (acceptedArtifactPollTimer)
1643
2104
  clearInterval(acceptedArtifactPollTimer);
1644
- resolve5(result);
2105
+ resolve7(result);
1645
2106
  };
1646
2107
  const pollAcceptedArtifacts = () => {
1647
2108
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1714,7 +2175,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1714
2175
  });
1715
2176
  for (const pendingLog of pendingLogs) {
1716
2177
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2178
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1717
2179
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2180
+ if (pendingLog.title === "Tool activity")
2181
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1718
2182
  }
1719
2183
  process.off("SIGTERM", forwardSigterm);
1720
2184
  if (attemptExit.error) {
@@ -1840,8 +2304,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1840
2304
  }
1841
2305
  if (planningClassification.planningRequired) {
1842
2306
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1843
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1844
- if (!existsSync2(expectedPlanPath)) {
2307
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2308
+ if (!existsSync4(expectedPlanPath)) {
1845
2309
  const failedAt = new Date().toISOString();
1846
2310
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1847
2311
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1861,6 +2325,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1861
2325
  throw new CliError2(failureDetail, 1);
1862
2326
  }
1863
2327
  }
2328
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2329
+ appendPiStageLog({
2330
+ projectRoot: context.projectRoot,
2331
+ runId: input.runId,
2332
+ stage: "Validate",
2333
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2334
+ status: "completed"
2335
+ });
2336
+ if (verificationAction && !reviewStarted) {
2337
+ verificationAction.complete("Completion verification checks finished.");
2338
+ }
2339
+ if (!reviewAction) {
2340
+ promoteToReviewing("Server-owned closeout is queued.");
2341
+ }
2342
+ if (reviewAction) {
2343
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2344
+ }
2345
+ const requestedAt = new Date().toISOString();
2346
+ patchAuthorityRun(context.projectRoot, input.runId, {
2347
+ status: "reviewing",
2348
+ completedAt: null,
2349
+ errorText: null,
2350
+ serverCloseout: {
2351
+ status: "pending",
2352
+ phase: "queued",
2353
+ requestedAt,
2354
+ updatedAt: requestedAt,
2355
+ runtimeWorkspace: latestRuntimeWorkspace,
2356
+ branch: latestRuntimeBranch,
2357
+ taskId: input.taskId ?? runtimeTaskId
2358
+ }
2359
+ });
2360
+ appendRunLog(context.projectRoot, input.runId, {
2361
+ id: `log:${input.runId}:server-closeout-requested`,
2362
+ title: "Server-owned closeout requested",
2363
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2364
+ tone: "info",
2365
+ status: "reviewing",
2366
+ createdAt: requestedAt,
2367
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2368
+ });
2369
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2370
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2371
+ await context.emitEvent("command.finished", {
2372
+ command: [
2373
+ "rig",
2374
+ "server",
2375
+ "task-run",
2376
+ ...input.taskId ? ["--task", input.taskId] : [],
2377
+ ...input.title ? ["--title", input.title] : []
2378
+ ],
2379
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2380
+ exitCode: 0,
2381
+ durationMs: 0,
2382
+ startedAt,
2383
+ finishedAt: requestedAt
2384
+ });
2385
+ process.exit(0);
2386
+ }
1864
2387
  const runPiPrFeedbackFix = async (message) => {
1865
2388
  appendPiStageLog({
1866
2389
  projectRoot: context.projectRoot,
@@ -1888,11 +2411,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1888
2411
  child.stdin.write(message);
1889
2412
  }
1890
2413
  child.stdin.end();
2414
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2415
+ let feedbackAssistantText = "";
2416
+ const feedbackPendingToolUses = new Map;
1891
2417
  const stdout = createLineInterface({ input: child.stdout });
1892
2418
  stdout.on("line", (line) => {
1893
2419
  const trimmed = line.trim();
1894
2420
  if (!trimmed)
1895
2421
  return;
2422
+ try {
2423
+ const record = JSON.parse(trimmed);
2424
+ const providerLogs = buildClaudeLogsFromRecord({
2425
+ runId: input.runId,
2426
+ record,
2427
+ createdAtFallback: new Date().toISOString(),
2428
+ status: "reviewing",
2429
+ pendingToolUses: feedbackPendingToolUses
2430
+ });
2431
+ for (const providerLog of providerLogs) {
2432
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2433
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2434
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2435
+ if (providerLog.title === "Tool activity")
2436
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2437
+ }
2438
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2439
+ projectRoot: context.projectRoot,
2440
+ runId: input.runId,
2441
+ messageId: feedbackAssistantMessageId,
2442
+ record,
2443
+ assistantText: feedbackAssistantText
2444
+ });
2445
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2446
+ if (hadAssistantDelta) {
2447
+ feedbackAssistantText = nextFeedbackAssistantText;
2448
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2449
+ }
2450
+ if (providerLogs.length > 0 || hadAssistantDelta)
2451
+ return;
2452
+ } catch {}
1896
2453
  appendRunLog(context.projectRoot, input.runId, {
1897
2454
  id: nextRunLogId(),
1898
2455
  title: "Pi PR feedback fix output",
@@ -1918,10 +2475,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1918
2475
  });
1919
2476
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1920
2477
  });
1921
- const exitCode = await new Promise((resolve5) => {
1922
- child.once("error", () => resolve5(1));
1923
- child.once("close", (code) => resolve5(code ?? 1));
2478
+ const exitCode = await new Promise((resolve7) => {
2479
+ child.once("error", () => resolve7(1));
2480
+ child.once("close", (code) => resolve7(code ?? 1));
1924
2481
  });
2482
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2483
+ runId: input.runId,
2484
+ status: exitCode === 0 ? "completed" : "failed",
2485
+ pendingToolUses: feedbackPendingToolUses
2486
+ })) {
2487
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2488
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2489
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2490
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2491
+ }
2492
+ if (feedbackAssistantText.trim()) {
2493
+ appendRunTimeline(context.projectRoot, input.runId, {
2494
+ id: feedbackAssistantMessageId,
2495
+ type: "assistant_message",
2496
+ text: feedbackAssistantText,
2497
+ state: "completed",
2498
+ createdAt: new Date().toISOString(),
2499
+ completedAt: new Date().toISOString()
2500
+ });
2501
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2502
+ }
1925
2503
  if (exitCode !== 0) {
1926
2504
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1927
2505
  }