@h-rig/cli 0.0.6-alpha.5 → 0.0.6-alpha.50

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 (49) hide show
  1. package/dist/bin/rig.js +4072 -1204
  2. package/dist/src/commands/_cli-format.js +369 -0
  3. package/dist/src/commands/_connection-state.js +12 -6
  4. package/dist/src/commands/_doctor-checks.js +65 -34
  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 +1109 -63
  8. package/dist/src/commands/_parsers.js +0 -2
  9. package/dist/src/commands/_pi-frontend.js +1066 -0
  10. package/dist/src/commands/_pi-install.js +4 -3
  11. package/dist/src/commands/_pi-remote-session.js +757 -0
  12. package/dist/src/commands/_pi-worker-bridge-extension.js +820 -0
  13. package/dist/src/commands/_policy.js +0 -2
  14. package/dist/src/commands/_preflight.js +84 -116
  15. package/dist/src/commands/_run-driver-helpers.js +2 -2
  16. package/dist/src/commands/_server-client.js +211 -48
  17. package/dist/src/commands/_snapshot-upload.js +60 -30
  18. package/dist/src/commands/_spinner.js +63 -0
  19. package/dist/src/commands/_task-picker.js +44 -16
  20. package/dist/src/commands/agent.js +8 -9
  21. package/dist/src/commands/browser.js +4 -6
  22. package/dist/src/commands/connect.js +134 -26
  23. package/dist/src/commands/dist.js +4 -6
  24. package/dist/src/commands/doctor.js +65 -34
  25. package/dist/src/commands/github.js +62 -32
  26. package/dist/src/commands/inbox.js +396 -31
  27. package/dist/src/commands/init.js +342 -88
  28. package/dist/src/commands/inspect.js +282 -23
  29. package/dist/src/commands/inspector.js +2 -4
  30. package/dist/src/commands/pi.js +168 -0
  31. package/dist/src/commands/plugin.js +81 -22
  32. package/dist/src/commands/profile-and-review.js +8 -10
  33. package/dist/src/commands/queue.js +2 -3
  34. package/dist/src/commands/remote.js +18 -20
  35. package/dist/src/commands/repo-git-harness.js +6 -8
  36. package/dist/src/commands/run.js +1407 -130
  37. package/dist/src/commands/server.js +266 -40
  38. package/dist/src/commands/setup.js +69 -44
  39. package/dist/src/commands/task-report-bug.js +5 -7
  40. package/dist/src/commands/task-run-driver.js +662 -70
  41. package/dist/src/commands/task.js +1846 -259
  42. package/dist/src/commands/test.js +3 -5
  43. package/dist/src/commands/workspace.js +4 -6
  44. package/dist/src/commands.js +4054 -1180
  45. package/dist/src/index.js +4065 -1200
  46. package/dist/src/launcher.js +5 -3
  47. package/dist/src/report-bug.js +3 -3
  48. package/dist/src/runner.js +5 -19
  49. package/package.json +7 -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,239 @@ 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 writeJsonFile3(path, value) {
422
+ mkdirSync(dirname(path), { recursive: true });
423
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
424
+ `, "utf8");
425
+ }
426
+ function normalizeConnection(value) {
427
+ if (!value || typeof value !== "object" || Array.isArray(value))
428
+ return null;
429
+ const record = value;
430
+ if (record.kind === "local")
431
+ return { kind: "local", mode: "auto" };
432
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
433
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
434
+ return { kind: "remote", baseUrl };
435
+ }
436
+ return null;
437
+ }
438
+ function readGlobalConnections(options = {}) {
439
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
440
+ const payload = readJsonFile(path);
441
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
442
+ return { connections: {} };
443
+ }
444
+ const rawConnections = payload.connections;
445
+ const connections = {};
446
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
447
+ for (const [alias, raw] of Object.entries(rawConnections)) {
448
+ const connection = normalizeConnection(raw);
449
+ if (connection)
450
+ connections[alias] = connection;
451
+ }
452
+ }
453
+ return { connections };
454
+ }
455
+ function readRepoConnection(projectRoot) {
456
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
457
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
458
+ return null;
459
+ const record = payload;
460
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
461
+ if (!selected)
462
+ return null;
463
+ return {
464
+ selected,
465
+ project: typeof record.project === "string" ? record.project : undefined,
466
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
467
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
468
+ };
469
+ }
470
+ function writeRepoConnection(projectRoot, state) {
471
+ writeJsonFile3(resolveRepoConnectionPath(projectRoot), state);
472
+ }
473
+ function resolveSelectedConnection(projectRoot, options = {}) {
474
+ const repo = readRepoConnection(projectRoot);
475
+ if (!repo)
476
+ return null;
477
+ if (repo.selected === "local")
478
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
479
+ const global = readGlobalConnections(options);
480
+ const connection = global.connections[repo.selected];
481
+ if (!connection) {
482
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
483
+ }
484
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
485
+ }
486
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
487
+ const repo = readRepoConnection(projectRoot);
488
+ if (!repo)
489
+ return;
490
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
491
+ }
492
+
493
+ // packages/cli/src/commands/_server-client.ts
494
+ var scopedGitHubBearerTokens = new Map;
495
+ function cleanToken(value) {
496
+ const trimmed = value?.trim();
497
+ return trimmed ? trimmed : null;
498
+ }
499
+ function readPrivateRemoteSessionToken(projectRoot) {
500
+ const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
501
+ if (!existsSync3(path))
502
+ return null;
503
+ try {
504
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
505
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
506
+ } catch {
507
+ return null;
508
+ }
509
+ }
510
+ function readGitHubBearerTokenForRemote(projectRoot) {
511
+ const scopedKey = resolve5(projectRoot);
512
+ if (scopedGitHubBearerTokens.has(scopedKey))
513
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
514
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
515
+ if (privateSession)
516
+ return privateSession;
517
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
518
+ }
519
+ async function ensureServerForCli(projectRoot) {
520
+ try {
521
+ const selected = resolveSelectedConnection(projectRoot);
522
+ if (selected?.connection.kind === "remote") {
523
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
524
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
525
+ return {
526
+ baseUrl: selected.connection.baseUrl,
527
+ authToken,
528
+ connectionKind: "remote",
529
+ serverProjectRoot
530
+ };
531
+ }
532
+ const connection = await ensureLocalRigServerConnection(projectRoot);
533
+ return {
534
+ baseUrl: connection.baseUrl,
535
+ authToken: connection.authToken,
536
+ connectionKind: "local",
537
+ serverProjectRoot: resolve5(projectRoot)
538
+ };
539
+ } catch (error) {
540
+ if (error instanceof Error) {
541
+ throw new CliError2(error.message, 1);
542
+ }
543
+ throw error;
544
+ }
545
+ }
546
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
547
+ const repo = readRepoConnection(projectRoot);
548
+ const slug = repo?.project?.trim();
549
+ if (!slug)
550
+ return null;
551
+ try {
552
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
553
+ headers: mergeHeaders(undefined, authToken)
554
+ });
555
+ if (!response.ok)
556
+ return null;
557
+ const payload = await response.json();
558
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
559
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
560
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
561
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
562
+ if (path)
563
+ writeRepoServerProjectRoot(projectRoot, path);
564
+ return path;
565
+ } catch {
566
+ return null;
567
+ }
568
+ }
569
+ function mergeHeaders(headers, authToken) {
570
+ const merged = new Headers(headers);
571
+ if (authToken) {
572
+ merged.set("authorization", `Bearer ${authToken}`);
573
+ }
574
+ return merged;
575
+ }
576
+ function diagnosticMessage(payload) {
577
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
578
+ return null;
579
+ const record = payload;
580
+ const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
581
+ const messages = diagnostics.flatMap((entry) => {
582
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
583
+ return [];
584
+ const diagnostic = entry;
585
+ const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
586
+ const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
587
+ return message ? [`${kind}: ${message}`] : [];
588
+ });
589
+ return messages.length > 0 ? messages.join("; ") : null;
590
+ }
591
+ async function requestServerJson(context, pathname, init = {}) {
592
+ const server = await ensureServerForCli(context.projectRoot);
593
+ const headers = mergeHeaders(init.headers, server.authToken);
594
+ if (server.serverProjectRoot)
595
+ headers.set("x-rig-project-root", server.serverProjectRoot);
596
+ const response = await fetch(`${server.baseUrl}${pathname}`, {
597
+ ...init,
598
+ headers
599
+ });
600
+ const text = await response.text();
601
+ const payload = text.trim().length > 0 ? (() => {
602
+ try {
603
+ return JSON.parse(text);
604
+ } catch {
605
+ return null;
606
+ }
607
+ })() : null;
608
+ if (!response.ok) {
609
+ const diagnostics = diagnosticMessage(payload);
610
+ const detail = diagnostics ?? (text || response.statusText);
611
+ throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
612
+ }
613
+ return payload;
614
+ }
615
+ async function updateWorkspaceTaskViaServer(context, input) {
616
+ const payload = await requestServerJson(context, "/api/tasks/update", {
617
+ method: "POST",
618
+ headers: { "content-type": "application/json" },
619
+ body: JSON.stringify(input)
620
+ });
621
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
622
+ }
623
+
391
624
  // packages/cli/src/commands/task-run-driver.ts
392
625
  var PI_CANONICAL_RUN_STAGES = [
393
626
  "Connect",
@@ -431,6 +664,7 @@ function buildPiRigBridgeEnv(input) {
431
664
  RIG_SERVER_RUN_ID: input.runId,
432
665
  RIG_TASK_ID: input.taskId,
433
666
  RIG_RUNTIME_ADAPTER: "pi",
667
+ RIG_STEERING_POLL_MS: "0",
434
668
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
435
669
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
436
670
  ...githubBridgeEnv(githubToken)
@@ -455,12 +689,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
455
689
  return 0;
456
690
  let copied = 0;
457
691
  for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
458
- const sourcePath = resolve4(sourceRoot, relativePath);
459
- const targetPath = resolve4(targetRoot, relativePath);
692
+ const sourcePath = resolve6(sourceRoot, relativePath);
693
+ const targetPath = resolve6(targetRoot, relativePath);
460
694
  try {
461
695
  if (!statSync(sourcePath).isFile())
462
696
  continue;
463
- mkdirSync(resolve4(targetPath, ".."), { recursive: true });
697
+ mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
464
698
  copyFileSync(sourcePath, targetPath);
465
699
  copied += 1;
466
700
  } catch {}
@@ -499,7 +733,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
499
733
  return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
500
734
  return {
501
735
  RIG_BASELINE_MODE: "dirty-snapshot",
502
- RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
736
+ RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
503
737
  };
504
738
  }
505
739
  function positiveInt(value, fallback) {
@@ -507,7 +741,7 @@ function positiveInt(value, fallback) {
507
741
  }
508
742
  function resolveTaskRunAutomationLimits(config, env = process.env) {
509
743
  const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
510
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 30);
744
+ const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
511
745
  return {
512
746
  maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
513
747
  maxPrFixIterations: configuredPrFixIterations
@@ -601,12 +835,6 @@ function appendPiStageLog(input) {
601
835
  });
602
836
  emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
603
837
  }
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
838
  function createCommandRunner(binary) {
611
839
  return async (args, options) => {
612
840
  const child = spawn(binary, [...args], {
@@ -617,9 +845,9 @@ function createCommandRunner(binary) {
617
845
  const stderrChunks = [];
618
846
  child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
619
847
  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({
848
+ return await new Promise((resolve7) => {
849
+ child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
850
+ child.once("close", (code) => resolve7({
623
851
  exitCode: code ?? 1,
624
852
  stdout: Buffer.concat(stdoutChunks).toString("utf8"),
625
853
  stderr: Buffer.concat(stderrChunks).toString("utf8")
@@ -643,8 +871,9 @@ async function runTaskRunPostValidationLifecycle(input) {
643
871
  if (!taskId) {
644
872
  return { status: "skipped" };
645
873
  }
646
- const config = input.config ?? {};
647
- const prMode = config.pr?.mode ?? "auto";
874
+ const configInput = input.config ?? null;
875
+ const config = configInput ?? {};
876
+ const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
648
877
  if (prMode === "off" || prMode === "ask") {
649
878
  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
879
  input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
@@ -660,7 +889,7 @@ async function runTaskRunPostValidationLifecycle(input) {
660
889
  gitCommand
661
890
  });
662
891
  const prAutomation = input.prAutomation ?? runPrAutomation;
663
- const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
892
+ const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
664
893
  const stage = input.appendStage ?? (() => {
665
894
  return;
666
895
  });
@@ -682,7 +911,7 @@ async function runTaskRunPostValidationLifecycle(input) {
682
911
  command: gitCommand
683
912
  });
684
913
  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");
914
+ await pushBranchSyncedWithOrigin({ projectRoot: workspace, branch, gitCommand });
686
915
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
687
916
  const pr = await prAutomation({
688
917
  projectRoot: workspace,
@@ -692,7 +921,9 @@ async function runTaskRunPostValidationLifecycle(input) {
692
921
  config,
693
922
  sourceTask: input.sourceTask,
694
923
  uploadedSnapshot: input.uploadedSnapshot,
924
+ artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
695
925
  command: ghCommand,
926
+ gitCommand,
696
927
  steerPi,
697
928
  lifecycle: {
698
929
  onPrOpened: async ({ prUrl }) => {
@@ -741,8 +972,6 @@ async function runTaskRunPostValidationLifecycle(input) {
741
972
  runtimeWorkspace: input.runtimeWorkspace ?? null
742
973
  })
743
974
  }
744
- }).catch(() => {
745
- return;
746
975
  });
747
976
  }
748
977
  },
@@ -761,8 +990,6 @@ async function runTaskRunPostValidationLifecycle(input) {
761
990
  runtimeWorkspace: input.runtimeWorkspace ?? null
762
991
  })
763
992
  }
764
- }).catch(() => {
765
- return;
766
993
  });
767
994
  }
768
995
  },
@@ -791,8 +1018,17 @@ async function runTaskRunPostValidationLifecycle(input) {
791
1018
  errorText: detail
792
1019
  })
793
1020
  }
794
- }).catch(() => {
795
- return;
1021
+ }).catch((error) => {
1022
+ try {
1023
+ appendRunLog(input.projectRoot, input.runId, {
1024
+ id: `log:${input.runId}:task-source-needs-attention-update`,
1025
+ title: "Task source needs-attention update failed",
1026
+ detail: error instanceof Error ? error.message : String(error),
1027
+ tone: "error",
1028
+ status: "needs_attention",
1029
+ createdAt: new Date().toISOString()
1030
+ });
1031
+ } catch {}
796
1032
  });
797
1033
  }
798
1034
  return { status: "needs_attention", pr };
@@ -815,7 +1051,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
815
1051
  return null;
816
1052
  }
817
1053
  for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
818
- const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
1054
+ const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
819
1055
  if (!summary || summary.status !== "fail") {
820
1056
  continue;
821
1057
  }
@@ -896,14 +1132,14 @@ function readTaskRunAcceptedArtifactState(input) {
896
1132
  if (!input.taskId || !input.workspaceDir) {
897
1133
  return { accepted: false, reason: null };
898
1134
  }
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");
1135
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1136
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1137
+ const taskResultPath = resolve6(artifactDir, "task-result.json");
902
1138
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
903
1139
  if (reviewStatus !== "APPROVED") {
904
1140
  return { accepted: false, reason: null };
905
1141
  }
906
- const taskResult = readJsonFile(taskResultPath, null);
1142
+ const taskResult = readJsonFile2(taskResultPath, null);
907
1143
  if (taskResult?.status !== "completed") {
908
1144
  return { accepted: false, reason: null };
909
1145
  }
@@ -935,13 +1171,13 @@ function resolveTaskRunRetryContext(input) {
935
1171
  if (!input.taskId || !input.workspaceDir) {
936
1172
  return { shouldRetry: false, failureDetail: null, nextPrompt: null };
937
1173
  }
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);
1174
+ const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1175
+ const reviewStatePath = resolve6(artifactDir, "review-state.json");
1176
+ const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1177
+ const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1178
+ const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1179
+ const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1180
+ const reviewState = readJsonFile2(reviewStatePath, null);
945
1181
  const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
946
1182
  const reviewRejected = isTaskRunReviewRejected(reviewState);
947
1183
  if (reviewStatus === "APPROVED") {
@@ -994,12 +1230,143 @@ function summarizeTaskRunReviewFailure(reviewState) {
994
1230
  }
995
1231
  return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
996
1232
  }
1233
+ function appendAssistantTimelineFromRecord(input) {
1234
+ let nextAssistantText = input.assistantText;
1235
+ if (input.record.type === "message_update") {
1236
+ const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1237
+ if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1238
+ nextAssistantText += assistantMessageEvent.delta;
1239
+ }
1240
+ } else if (input.record.type === "stream_event") {
1241
+ const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1242
+ const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1243
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
1244
+ nextAssistantText += delta.text;
1245
+ }
1246
+ } else if (input.record.type === "assistant") {
1247
+ const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1248
+ const content = Array.isArray(message.content) ? message.content : [];
1249
+ const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1250
+ if (fullText.length > nextAssistantText.length)
1251
+ nextAssistantText = fullText;
1252
+ }
1253
+ if (nextAssistantText !== input.assistantText) {
1254
+ appendRunTimeline(input.projectRoot, input.runId, {
1255
+ id: input.messageId,
1256
+ type: "assistant_message",
1257
+ text: nextAssistantText,
1258
+ state: "streaming",
1259
+ createdAt: new Date().toISOString()
1260
+ });
1261
+ }
1262
+ return nextAssistantText;
1263
+ }
1264
+ function appendPiRpcProtocolLogFromRecord(input) {
1265
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1266
+ if (type === "response") {
1267
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
1268
+ const success = input.record.success !== false;
1269
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
1270
+ return true;
1271
+ }
1272
+ appendRunLog(input.projectRoot, input.runId, {
1273
+ id: input.nextRunLogId(),
1274
+ title: success ? "Pi RPC response" : "Pi RPC error",
1275
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
1276
+ tone: success ? "tool" : "error",
1277
+ status: input.status,
1278
+ payload: input.record,
1279
+ createdAt: new Date().toISOString()
1280
+ });
1281
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
1282
+ return true;
1283
+ }
1284
+ if (type !== "extension_ui_request")
1285
+ return false;
1286
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
1287
+ let title = "Pi UI event";
1288
+ let detail = method;
1289
+ let tone = "info";
1290
+ if (method === "notify") {
1291
+ title = "Pi notification";
1292
+ detail = String(input.record.message ?? "");
1293
+ tone = input.record.notifyType === "error" ? "error" : "info";
1294
+ } else if (method === "setStatus") {
1295
+ title = "Pi UI status";
1296
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
1297
+ tone = "tool";
1298
+ } else if (method === "setWidget") {
1299
+ title = "Pi UI widget";
1300
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
1301
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
1302
+ tone = "tool";
1303
+ } else if (method === "setTitle") {
1304
+ title = "Pi UI title";
1305
+ detail = String(input.record.title ?? "");
1306
+ tone = "tool";
1307
+ } else if (method === "set_editor_text") {
1308
+ title = "Pi editor update";
1309
+ detail = String(input.record.text ?? "").slice(0, 500);
1310
+ tone = "tool";
1311
+ } else {
1312
+ title = "Pi UI request";
1313
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
1314
+ }
1315
+ appendRunLog(input.projectRoot, input.runId, {
1316
+ id: input.nextRunLogId(),
1317
+ title,
1318
+ detail,
1319
+ tone,
1320
+ status: input.status,
1321
+ payload: input.record,
1322
+ createdAt: new Date().toISOString()
1323
+ });
1324
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1325
+ return true;
1326
+ }
1327
+ function appendPiToolTimelineFromRecord(input) {
1328
+ const type = typeof input.record.type === "string" ? input.record.type : "";
1329
+ if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1330
+ return false;
1331
+ const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1332
+ const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1333
+ const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1334
+ appendRunTimeline(input.projectRoot, input.runId, {
1335
+ id: `tool:${toolCallId}:${type}`,
1336
+ type,
1337
+ toolName,
1338
+ status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1339
+ createdAt: new Date().toISOString()
1340
+ });
1341
+ return true;
1342
+ }
1343
+ function isNonRenderablePiProtocolRecord(record) {
1344
+ const type = typeof record.type === "string" ? record.type : "";
1345
+ 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");
1346
+ }
1347
+ function appendToolTimelineFromLog(input) {
1348
+ const title = typeof input.log.title === "string" ? input.log.title : "";
1349
+ if (title !== "Tool activity")
1350
+ return;
1351
+ const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1352
+ 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;
1353
+ const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1354
+ appendRunTimeline(input.projectRoot, input.runId, {
1355
+ id: `tool:${logId}`,
1356
+ type: "tool_execution_update",
1357
+ toolName,
1358
+ status: typeof input.log.status === "string" ? input.log.status : "running",
1359
+ detail: typeof input.log.detail === "string" ? input.log.detail : null,
1360
+ payload,
1361
+ createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1362
+ });
1363
+ }
997
1364
  function readTaskRunReviewStatus(reviewStatusPath) {
998
- if (!existsSync2(reviewStatusPath)) {
1365
+ if (!existsSync4(reviewStatusPath)) {
999
1366
  return null;
1000
1367
  }
1001
1368
  try {
1002
- const status = readFileSync2(reviewStatusPath, "utf8").trim().toUpperCase();
1369
+ const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1003
1370
  return status === "APPROVED" || status === "REJECTED" ? status : null;
1004
1371
  } catch {
1005
1372
  return null;
@@ -1017,7 +1384,38 @@ function isTaskRunReviewRejected(reviewState) {
1017
1384
  function runSourceTaskIdentity(sourceTask) {
1018
1385
  return sourceTask;
1019
1386
  }
1020
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateConfiguredTaskSourceTask) {
1387
+ function sourceTaskIssueNodeId(sourceTask) {
1388
+ if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1389
+ return null;
1390
+ const record = sourceTask;
1391
+ const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1392
+ if (direct?.trim())
1393
+ return direct.trim();
1394
+ const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1395
+ return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1396
+ }
1397
+ var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1398
+ const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1399
+ id: input.taskId,
1400
+ ...input.update.status ? { status: input.update.status } : {},
1401
+ ...input.update.comment ? { comment: input.update.comment } : {},
1402
+ ...input.update.title ? { title: input.update.title } : {},
1403
+ ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1404
+ issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1405
+ });
1406
+ if (serverResult.ok === false) {
1407
+ throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1408
+ }
1409
+ return {
1410
+ updated: serverResult.ok !== false,
1411
+ taskId: input.taskId,
1412
+ status: input.update.status,
1413
+ source: "server",
1414
+ sourceKind: "server",
1415
+ projectSync: serverResult.projectSync
1416
+ };
1417
+ };
1418
+ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
1021
1419
  if (!taskId)
1022
1420
  return;
1023
1421
  const config = await loadTaskRunAutomationConfig(projectRoot);
@@ -1107,11 +1505,7 @@ async function executeRigOwnedTaskRun(context, input) {
1107
1505
  ...input.model ? ["--model", input.model] : [],
1108
1506
  "--prompt"
1109
1507
  ] : input.runtimeAdapter === "pi" ? [
1110
- "--print",
1111
- "--verbose",
1112
- "--mode",
1113
- "json",
1114
- "--no-session",
1508
+ "__rig_pi_session_daemon__",
1115
1509
  ...input.model ? ["--model", input.model] : []
1116
1510
  ] : [
1117
1511
  "--print",
@@ -1186,15 +1580,15 @@ async function executeRigOwnedTaskRun(context, input) {
1186
1580
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1187
1581
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1188
1582
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1189
- const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1583
+ const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1190
1584
  const persistedPlanning = {
1191
1585
  ...planningClassification,
1192
1586
  classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1193
1587
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1194
1588
  classifiedAt: new Date().toISOString()
1195
1589
  };
1196
- mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1197
- writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1590
+ mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1591
+ writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1198
1592
  `, "utf8");
1199
1593
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1200
1594
  prompt = `${prompt}
@@ -1208,7 +1602,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1208
1602
  projectRoot: context.projectRoot,
1209
1603
  runId: input.runId,
1210
1604
  stage,
1211
- 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,
1605
+ 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,
1212
1606
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
1213
1607
  });
1214
1608
  }
@@ -1244,11 +1638,12 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1244
1638
  let verificationStarted = false;
1245
1639
  let reviewStarted = false;
1246
1640
  let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1247
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1641
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1248
1642
  let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1249
1643
  let latestProviderCommand = null;
1250
1644
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1251
1645
  let snapshotSidecarPromise = null;
1646
+ let wrapperManagesRuntimeSnapshot = false;
1252
1647
  let dirtyBaselineApplied = false;
1253
1648
  const childEnv = {
1254
1649
  ...process.env,
@@ -1301,6 +1696,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1301
1696
  detail: detail ?? "Verifier review is running."
1302
1697
  });
1303
1698
  };
1699
+ const nextRunLogId = createRunLogIdFactory(input.runId);
1304
1700
  const handleWrapperEvent = (rawPayload) => {
1305
1701
  let event = null;
1306
1702
  try {
@@ -1316,6 +1712,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1316
1712
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
1317
1713
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
1318
1714
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
1715
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
1319
1716
  latestRuntimeBranch = runtimeId;
1320
1717
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
1321
1718
  runtimeId: runtimeId ?? null,
@@ -1327,10 +1724,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1327
1724
  patchAuthorityRun(context.projectRoot, input.runId, {
1328
1725
  status: "running",
1329
1726
  worktreePath: latestRuntimeWorkspace,
1330
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1727
+ artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1331
1728
  logRoot: latestLogsDir,
1332
- sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
1333
- sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
1729
+ sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1730
+ sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1334
1731
  branch: runtimeId
1335
1732
  });
1336
1733
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
@@ -1338,8 +1735,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1338
1735
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1339
1736
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1340
1737
  if (readyFile) {
1341
- mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1342
- writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1738
+ mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1739
+ writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1343
1740
  `, "utf8");
1344
1741
  }
1345
1742
  appendRunLog(context.projectRoot, input.runId, {
@@ -1353,7 +1750,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1353
1750
  });
1354
1751
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1355
1752
  }
1356
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1753
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1357
1754
  snapshotSidecarPromise = (async () => {
1358
1755
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
1359
1756
  projectRoot: context.projectRoot,
@@ -1435,9 +1832,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1435
1832
  }
1436
1833
  return true;
1437
1834
  }
1835
+ 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") {
1836
+ 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";
1837
+ 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");
1838
+ appendRunLog(context.projectRoot, input.runId, {
1839
+ id: nextRunLogId(),
1840
+ title,
1841
+ detail,
1842
+ tone: event.type === "pi.session.error" ? "error" : "info",
1843
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1844
+ payload: {
1845
+ eventType: event.type,
1846
+ runId: typeof payload.runId === "string" ? payload.runId : null,
1847
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
1848
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
1849
+ },
1850
+ createdAt: new Date().toISOString()
1851
+ });
1852
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1853
+ return true;
1854
+ }
1855
+ if (event.type === "pi.session.ready") {
1856
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
1857
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
1858
+ if (privateMetadata) {
1859
+ patchAuthorityRun(context.projectRoot, input.runId, {
1860
+ piSession: publicMetadata,
1861
+ piSessionPrivate: privateMetadata
1862
+ });
1863
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
1864
+ appendRunLog(context.projectRoot, input.runId, {
1865
+ id: `log:${input.runId}:pi-session-ready`,
1866
+ title: "Worker Pi session ready",
1867
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
1868
+ tone: "info",
1869
+ status: "running",
1870
+ payload: {
1871
+ sessionId: sessionId || null,
1872
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
1873
+ },
1874
+ createdAt: new Date().toISOString()
1875
+ });
1876
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
1877
+ }
1878
+ return true;
1879
+ }
1880
+ 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") {
1881
+ 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";
1882
+ 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")}`;
1883
+ appendRunLog(context.projectRoot, input.runId, {
1884
+ id: nextRunLogId(),
1885
+ title,
1886
+ detail,
1887
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
1888
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
1889
+ payload,
1890
+ createdAt: new Date().toISOString()
1891
+ });
1892
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
1893
+ return true;
1894
+ }
1438
1895
  return false;
1439
1896
  };
1440
- const nextRunLogId = createRunLogIdFactory(input.runId);
1441
1897
  const handleAgentStdoutLine = (line) => {
1442
1898
  const trimmed = line.trim();
1443
1899
  if (!trimmed)
@@ -1471,6 +1927,19 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1471
1927
  try {
1472
1928
  const record = JSON.parse(trimmed);
1473
1929
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
1930
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
1931
+ projectRoot: context.projectRoot,
1932
+ runId: input.runId,
1933
+ record,
1934
+ status: liveLogStatus,
1935
+ nextRunLogId
1936
+ })) {
1937
+ return;
1938
+ }
1939
+ if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
1940
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1941
+ return;
1942
+ }
1474
1943
  const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
1475
1944
  runId: input.runId,
1476
1945
  record,
@@ -1487,7 +1956,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1487
1956
  if (providerLogs.length > 0) {
1488
1957
  for (const providerLog of providerLogs) {
1489
1958
  appendRunLog(context.projectRoot, input.runId, providerLog);
1959
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
1490
1960
  emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
1961
+ if (providerLog.title === "Tool activity")
1962
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1491
1963
  }
1492
1964
  }
1493
1965
  if (input.runtimeAdapter === "codex") {
@@ -1539,6 +2011,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1539
2011
  return;
1540
2012
  }
1541
2013
  }
2014
+ if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
2015
+ return;
2016
+ }
1542
2017
  if (record.type === "assistant") {
1543
2018
  const message = record.message && typeof record.message === "object" ? record.message : record;
1544
2019
  const content = Array.isArray(message.content) ? message.content : [];
@@ -1645,7 +2120,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1645
2120
  let acceptedArtifactObservedAt = null;
1646
2121
  let acceptedArtifactPollTimer = null;
1647
2122
  let acceptedArtifactKillTimer = null;
1648
- const attemptExit = await new Promise((resolve5) => {
2123
+ const attemptExit = await new Promise((resolve7) => {
1649
2124
  let settled = false;
1650
2125
  const settle = (result) => {
1651
2126
  if (settled)
@@ -1653,7 +2128,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1653
2128
  settled = true;
1654
2129
  if (acceptedArtifactPollTimer)
1655
2130
  clearInterval(acceptedArtifactPollTimer);
1656
- resolve5(result);
2131
+ resolve7(result);
1657
2132
  };
1658
2133
  const pollAcceptedArtifacts = () => {
1659
2134
  const artifactState = readTaskRunAcceptedArtifactState({
@@ -1726,7 +2201,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1726
2201
  });
1727
2202
  for (const pendingLog of pendingLogs) {
1728
2203
  appendRunLog(context.projectRoot, input.runId, pendingLog);
2204
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
1729
2205
  emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2206
+ if (pendingLog.title === "Tool activity")
2207
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
1730
2208
  }
1731
2209
  process.off("SIGTERM", forwardSigterm);
1732
2210
  if (attemptExit.error) {
@@ -1852,8 +2330,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1852
2330
  }
1853
2331
  if (planningClassification.planningRequired) {
1854
2332
  const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1855
- const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1856
- if (!existsSync2(expectedPlanPath)) {
2333
+ const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2334
+ if (!existsSync4(expectedPlanPath)) {
1857
2335
  const failedAt = new Date().toISOString();
1858
2336
  const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1859
2337
  patchAuthorityRun(context.projectRoot, input.runId, {
@@ -1873,6 +2351,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1873
2351
  throw new CliError2(failureDetail, 1);
1874
2352
  }
1875
2353
  }
2354
+ if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2355
+ appendPiStageLog({
2356
+ projectRoot: context.projectRoot,
2357
+ runId: input.runId,
2358
+ stage: "Validate",
2359
+ detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2360
+ status: "completed"
2361
+ });
2362
+ if (verificationAction && !reviewStarted) {
2363
+ verificationAction.complete("Completion verification checks finished.");
2364
+ }
2365
+ if (!reviewAction) {
2366
+ promoteToReviewing("Server-owned closeout is queued.");
2367
+ }
2368
+ if (reviewAction) {
2369
+ reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2370
+ }
2371
+ const requestedAt = new Date().toISOString();
2372
+ patchAuthorityRun(context.projectRoot, input.runId, {
2373
+ status: "reviewing",
2374
+ completedAt: null,
2375
+ errorText: null,
2376
+ serverCloseout: {
2377
+ status: "pending",
2378
+ phase: "queued",
2379
+ requestedAt,
2380
+ updatedAt: requestedAt,
2381
+ runtimeWorkspace: latestRuntimeWorkspace,
2382
+ branch: latestRuntimeBranch,
2383
+ taskId: input.taskId ?? runtimeTaskId
2384
+ }
2385
+ });
2386
+ appendRunLog(context.projectRoot, input.runId, {
2387
+ id: `log:${input.runId}:server-closeout-requested`,
2388
+ title: "Server-owned closeout requested",
2389
+ detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2390
+ tone: "info",
2391
+ status: "reviewing",
2392
+ createdAt: requestedAt,
2393
+ payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2394
+ });
2395
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2396
+ emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
2397
+ await context.emitEvent("command.finished", {
2398
+ command: [
2399
+ "rig",
2400
+ "server",
2401
+ "task-run",
2402
+ ...input.taskId ? ["--task", input.taskId] : [],
2403
+ ...input.title ? ["--title", input.title] : []
2404
+ ],
2405
+ formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2406
+ exitCode: 0,
2407
+ durationMs: 0,
2408
+ startedAt,
2409
+ finishedAt: requestedAt
2410
+ });
2411
+ process.exit(0);
2412
+ }
1876
2413
  const runPiPrFeedbackFix = async (message) => {
1877
2414
  appendPiStageLog({
1878
2415
  projectRoot: context.projectRoot,
@@ -1900,11 +2437,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1900
2437
  child.stdin.write(message);
1901
2438
  }
1902
2439
  child.stdin.end();
2440
+ const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2441
+ let feedbackAssistantText = "";
2442
+ const feedbackPendingToolUses = new Map;
1903
2443
  const stdout = createLineInterface({ input: child.stdout });
1904
2444
  stdout.on("line", (line) => {
1905
2445
  const trimmed = line.trim();
1906
2446
  if (!trimmed)
1907
2447
  return;
2448
+ try {
2449
+ const record = JSON.parse(trimmed);
2450
+ const providerLogs = buildClaudeLogsFromRecord({
2451
+ runId: input.runId,
2452
+ record,
2453
+ createdAtFallback: new Date().toISOString(),
2454
+ status: "reviewing",
2455
+ pendingToolUses: feedbackPendingToolUses
2456
+ });
2457
+ for (const providerLog of providerLogs) {
2458
+ appendRunLog(context.projectRoot, input.runId, providerLog);
2459
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2460
+ emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2461
+ if (providerLog.title === "Tool activity")
2462
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2463
+ }
2464
+ const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2465
+ projectRoot: context.projectRoot,
2466
+ runId: input.runId,
2467
+ messageId: feedbackAssistantMessageId,
2468
+ record,
2469
+ assistantText: feedbackAssistantText
2470
+ });
2471
+ const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2472
+ if (hadAssistantDelta) {
2473
+ feedbackAssistantText = nextFeedbackAssistantText;
2474
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2475
+ }
2476
+ if (providerLogs.length > 0 || hadAssistantDelta)
2477
+ return;
2478
+ } catch {}
1908
2479
  appendRunLog(context.projectRoot, input.runId, {
1909
2480
  id: nextRunLogId(),
1910
2481
  title: "Pi PR feedback fix output",
@@ -1930,10 +2501,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1930
2501
  });
1931
2502
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
1932
2503
  });
1933
- const exitCode = await new Promise((resolve5) => {
1934
- child.once("error", () => resolve5(1));
1935
- child.once("close", (code) => resolve5(code ?? 1));
2504
+ const exitCode = await new Promise((resolve7) => {
2505
+ child.once("error", () => resolve7(1));
2506
+ child.once("close", (code) => resolve7(code ?? 1));
1936
2507
  });
2508
+ for (const pendingLog of flushPendingClaudeToolUseLogs({
2509
+ runId: input.runId,
2510
+ status: exitCode === 0 ? "completed" : "failed",
2511
+ pendingToolUses: feedbackPendingToolUses
2512
+ })) {
2513
+ appendRunLog(context.projectRoot, input.runId, pendingLog);
2514
+ appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2515
+ emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2516
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2517
+ }
2518
+ if (feedbackAssistantText.trim()) {
2519
+ appendRunTimeline(context.projectRoot, input.runId, {
2520
+ id: feedbackAssistantMessageId,
2521
+ type: "assistant_message",
2522
+ text: feedbackAssistantText,
2523
+ state: "completed",
2524
+ createdAt: new Date().toISOString(),
2525
+ completedAt: new Date().toISOString()
2526
+ });
2527
+ emitServerRunEvent({ type: "timeline", runId: input.runId });
2528
+ }
1937
2529
  if (exitCode !== 0) {
1938
2530
  throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
1939
2531
  }