@h-rig/cli 0.0.6-alpha.21 → 0.0.6-alpha.23

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.
@@ -2747,6 +2747,66 @@ async function steerRunViaServer(context, runId, message) {
2747
2747
  });
2748
2748
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2749
2749
  }
2750
+ async function getRunPiSessionViaServer(context, runId) {
2751
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
2752
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2753
+ }
2754
+ async function getRunPiMessagesViaServer(context, runId) {
2755
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
2756
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
2757
+ }
2758
+ async function getRunPiStatusViaServer(context, runId) {
2759
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
2760
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2761
+ }
2762
+ async function getRunPiCommandsViaServer(context, runId) {
2763
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
2764
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
2765
+ }
2766
+ async function sendRunPiPromptViaServer(context, runId, text2, streamingBehavior) {
2767
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
2768
+ method: "POST",
2769
+ headers: { "content-type": "application/json" },
2770
+ body: JSON.stringify({ text: text2, streamingBehavior })
2771
+ });
2772
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2773
+ }
2774
+ async function sendRunPiShellViaServer(context, runId, text2) {
2775
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
2776
+ method: "POST",
2777
+ headers: { "content-type": "application/json" },
2778
+ body: JSON.stringify({ text: text2 })
2779
+ });
2780
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2781
+ }
2782
+ async function runRunPiCommandViaServer(context, runId, text2) {
2783
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
2784
+ method: "POST",
2785
+ headers: { "content-type": "application/json" },
2786
+ body: JSON.stringify({ text: text2 })
2787
+ });
2788
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
2789
+ }
2790
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
2791
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
2792
+ method: "POST",
2793
+ headers: { "content-type": "application/json" },
2794
+ body: JSON.stringify({ requestId, ...valueOrCancel })
2795
+ });
2796
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2797
+ }
2798
+ async function abortRunPiViaServer(context, runId) {
2799
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
2800
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
2801
+ }
2802
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
2803
+ const server = await ensureServerForCli(context.projectRoot);
2804
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
2805
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
2806
+ if (server.authToken)
2807
+ url.searchParams.set("token", server.authToken);
2808
+ return url.toString();
2809
+ }
2750
2810
  async function submitTaskRunViaServer(context, input) {
2751
2811
  const isTaskRun = Boolean(input.taskId);
2752
2812
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -2779,183 +2839,6 @@ async function submitTaskRunViaServer(context, input) {
2779
2839
  return { runId };
2780
2840
  }
2781
2841
 
2782
- // packages/cli/src/commands/_pi-install.ts
2783
- import { existsSync as existsSync5, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2784
- import { homedir as homedir3 } from "os";
2785
- import { resolve as resolve9 } from "path";
2786
- var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2787
- var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2788
- var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2789
- export { default } from '@rig/pi-rig';
2790
- `;
2791
- async function defaultCommandRunner(command, options = {}) {
2792
- const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
2793
- const [stdout, stderr, exitCode] = await Promise.all([
2794
- new Response(proc.stdout).text(),
2795
- new Response(proc.stderr).text(),
2796
- proc.exited
2797
- ]);
2798
- return { exitCode, stdout, stderr };
2799
- }
2800
- function resolvePiRigExtensionPath(homeDir) {
2801
- return resolve9(homeDir, ".pi", "agent", "extensions", "pi-rig");
2802
- }
2803
- function resolvePiRigPackageSource(projectRoot, exists = existsSync5) {
2804
- const localPackage = resolve9(projectRoot, "packages", "pi-rig");
2805
- if (exists(resolve9(localPackage, "package.json")))
2806
- return localPackage;
2807
- return `npm:${PI_RIG_PACKAGE_NAME}`;
2808
- }
2809
- function resolvePiHomeDir(inputHomeDir) {
2810
- return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir3();
2811
- }
2812
- function piListContainsPiRig(output) {
2813
- return output.split(/\r?\n/).some((line) => {
2814
- const normalized = line.trim();
2815
- return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
2816
- });
2817
- }
2818
- async function safeRun(runner, command, options) {
2819
- try {
2820
- return await runner(command, options);
2821
- } catch (error) {
2822
- return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
2823
- }
2824
- }
2825
- function splitInstallCommand(value) {
2826
- return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
2827
- }
2828
- async function ensurePiBinaryAvailable(input) {
2829
- const current = await safeRun(input.runner, ["pi", "--version"]);
2830
- if (current.exitCode === 0) {
2831
- const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
2832
- if (updateCommand) {
2833
- const parts2 = splitInstallCommand(updateCommand);
2834
- if (parts2.length > 0)
2835
- await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
2836
- }
2837
- return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
2838
- }
2839
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
2840
- const parts = splitInstallCommand(installCommand);
2841
- if (parts.length === 0) {
2842
- return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
2843
- }
2844
- const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
2845
- if (install.exitCode !== 0) {
2846
- return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
2847
- }
2848
- const next = await safeRun(input.runner, ["pi", "--version"]);
2849
- return {
2850
- ok: next.exitCode === 0,
2851
- installedOrUpdated: true,
2852
- detail: (next.stdout || next.stderr).trim() || undefined,
2853
- ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
2854
- };
2855
- }
2856
- function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync5) {
2857
- const extensionPath = resolvePiRigExtensionPath(homeDir);
2858
- const indexPath = resolve9(extensionPath, "index.ts");
2859
- if (!exists(indexPath))
2860
- return;
2861
- try {
2862
- const content = readFileSync4(indexPath, "utf8");
2863
- if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
2864
- rmSync3(extensionPath, { recursive: true, force: true });
2865
- }
2866
- } catch {}
2867
- }
2868
- async function checkPiRigInstall(input = {}) {
2869
- const home = resolvePiHomeDir(input.homeDir);
2870
- const extensionPath = resolvePiRigExtensionPath(home);
2871
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
2872
- return {
2873
- extensionPath,
2874
- pi: { ok: true, label: "pi", detail: "fake-pi" },
2875
- piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
2876
- };
2877
- }
2878
- const exists = input.exists ?? existsSync5;
2879
- const runner = input.commandRunner ?? defaultCommandRunner;
2880
- const piResult = await safeRun(runner, ["pi", "--version"]);
2881
- const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
2882
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
2883
- ${piListResult.stderr}`);
2884
- const legacyBridge = exists(resolve9(extensionPath, "index.ts"));
2885
- const hasPiRig = listedPiRig;
2886
- return {
2887
- extensionPath,
2888
- pi: {
2889
- ok: piResult.exitCode === 0,
2890
- label: "pi",
2891
- detail: (piResult.stdout || piResult.stderr).trim() || undefined,
2892
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
2893
- },
2894
- piRig: {
2895
- ok: hasPiRig,
2896
- label: "pi-rig global extension",
2897
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
2898
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
2899
- }
2900
- };
2901
- }
2902
- async function ensurePiRigInstalled(input) {
2903
- const home = resolvePiHomeDir(input.homeDir);
2904
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
2905
- const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
2906
- return { ...status2, installedPath: status2.extensionPath };
2907
- }
2908
- const runner = input.commandRunner ?? defaultCommandRunner;
2909
- const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
2910
- const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
2911
- extensionPath: resolvePiRigExtensionPath(home),
2912
- pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
2913
- piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
2914
- };
2915
- if (!piAvailable.ok) {
2916
- throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
2917
- }
2918
- const packageSource = resolvePiRigPackageSource(input.projectRoot);
2919
- removeManagedLegacyPiRigBridge(home);
2920
- const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
2921
- if (install.exitCode !== 0) {
2922
- throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
2923
- }
2924
- const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
2925
- return { ...next, installedPath: packageSource };
2926
- }
2927
- async function ensureRemotePiRigInstalled(input) {
2928
- const payload = await input.requestJson("/api/pi-rig/install", {
2929
- method: "POST",
2930
- headers: { "content-type": "application/json" },
2931
- body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
2932
- });
2933
- const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2934
- const piOk = record.piOk === true || record.ok === true;
2935
- const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
2936
- const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
2937
- return {
2938
- remote: true,
2939
- extensionPath,
2940
- pi: {
2941
- ok: piOk,
2942
- label: "pi",
2943
- detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
2944
- hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
2945
- },
2946
- piRig: {
2947
- ok: piRigOk,
2948
- label: "pi-rig global extension",
2949
- detail: extensionPath,
2950
- hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
2951
- }
2952
- };
2953
- }
2954
- async function buildPiSetupChecks(input = {}) {
2955
- const status = await checkPiRigInstall(input);
2956
- return [status.pi, status.piRig];
2957
- }
2958
-
2959
2842
  // packages/cli/src/commands/_preflight.ts
2960
2843
  function preflightCheck(id, label, status, detail, remediation) {
2961
2844
  return {
@@ -3113,14 +2996,7 @@ async function runFastTaskRunPreflight(context, options = {}) {
3113
2996
  }
3114
2997
  }
3115
2998
  if ((options.runtimeAdapter ?? "pi") === "pi") {
3116
- const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
3117
- ok: false,
3118
- label: "pi/pi-rig checks",
3119
- hint: message(error)
3120
- }]);
3121
- for (const pi of piChecks) {
3122
- checks.push(preflightCheck(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "fail", pi.detail, pi.hint ?? (pi.ok ? undefined : "Run `rig init --yes` to install/update Pi and enable pi-rig.")));
3123
- }
2999
+ checks.push(preflightCheck("runtime", "worker Pi SDK session daemon", "pass", selectedServer?.connectionKind === "remote" ? "remote worker-owned runtime" : "bundled server-owned runtime"));
3124
3000
  } else {
3125
3001
  checks.push(preflightCheck("runtime", "runtime adapter", "pass", options.runtimeAdapter));
3126
3002
  }
@@ -3236,7 +3112,7 @@ async function executeQueue(context, args) {
3236
3112
  }
3237
3113
 
3238
3114
  // packages/cli/src/commands/agent.ts
3239
- import { resolve as resolve11 } from "path";
3115
+ import { resolve as resolve10 } from "path";
3240
3116
  import {
3241
3117
  agentId,
3242
3118
  cleanupAgentRuntime,
@@ -3246,8 +3122,8 @@ import {
3246
3122
  } from "@rig/runtime/control-plane/runtime/isolation";
3247
3123
 
3248
3124
  // packages/cli/src/commands/_authority-runs.ts
3249
- import { existsSync as existsSync6 } from "fs";
3250
- import { resolve as resolve10 } from "path";
3125
+ import { existsSync as existsSync5 } from "fs";
3126
+ import { resolve as resolve9 } from "path";
3251
3127
  import {
3252
3128
  readAuthorityRun,
3253
3129
  readJsonlFile as readJsonlFile2,
@@ -3269,8 +3145,8 @@ function normalizeRuntimeAdapter(value) {
3269
3145
  return "claude-code";
3270
3146
  }
3271
3147
  function readLatestBeadRecord(projectRoot, taskId) {
3272
- const issuesPath = resolve10(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3273
- if (!existsSync6(issuesPath)) {
3148
+ const issuesPath = resolve9(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3149
+ if (!existsSync5(issuesPath)) {
3274
3150
  return null;
3275
3151
  }
3276
3152
  let latest = null;
@@ -3338,7 +3214,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3338
3214
  } else if ("errorText" in next) {
3339
3215
  delete next.errorText;
3340
3216
  }
3341
- writeJsonFile3(resolve10(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3217
+ writeJsonFile3(resolve9(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3342
3218
  return next;
3343
3219
  }
3344
3220
 
@@ -3459,10 +3335,10 @@ async function executeAgent(context, args) {
3459
3335
  status: "running",
3460
3336
  startedAt: createdAt,
3461
3337
  worktreePath: runtime.workspaceDir,
3462
- artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3338
+ artifactRoot: resolve10(runtime.workspaceDir, "artifacts", taskId),
3463
3339
  logRoot: runtime.logsDir,
3464
- sessionPath: resolve11(runtime.sessionDir, "session.json"),
3465
- sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3340
+ sessionPath: resolve10(runtime.sessionDir, "session.json"),
3341
+ sessionLogPath: resolve10(runtime.logsDir, "agent-stdout.log"),
3466
3342
  pid: process.pid
3467
3343
  });
3468
3344
  const result = await runInAgentRuntime({
@@ -3482,10 +3358,10 @@ async function executeAgent(context, args) {
3482
3358
  startedAt: createdAt,
3483
3359
  completedAt: failedAt,
3484
3360
  worktreePath: runtime.workspaceDir,
3485
- artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3361
+ artifactRoot: resolve10(runtime.workspaceDir, "artifacts", taskId),
3486
3362
  logRoot: runtime.logsDir,
3487
- sessionPath: resolve11(runtime.sessionDir, "session.json"),
3488
- sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3363
+ sessionPath: resolve10(runtime.sessionDir, "session.json"),
3364
+ sessionLogPath: resolve10(runtime.logsDir, "agent-stdout.log"),
3489
3365
  pid: process.pid,
3490
3366
  errorText: result.stderr ? result.stderr.trim() : `Agent runtime command failed (${result.exitCode})`
3491
3367
  });
@@ -3502,10 +3378,10 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3502
3378
  startedAt: createdAt,
3503
3379
  completedAt,
3504
3380
  worktreePath: runtime.workspaceDir,
3505
- artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3381
+ artifactRoot: resolve10(runtime.workspaceDir, "artifacts", taskId),
3506
3382
  logRoot: runtime.logsDir,
3507
- sessionPath: resolve11(runtime.sessionDir, "session.json"),
3508
- sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3383
+ sessionPath: resolve10(runtime.sessionDir, "session.json"),
3384
+ sessionLogPath: resolve10(runtime.logsDir, "agent-stdout.log"),
3509
3385
  pid: process.pid
3510
3386
  });
3511
3387
  return {
@@ -3579,17 +3455,17 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3579
3455
  import {
3580
3456
  chmodSync,
3581
3457
  copyFileSync as copyFileSync2,
3582
- existsSync as existsSync7,
3458
+ existsSync as existsSync6,
3583
3459
  mkdirSync as mkdirSync5,
3584
3460
  readdirSync,
3585
3461
  readlinkSync,
3586
- rmSync as rmSync4,
3462
+ rmSync as rmSync3,
3587
3463
  statSync,
3588
3464
  symlinkSync,
3589
3465
  unlinkSync
3590
3466
  } from "fs";
3591
- import { homedir as homedir4 } from "os";
3592
- import { resolve as resolve12 } from "path";
3467
+ import { homedir as homedir3 } from "os";
3468
+ import { resolve as resolve11 } from "path";
3593
3469
  import { buildBinary as buildBinary2 } from "@rig/runtime/control-plane/runtime/isolation";
3594
3470
  import {
3595
3471
  computeRuntimeImageFingerprint,
@@ -3608,13 +3484,13 @@ async function runQuietBinaryProbe(binaryPath, args, cwd) {
3608
3484
 
3609
3485
  // packages/cli/src/commands/dist.ts
3610
3486
  function collectRigValidatorBuildTargets(input) {
3611
- const validatorsRoot = resolve12(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3612
- if (!existsSync7(validatorsRoot))
3487
+ const validatorsRoot = resolve11(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3488
+ if (!existsSync6(validatorsRoot))
3613
3489
  return [];
3614
3490
  const targets = [];
3615
3491
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
3616
3492
  for (const category of categories) {
3617
- const categoryDir = resolve12(validatorsRoot, category.name);
3493
+ const categoryDir = resolve11(validatorsRoot, category.name);
3618
3494
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
3619
3495
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
3620
3496
  continue;
@@ -3623,7 +3499,7 @@ function collectRigValidatorBuildTargets(input) {
3623
3499
  continue;
3624
3500
  targets.push({
3625
3501
  source: `packages/runtime/src/control-plane/validators/${category.name}/${entry.name}`,
3626
- dest: resolve12(input.imageDir, `bin/validators/${category.name}-${check}`),
3502
+ dest: resolve11(input.imageDir, `bin/validators/${category.name}-${check}`),
3627
3503
  cwd: input.hostProjectRoot
3628
3504
  });
3629
3505
  }
@@ -3632,16 +3508,16 @@ function collectRigValidatorBuildTargets(input) {
3632
3508
  }
3633
3509
  async function findLatestDistBinary(projectRoot) {
3634
3510
  const distRoot = resolveControlPlaneHostDistDir(projectRoot);
3635
- if (!existsSync7(distRoot)) {
3511
+ if (!existsSync6(distRoot)) {
3636
3512
  return null;
3637
3513
  }
3638
3514
  const entries = readdirSync(distRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("rig-")).map((entry) => ({
3639
3515
  name: entry.name,
3640
- mtimeMs: statSync(resolve12(distRoot, entry.name)).mtimeMs
3516
+ mtimeMs: statSync(resolve11(distRoot, entry.name)).mtimeMs
3641
3517
  })).sort((a, b) => b.mtimeMs - a.mtimeMs || b.name.localeCompare(a.name));
3642
3518
  for (const { name } of entries) {
3643
- const candidate = resolve12(distRoot, name, "bin", "rig");
3644
- if (existsSync7(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3519
+ const candidate = resolve11(distRoot, name, "bin", "rig");
3520
+ if (existsSync6(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3645
3521
  return candidate;
3646
3522
  }
3647
3523
  }
@@ -3653,7 +3529,7 @@ async function isRunnableRigBinary(binaryPath, projectRoot) {
3653
3529
  async function runDistDoctor(projectRoot) {
3654
3530
  const bunPath = Bun.which("bun");
3655
3531
  const rigPath = Bun.which("rig");
3656
- const userBinDir = resolve12(homedir4(), ".local/bin");
3532
+ const userBinDir = resolve11(homedir3(), ".local/bin");
3657
3533
  const userBinInPath = (process.env.PATH || "").split(":").filter(Boolean).includes(userBinDir);
3658
3534
  let rigRunnable = false;
3659
3535
  if (rigPath) {
@@ -3701,15 +3577,15 @@ async function executeDist(context, args) {
3701
3577
  let source = await findLatestDistBinary(context.projectRoot);
3702
3578
  let buildDir = null;
3703
3579
  if (!source) {
3704
- buildDir = resolve12(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3580
+ buildDir = resolve11(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3705
3581
  await context.runCommand(["bun", "run", "packages/cli/bin/build-rig-binaries.ts", "--output-dir", buildDir]);
3706
- source = resolve12(buildDir, "bin", "rig");
3582
+ source = resolve11(buildDir, "bin", "rig");
3707
3583
  }
3708
- if (!existsSync7(source)) {
3584
+ if (!existsSync6(source)) {
3709
3585
  throw new CliError2(`Unable to locate rig binary at ${source}.`, 2);
3710
3586
  }
3711
- const installedPath = resolve12(installDir, "rig");
3712
- if (existsSync7(installedPath)) {
3587
+ const installedPath = resolve11(installDir, "rig");
3588
+ if (existsSync6(installedPath)) {
3713
3589
  unlinkSync(installedPath);
3714
3590
  }
3715
3591
  copyFileSync2(source, installedPath);
@@ -3751,22 +3627,22 @@ async function executeDist(context, args) {
3751
3627
  requireNoExtraArgs(rest, "bun run rig dist rebuild-agent");
3752
3628
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
3753
3629
  const currentId = computeRuntimeImageId(fp);
3754
- const imagesDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3630
+ const imagesDir = resolve11(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3755
3631
  mkdirSync5(imagesDir, { recursive: true });
3756
3632
  let pruned = 0;
3757
3633
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
3758
3634
  if (entry.isDirectory() && entry.name !== currentId) {
3759
- rmSync4(resolve12(imagesDir, entry.name), { recursive: true, force: true });
3635
+ rmSync3(resolve11(imagesDir, entry.name), { recursive: true, force: true });
3760
3636
  pruned++;
3761
3637
  }
3762
3638
  }
3763
3639
  if (pruned > 0 && context.outputMode === "text") {
3764
3640
  console.log(`Pruned ${pruned} stale image(s).`);
3765
3641
  }
3766
- const imageDir = resolve12(imagesDir, currentId);
3767
- mkdirSync5(resolve12(imageDir, "bin/hooks"), { recursive: true });
3768
- mkdirSync5(resolve12(imageDir, "bin/plugins"), { recursive: true });
3769
- mkdirSync5(resolve12(imageDir, "bin/validators"), { recursive: true });
3642
+ const imageDir = resolve11(imagesDir, currentId);
3643
+ mkdirSync5(resolve11(imageDir, "bin/hooks"), { recursive: true });
3644
+ mkdirSync5(resolve11(imageDir, "bin/plugins"), { recursive: true });
3645
+ mkdirSync5(resolve11(imageDir, "bin/validators"), { recursive: true });
3770
3646
  const hookNames = [
3771
3647
  "scope-guard",
3772
3648
  "import-guard",
@@ -3780,25 +3656,25 @@ async function executeDist(context, args) {
3780
3656
  ];
3781
3657
  const targets = [];
3782
3658
  const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || context.projectRoot;
3783
- targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve12(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3784
- targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3659
+ targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve11(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3660
+ targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve11(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3785
3661
  for (const hookName of hookNames) {
3786
3662
  const src = `packages/runtime/src/control-plane/hooks/${hookName}.ts`;
3787
- targets.push({ source: src, dest: resolve12(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3788
- targets.push({ source: src, dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3663
+ targets.push({ source: src, dest: resolve11(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3664
+ targets.push({ source: src, dest: resolve11(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3789
3665
  }
3790
- const pluginsDir = resolve12(context.projectRoot, "rig/plugins");
3791
- const binPluginsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3792
- const validatorsRoot = resolve12(hostProjectRoot, "packages/runtime/src/control-plane/validators");
3793
- const binValidatorsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
3666
+ const pluginsDir = resolve11(context.projectRoot, "rig/plugins");
3667
+ const binPluginsDir = resolve11(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3668
+ const validatorsRoot = resolve11(hostProjectRoot, "packages/runtime/src/control-plane/validators");
3669
+ const binValidatorsDir = resolve11(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
3794
3670
  mkdirSync5(binPluginsDir, { recursive: true });
3795
3671
  mkdirSync5(binValidatorsDir, { recursive: true });
3796
- if (existsSync7(pluginsDir)) {
3672
+ if (existsSync6(pluginsDir)) {
3797
3673
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
3798
3674
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
3799
3675
  if (!m)
3800
3676
  continue;
3801
- targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve12(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
3677
+ targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve11(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
3802
3678
  }
3803
3679
  }
3804
3680
  targets.push(...collectRigValidatorBuildTargets({ contextProjectRoot: context.projectRoot, hostProjectRoot, imageDir }));
@@ -3809,17 +3685,17 @@ async function executeDist(context, args) {
3809
3685
  const isValidator = dest.includes("/bin/validators/");
3810
3686
  await buildBinary2(source, dest, cwd, isValidator ? { AGENT_BUN_PATH: Bun.which("bun") || "bun" } : { AGENT_PROJECT_ROOT: context.projectRoot });
3811
3687
  }
3812
- if (existsSync7(pluginsDir)) {
3688
+ if (existsSync6(pluginsDir)) {
3813
3689
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
3814
3690
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
3815
3691
  if (!m)
3816
3692
  continue;
3817
3693
  const pluginName = m[1];
3818
- const imageBin = resolve12(imageDir, `bin/plugins/${pluginName}`);
3694
+ const imageBin = resolve11(imageDir, `bin/plugins/${pluginName}`);
3819
3695
  if (!pluginName)
3820
3696
  continue;
3821
- const symlinkPath = resolve12(binPluginsDir, pluginName);
3822
- if (existsSync7(imageBin)) {
3697
+ const symlinkPath = resolve11(binPluginsDir, pluginName);
3698
+ if (existsSync6(imageBin)) {
3823
3699
  try {
3824
3700
  unlinkSync(symlinkPath);
3825
3701
  } catch {}
@@ -3827,10 +3703,10 @@ async function executeDist(context, args) {
3827
3703
  }
3828
3704
  }
3829
3705
  }
3830
- if (existsSync7(validatorsRoot)) {
3706
+ if (existsSync6(validatorsRoot)) {
3831
3707
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
3832
3708
  for (const category of categories) {
3833
- const categoryDir = resolve12(validatorsRoot, category.name);
3709
+ const categoryDir = resolve11(validatorsRoot, category.name);
3834
3710
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
3835
3711
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
3836
3712
  continue;
@@ -3838,9 +3714,9 @@ async function executeDist(context, args) {
3838
3714
  if (!check || check === "index" || check === "shared")
3839
3715
  continue;
3840
3716
  const validatorName = `${category.name}-${check}`;
3841
- const imageBin = resolve12(imageDir, `bin/validators/${validatorName}`);
3842
- const symlinkPath = resolve12(binValidatorsDir, validatorName);
3843
- if (existsSync7(imageBin)) {
3717
+ const imageBin = resolve11(imageDir, `bin/validators/${validatorName}`);
3718
+ const symlinkPath = resolve11(binValidatorsDir, validatorName);
3719
+ if (existsSync6(imageBin)) {
3844
3720
  try {
3845
3721
  unlinkSync(symlinkPath);
3846
3722
  } catch {}
@@ -3849,18 +3725,18 @@ async function executeDist(context, args) {
3849
3725
  }
3850
3726
  }
3851
3727
  }
3852
- const agentsDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
3853
- if (existsSync7(agentsDir)) {
3728
+ const agentsDir = resolve11(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
3729
+ if (existsSync6(agentsDir)) {
3854
3730
  let relinkCount = 0;
3855
3731
  for (const agentEntry of readdirSync(agentsDir, { withFileTypes: true })) {
3856
3732
  if (!agentEntry.isDirectory())
3857
3733
  continue;
3858
- const agentBinDir = resolve12(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
3859
- if (!existsSync7(agentBinDir))
3734
+ const agentBinDir = resolve11(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
3735
+ if (!existsSync6(agentBinDir))
3860
3736
  continue;
3861
3737
  const walkDir = (dir) => {
3862
3738
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3863
- const fullPath = resolve12(dir, entry.name);
3739
+ const fullPath = resolve11(dir, entry.name);
3864
3740
  if (entry.isDirectory()) {
3865
3741
  walkDir(fullPath);
3866
3742
  } else if (entry.isSymbolicLink()) {
@@ -3894,7 +3770,7 @@ async function executeDist(context, args) {
3894
3770
 
3895
3771
  // packages/cli/src/commands/inbox.ts
3896
3772
  import { writeFileSync as writeFileSync4 } from "fs";
3897
- import { resolve as resolve13 } from "path";
3773
+ import { resolve as resolve12 } from "path";
3898
3774
  import {
3899
3775
  listAuthorityRuns,
3900
3776
  readJsonlFile as readJsonlFile3,
@@ -3911,7 +3787,7 @@ async function executeInbox(context, args) {
3911
3787
  pending = task.rest;
3912
3788
  requireNoExtraArgs(pending, "bun run rig inbox approvals [--run <id>] [--task <id>]");
3913
3789
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
3914
- const approvals = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
3790
+ const approvals = runs.flatMap((entry) => readJsonlFile3(resolve12(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
3915
3791
  runId: entry.runId,
3916
3792
  record
3917
3793
  })));
@@ -3939,7 +3815,7 @@ async function executeInbox(context, args) {
3939
3815
  if (decision.value !== "approve" && decision.value !== "reject") {
3940
3816
  throw new CliError2("decision must be approve or reject.");
3941
3817
  }
3942
- const approvalsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
3818
+ const approvalsPath = resolve12(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
3943
3819
  const approvals = readJsonlFile3(approvalsPath);
3944
3820
  const resolvedAt = new Date().toISOString();
3945
3821
  const next = approvals.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", decision: decision.value, note: note3.value ?? null, resolvedAt } : entry);
@@ -3956,7 +3832,7 @@ async function executeInbox(context, args) {
3956
3832
  pending = task.rest;
3957
3833
  requireNoExtraArgs(pending, "bun run rig inbox inputs [--run <id>] [--task <id>]");
3958
3834
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
3959
- const requests = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
3835
+ const requests = runs.flatMap((entry) => readJsonlFile3(resolve12(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
3960
3836
  runId: entry.runId,
3961
3837
  record
3962
3838
  })));
@@ -3998,7 +3874,7 @@ async function executeInbox(context, args) {
3998
3874
  const [key, ...restValue] = entry.split("=");
3999
3875
  return [key, restValue.join("=")];
4000
3876
  }));
4001
- const requestsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
3877
+ const requestsPath = resolve12(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
4002
3878
  const requests = readJsonlFile3(requestsPath);
4003
3879
  const resolvedAt = new Date().toISOString();
4004
3880
  const next = requests.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", answers: parsedAnswers, respondedAt: resolvedAt, resolvedAt } : entry);
@@ -4011,13 +3887,190 @@ async function executeInbox(context, args) {
4011
3887
  throw new CliError2(`Unknown inbox command: ${command}`);
4012
3888
  }
4013
3889
  }
4014
-
4015
- // packages/cli/src/commands/init.ts
4016
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4017
- import { spawnSync } from "child_process";
4018
- import { resolve as resolve16 } from "path";
4019
- import { buildRigInitConfigSource } from "@rig/core";
4020
- import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
3890
+
3891
+ // packages/cli/src/commands/init.ts
3892
+ import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3893
+ import { spawnSync } from "child_process";
3894
+ import { resolve as resolve16 } from "path";
3895
+ import { buildRigInitConfigSource } from "@rig/core";
3896
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
3897
+
3898
+ // packages/cli/src/commands/_pi-install.ts
3899
+ import { existsSync as existsSync7, readFileSync as readFileSync4, rmSync as rmSync4 } from "fs";
3900
+ import { homedir as homedir4 } from "os";
3901
+ import { resolve as resolve13 } from "path";
3902
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
3903
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
3904
+ var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
3905
+ export { default } from '@rig/pi-rig';
3906
+ `;
3907
+ async function defaultCommandRunner(command, options = {}) {
3908
+ const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
3909
+ const [stdout, stderr, exitCode] = await Promise.all([
3910
+ new Response(proc.stdout).text(),
3911
+ new Response(proc.stderr).text(),
3912
+ proc.exited
3913
+ ]);
3914
+ return { exitCode, stdout, stderr };
3915
+ }
3916
+ function resolvePiRigExtensionPath(homeDir) {
3917
+ return resolve13(homeDir, ".pi", "agent", "extensions", "pi-rig");
3918
+ }
3919
+ function resolvePiRigPackageSource(projectRoot, exists = existsSync7) {
3920
+ const localPackage = resolve13(projectRoot, "packages", "pi-rig");
3921
+ if (exists(resolve13(localPackage, "package.json")))
3922
+ return localPackage;
3923
+ return `npm:${PI_RIG_PACKAGE_NAME}`;
3924
+ }
3925
+ function resolvePiHomeDir(inputHomeDir) {
3926
+ return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir4();
3927
+ }
3928
+ function piListContainsPiRig(output) {
3929
+ return output.split(/\r?\n/).some((line) => {
3930
+ const normalized = line.trim();
3931
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
3932
+ });
3933
+ }
3934
+ async function safeRun(runner, command, options) {
3935
+ try {
3936
+ return await runner(command, options);
3937
+ } catch (error) {
3938
+ return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
3939
+ }
3940
+ }
3941
+ function splitInstallCommand(value) {
3942
+ return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
3943
+ }
3944
+ async function ensurePiBinaryAvailable(input) {
3945
+ const current = await safeRun(input.runner, ["pi", "--version"]);
3946
+ if (current.exitCode === 0) {
3947
+ const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
3948
+ if (updateCommand) {
3949
+ const parts2 = splitInstallCommand(updateCommand);
3950
+ if (parts2.length > 0)
3951
+ await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3952
+ }
3953
+ return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
3954
+ }
3955
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
3956
+ const parts = splitInstallCommand(installCommand);
3957
+ if (parts.length === 0) {
3958
+ return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
3959
+ }
3960
+ const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3961
+ if (install.exitCode !== 0) {
3962
+ return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
3963
+ }
3964
+ const next = await safeRun(input.runner, ["pi", "--version"]);
3965
+ return {
3966
+ ok: next.exitCode === 0,
3967
+ installedOrUpdated: true,
3968
+ detail: (next.stdout || next.stderr).trim() || undefined,
3969
+ ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
3970
+ };
3971
+ }
3972
+ function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync7) {
3973
+ const extensionPath = resolvePiRigExtensionPath(homeDir);
3974
+ const indexPath = resolve13(extensionPath, "index.ts");
3975
+ if (!exists(indexPath))
3976
+ return;
3977
+ try {
3978
+ const content = readFileSync4(indexPath, "utf8");
3979
+ if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
3980
+ rmSync4(extensionPath, { recursive: true, force: true });
3981
+ }
3982
+ } catch {}
3983
+ }
3984
+ async function checkPiRigInstall(input = {}) {
3985
+ const home = resolvePiHomeDir(input.homeDir);
3986
+ const extensionPath = resolvePiRigExtensionPath(home);
3987
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
3988
+ return {
3989
+ extensionPath,
3990
+ pi: { ok: true, label: "pi", detail: "fake-pi" },
3991
+ piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
3992
+ };
3993
+ }
3994
+ const exists = input.exists ?? existsSync7;
3995
+ const runner = input.commandRunner ?? defaultCommandRunner;
3996
+ const piResult = await safeRun(runner, ["pi", "--version"]);
3997
+ const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
3998
+ const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
3999
+ ${piListResult.stderr}`);
4000
+ const legacyBridge = exists(resolve13(extensionPath, "index.ts"));
4001
+ const hasPiRig = listedPiRig;
4002
+ return {
4003
+ extensionPath,
4004
+ pi: {
4005
+ ok: piResult.exitCode === 0,
4006
+ label: "pi",
4007
+ detail: (piResult.stdout || piResult.stderr).trim() || undefined,
4008
+ hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
4009
+ },
4010
+ piRig: {
4011
+ ok: hasPiRig,
4012
+ label: "pi-rig global extension",
4013
+ detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
4014
+ hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
4015
+ }
4016
+ };
4017
+ }
4018
+ async function ensurePiRigInstalled(input) {
4019
+ const home = resolvePiHomeDir(input.homeDir);
4020
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
4021
+ const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
4022
+ return { ...status2, installedPath: status2.extensionPath };
4023
+ }
4024
+ const runner = input.commandRunner ?? defaultCommandRunner;
4025
+ const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
4026
+ const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
4027
+ extensionPath: resolvePiRigExtensionPath(home),
4028
+ pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
4029
+ piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
4030
+ };
4031
+ if (!piAvailable.ok) {
4032
+ throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
4033
+ }
4034
+ const packageSource = resolvePiRigPackageSource(input.projectRoot);
4035
+ removeManagedLegacyPiRigBridge(home);
4036
+ const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
4037
+ if (install.exitCode !== 0) {
4038
+ throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
4039
+ }
4040
+ const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
4041
+ return { ...next, installedPath: packageSource };
4042
+ }
4043
+ async function ensureRemotePiRigInstalled(input) {
4044
+ const payload = await input.requestJson("/api/pi-rig/install", {
4045
+ method: "POST",
4046
+ headers: { "content-type": "application/json" },
4047
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
4048
+ });
4049
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
4050
+ const piOk = record.piOk === true || record.ok === true;
4051
+ const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
4052
+ const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
4053
+ return {
4054
+ remote: true,
4055
+ extensionPath,
4056
+ pi: {
4057
+ ok: piOk,
4058
+ label: "pi",
4059
+ detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
4060
+ hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
4061
+ },
4062
+ piRig: {
4063
+ ok: piRigOk,
4064
+ label: "pi-rig global extension",
4065
+ detail: extensionPath,
4066
+ hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
4067
+ }
4068
+ };
4069
+ }
4070
+ async function buildPiSetupChecks(input = {}) {
4071
+ const status = await checkPiRigInstall(input);
4072
+ return [status.pi, status.piRig];
4073
+ }
4021
4074
 
4022
4075
  // packages/cli/src/commands/_snapshot-upload.ts
4023
4076
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -6385,8 +6438,477 @@ async function promptForTaskSelection(question) {
6385
6438
  }
6386
6439
  }
6387
6440
 
6388
- // packages/cli/src/commands/_operator-view.ts
6441
+ // packages/cli/src/commands/_pi-frontend.ts
6442
+ import { mkdtempSync, rmSync as rmSync5 } from "fs";
6443
+ import { tmpdir } from "os";
6444
+ import { join as join2 } from "path";
6445
+ import { main as runPiMain } from "@earendil-works/pi-coding-agent";
6446
+
6447
+ // packages/cli/src/commands/_pi-worker-bridge-extension.ts
6389
6448
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6449
+ var MAX_TRANSCRIPT_LINES = 120;
6450
+ function recordOf(value) {
6451
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6452
+ }
6453
+ function asText(value) {
6454
+ if (typeof value === "string")
6455
+ return value;
6456
+ if (value === null || value === undefined)
6457
+ return "";
6458
+ if (typeof value === "number" || typeof value === "boolean")
6459
+ return String(value);
6460
+ try {
6461
+ return JSON.stringify(value);
6462
+ } catch {
6463
+ return String(value);
6464
+ }
6465
+ }
6466
+ function textFromContent(content) {
6467
+ if (typeof content === "string")
6468
+ return content;
6469
+ if (!Array.isArray(content))
6470
+ return asText(content);
6471
+ return content.flatMap((part) => {
6472
+ const item = recordOf(part);
6473
+ if (!item)
6474
+ return [];
6475
+ if (typeof item.text === "string")
6476
+ return [item.text];
6477
+ if (typeof item.content === "string")
6478
+ return [item.content];
6479
+ if (item.type === "toolCall")
6480
+ return [`\u23FA ${String(item.name ?? "tool")} ${asText(item.arguments ?? "")}`.trim()];
6481
+ if (item.type === "toolResult")
6482
+ return [`\u21B3 ${asText(item.content ?? item.result ?? "")}`.trim()];
6483
+ return [];
6484
+ }).join(`
6485
+ `);
6486
+ }
6487
+ function appendTranscript(state, label, text2) {
6488
+ const trimmed = text2.trimEnd();
6489
+ if (!trimmed)
6490
+ return;
6491
+ const lines = trimmed.split(/\r?\n/);
6492
+ state.transcript.push(`${label}: ${lines[0] ?? ""}`);
6493
+ for (const line of lines.slice(1))
6494
+ state.transcript.push(` ${line}`);
6495
+ if (state.transcript.length > MAX_TRANSCRIPT_LINES) {
6496
+ state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
6497
+ }
6498
+ }
6499
+ function parseExtensionUiRequest(value) {
6500
+ const request = recordOf(value) ?? {};
6501
+ const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
6502
+ const method = String(request.method ?? request.type ?? "input");
6503
+ const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
6504
+ const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
6505
+ const options = rawOptions.map((option) => {
6506
+ const record = recordOf(option);
6507
+ return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
6508
+ }).filter(Boolean);
6509
+ return { requestId, method, prompt, options };
6510
+ }
6511
+ function renderBridgeWidget(state) {
6512
+ const statusParts = [
6513
+ state.wsConnected ? "live WS" : "WS pending",
6514
+ state.status,
6515
+ state.model,
6516
+ state.cwd
6517
+ ].filter(Boolean);
6518
+ const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
6519
+ if (state.activity)
6520
+ lines.push(state.activity);
6521
+ if (state.commands.length > 0) {
6522
+ lines.push(`Worker commands: ${state.commands.slice(0, 10).join(", ")}${state.commands.length > 10 ? ", \u2026" : ""}`);
6523
+ }
6524
+ lines.push("");
6525
+ if (state.transcript.length > 0) {
6526
+ lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
6527
+ } else {
6528
+ lines.push("Waiting for worker Pi daemon transcript\u2026");
6529
+ }
6530
+ if (state.pendingUi) {
6531
+ lines.push("");
6532
+ lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
6533
+ lines.push(state.pendingUi.prompt);
6534
+ state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
6535
+ lines.push("Reply in the Pi editor. /cancel cancels this request.");
6536
+ }
6537
+ return lines;
6538
+ }
6539
+ function updatePiUi(ctx, state) {
6540
+ ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
6541
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
6542
+ ctx.ui.setWorkingVisible(false);
6543
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
6544
+ }
6545
+ function applyStatus(state, payload) {
6546
+ const status = recordOf(payload.status) ?? payload;
6547
+ state.streaming = status.isStreaming === true || status.isCompacting === true || status.isBashRunning === true;
6548
+ state.cwd = typeof status.cwd === "string" ? status.cwd : state.cwd;
6549
+ state.model = typeof status.model === "string" ? status.model : state.model;
6550
+ const pending = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
6551
+ state.status = `${state.streaming ? "streaming" : "idle"}${pending ? ` \xB7 ${pending} queued` : ""}`;
6552
+ }
6553
+ function applyMessage(state, message2) {
6554
+ const record = recordOf(message2);
6555
+ if (!record)
6556
+ return;
6557
+ const role = String(record.role ?? "system");
6558
+ const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
6559
+ appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
6560
+ }
6561
+ function applyPiEvent(state, eventValue) {
6562
+ const event = recordOf(eventValue);
6563
+ if (!event)
6564
+ return;
6565
+ const type = String(event.type ?? "event");
6566
+ if (type === "agent_start") {
6567
+ state.streaming = true;
6568
+ state.status = "streaming";
6569
+ return;
6570
+ }
6571
+ if (type === "agent_end") {
6572
+ state.streaming = false;
6573
+ state.status = "idle";
6574
+ appendTranscript(state, "System", "Agent turn complete.");
6575
+ return;
6576
+ }
6577
+ if (type === "message_start" || type === "message_end" || type === "turn_end") {
6578
+ applyMessage(state, event.message);
6579
+ return;
6580
+ }
6581
+ if (type === "message_update") {
6582
+ const assistantEvent = recordOf(event.assistantMessageEvent);
6583
+ const delta = typeof assistantEvent?.delta === "string" ? assistantEvent.delta : typeof assistantEvent?.text === "string" ? assistantEvent.text : "";
6584
+ if (delta)
6585
+ appendTranscript(state, assistantEvent?.type === "thinking_delta" ? "Thinking" : "Pi", delta);
6586
+ return;
6587
+ }
6588
+ if (type === "tool_execution_start") {
6589
+ appendTranscript(state, "Tool", `${String(event.toolName ?? "tool")} ${asText(event.args ?? "")}`.trim());
6590
+ return;
6591
+ }
6592
+ if (type === "tool_execution_update") {
6593
+ appendTranscript(state, "Tool", asText(event.partialResult ?? ""));
6594
+ return;
6595
+ }
6596
+ if (type === "tool_execution_end") {
6597
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
6598
+ return;
6599
+ }
6600
+ if (type === "queue_update") {
6601
+ const steering = Array.isArray(event.steering) ? event.steering.length : 0;
6602
+ const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
6603
+ state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
6604
+ }
6605
+ }
6606
+ function applyUiEvent(state, value) {
6607
+ const event = recordOf(value);
6608
+ if (!event)
6609
+ return;
6610
+ const type = String(event.type ?? "ui");
6611
+ if (type === "shell.start")
6612
+ appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
6613
+ else if (type === "shell.chunk")
6614
+ appendTranscript(state, "Tool", asText(event.chunk));
6615
+ else if (type === "shell.end")
6616
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
6617
+ else
6618
+ appendTranscript(state, "System", `${type}: ${asText(event)}`);
6619
+ }
6620
+ function applyEnvelope(state, envelopeValue) {
6621
+ const envelope = recordOf(envelopeValue);
6622
+ if (!envelope)
6623
+ return;
6624
+ const type = String(envelope.type ?? "");
6625
+ if (type === "ready") {
6626
+ const metadata = recordOf(envelope.metadata);
6627
+ state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
6628
+ state.status = "worker Pi daemon ready";
6629
+ appendTranscript(state, "System", "Connected to worker Pi daemon.");
6630
+ } else if (type === "status.update") {
6631
+ applyStatus(state, envelope);
6632
+ } else if (type === "activity.update") {
6633
+ const activity = recordOf(envelope.activity);
6634
+ state.activity = [activity?.label, activity?.detail].map(asText).filter(Boolean).join(" \u2014 ");
6635
+ } else if (type === "extension_ui_request") {
6636
+ state.pendingUi = parseExtensionUiRequest(envelope.request);
6637
+ appendTranscript(state, "System", `Extension UI request: ${state.pendingUi.prompt}`);
6638
+ } else if (type === "pi.ui_event") {
6639
+ applyUiEvent(state, envelope.event);
6640
+ } else if (type === "pi.event") {
6641
+ applyPiEvent(state, envelope.event);
6642
+ } else if (type === "error") {
6643
+ appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
6644
+ }
6645
+ }
6646
+ async function waitForWorkerReady(options, ctx, state) {
6647
+ while (true) {
6648
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
6649
+ ready: false,
6650
+ status: error instanceof Error ? error.message : String(error),
6651
+ retryAfterMs: 1000
6652
+ }));
6653
+ if (session.ready === false) {
6654
+ const status = String(session.status ?? "starting");
6655
+ state.status = `waiting for worker Pi daemon \xB7 ${status}`;
6656
+ updatePiUi(ctx, state);
6657
+ if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
6658
+ appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
6659
+ return false;
6660
+ }
6661
+ await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
6662
+ continue;
6663
+ }
6664
+ const sessionRecord = recordOf(session) ?? {};
6665
+ applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
6666
+ updatePiUi(ctx, state);
6667
+ return true;
6668
+ }
6669
+ }
6670
+ function parseWsPayload(message2) {
6671
+ if (typeof message2.data === "string")
6672
+ return JSON.parse(message2.data);
6673
+ return JSON.parse(Buffer.from(message2.data).toString("utf8"));
6674
+ }
6675
+ async function connectWorkerStream(options, ctx, state) {
6676
+ const ready = await waitForWorkerReady(options, ctx, state);
6677
+ if (!ready)
6678
+ return;
6679
+ let catchupDone = false;
6680
+ const buffered = [];
6681
+ const wsUrl = await buildRunPiEventsWebSocketUrl(options.context, options.runId);
6682
+ const socket = new WebSocket(wsUrl);
6683
+ const closePromise = new Promise((resolve19) => {
6684
+ socket.onopen = () => {
6685
+ state.wsConnected = true;
6686
+ state.status = "live worker Pi WebSocket connected";
6687
+ updatePiUi(ctx, state);
6688
+ };
6689
+ socket.onmessage = (message2) => {
6690
+ try {
6691
+ const payload = parseWsPayload(message2);
6692
+ if (!catchupDone)
6693
+ buffered.push(payload);
6694
+ else {
6695
+ applyEnvelope(state, payload);
6696
+ updatePiUi(ctx, state);
6697
+ }
6698
+ } catch (error) {
6699
+ appendTranscript(state, "Error", `Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
6700
+ updatePiUi(ctx, state);
6701
+ }
6702
+ };
6703
+ socket.onerror = () => socket.close();
6704
+ socket.onclose = () => {
6705
+ state.wsConnected = false;
6706
+ state.status = "worker Pi WebSocket disconnected";
6707
+ updatePiUi(ctx, state);
6708
+ resolve19();
6709
+ };
6710
+ });
6711
+ try {
6712
+ const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
6713
+ getRunPiMessagesViaServer(options.context, options.runId),
6714
+ getRunPiStatusViaServer(options.context, options.runId),
6715
+ getRunPiCommandsViaServer(options.context, options.runId)
6716
+ ]);
6717
+ const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
6718
+ for (const message2 of messages)
6719
+ applyMessage(state, message2);
6720
+ applyStatus(state, statusPayload);
6721
+ const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
6722
+ state.commands = commands.flatMap((command) => {
6723
+ const record = recordOf(command);
6724
+ return typeof record?.name === "string" ? [`/${record.name}`] : [];
6725
+ });
6726
+ catchupDone = true;
6727
+ for (const payload of buffered.splice(0))
6728
+ applyEnvelope(state, payload);
6729
+ updatePiUi(ctx, state);
6730
+ } catch (error) {
6731
+ appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
6732
+ catchupDone = true;
6733
+ updatePiUi(ctx, state);
6734
+ }
6735
+ await closePromise;
6736
+ }
6737
+ async function answerPendingUi(options, state, line) {
6738
+ const pending = state.pendingUi;
6739
+ if (!pending)
6740
+ return false;
6741
+ if (line === "/cancel") {
6742
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { cancelled: true });
6743
+ } else if (pending.method === "confirm") {
6744
+ const confirmed = /^(y|yes|true|1)$/i.test(line);
6745
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: confirmed, confirmed });
6746
+ } else if (pending.options.length > 0 && /^\d+$/.test(line)) {
6747
+ const selected = pending.options[Math.max(0, Number(line) - 1)] ?? line;
6748
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: selected });
6749
+ } else {
6750
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: line });
6751
+ }
6752
+ appendTranscript(state, "System", `Responded to extension UI request ${pending.requestId}.`);
6753
+ state.pendingUi = null;
6754
+ return true;
6755
+ }
6756
+ async function routeInput(options, ctx, state, line) {
6757
+ const text2 = line.trim();
6758
+ if (!text2)
6759
+ return;
6760
+ if (await answerPendingUi(options, state, text2)) {
6761
+ updatePiUi(ctx, state);
6762
+ return;
6763
+ }
6764
+ if (text2 === "/detach" || text2 === "/quit" || text2 === "/q") {
6765
+ appendTranscript(state, "System", "Detached locally; worker Pi daemon continues.");
6766
+ updatePiUi(ctx, state);
6767
+ ctx.shutdown();
6768
+ return;
6769
+ }
6770
+ if (text2 === "/stop") {
6771
+ await abortRunPiViaServer(options.context, options.runId);
6772
+ appendTranscript(state, "System", "Stop requested for worker Pi daemon.");
6773
+ updatePiUi(ctx, state);
6774
+ ctx.shutdown();
6775
+ return;
6776
+ }
6777
+ if (text2.startsWith("!")) {
6778
+ appendTranscript(state, "You", text2);
6779
+ await sendRunPiShellViaServer(options.context, options.runId, text2);
6780
+ } else if (text2.startsWith("/")) {
6781
+ appendTranscript(state, "You", text2);
6782
+ const result = await runRunPiCommandViaServer(options.context, options.runId, text2);
6783
+ const message2 = typeof result.message === "string" ? result.message : "worker command accepted";
6784
+ appendTranscript(state, "System", message2);
6785
+ } else {
6786
+ appendTranscript(state, "You", text2);
6787
+ await sendRunPiPromptViaServer(options.context, options.runId, text2, state.streaming ? "steer" : undefined);
6788
+ }
6789
+ updatePiUi(ctx, state);
6790
+ }
6791
+ function createRigWorkerPiBridgeExtension(options) {
6792
+ return (pi) => {
6793
+ const state = {
6794
+ transcript: [],
6795
+ status: "starting worker Pi daemon bridge",
6796
+ activity: "",
6797
+ cwd: "",
6798
+ model: "",
6799
+ commands: [],
6800
+ streaming: false,
6801
+ pendingUi: null,
6802
+ wsConnected: false
6803
+ };
6804
+ if (options.initialMessageSent)
6805
+ appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
6806
+ pi.on("session_start", async (_event, ctx) => {
6807
+ updatePiUi(ctx, state);
6808
+ ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
6809
+ ctx.ui.onTerminalInput((data) => {
6810
+ if (data.includes("\x04")) {
6811
+ ctx.shutdown();
6812
+ return { consume: true };
6813
+ }
6814
+ if (!data.includes("\r") && !data.includes(`
6815
+ `))
6816
+ return;
6817
+ const inlineText = data.replace(/[\r\n]+/g, "").trim();
6818
+ const editorText = ctx.ui.getEditorText().trim();
6819
+ const text2 = [editorText, inlineText].filter(Boolean).join(" ").trim();
6820
+ if (!text2)
6821
+ return;
6822
+ ctx.ui.setEditorText("");
6823
+ routeInput(options, ctx, state, text2).catch((error) => {
6824
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
6825
+ updatePiUi(ctx, state);
6826
+ });
6827
+ return { consume: true };
6828
+ });
6829
+ connectWorkerStream(options, ctx, state).catch((error) => {
6830
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
6831
+ updatePiUi(ctx, state);
6832
+ });
6833
+ });
6834
+ pi.on("session_shutdown", () => {});
6835
+ };
6836
+ }
6837
+
6838
+ // packages/cli/src/commands/_pi-frontend.ts
6839
+ function setTemporaryEnv(updates) {
6840
+ const previous = new Map;
6841
+ for (const [key, value] of Object.entries(updates)) {
6842
+ previous.set(key, process.env[key]);
6843
+ process.env[key] = value;
6844
+ }
6845
+ return () => {
6846
+ for (const [key, value] of previous) {
6847
+ if (value === undefined)
6848
+ delete process.env[key];
6849
+ else
6850
+ process.env[key] = value;
6851
+ }
6852
+ };
6853
+ }
6854
+ async function attachRunBundledPiFrontend(context, input) {
6855
+ const tempRoot = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-"));
6856
+ const cwd = join2(tempRoot, "workspace");
6857
+ const agentDir = join2(tempRoot, "agent");
6858
+ const sessionDir = join2(tempRoot, "sessions");
6859
+ const previousCwd = process.cwd();
6860
+ const restoreEnv = setTemporaryEnv({
6861
+ PI_CODING_AGENT_DIR: agentDir,
6862
+ PI_CODING_AGENT_SESSION_DIR: sessionDir,
6863
+ PI_OFFLINE: "1",
6864
+ PI_SKIP_VERSION_CHECK: "1"
6865
+ });
6866
+ let detached = false;
6867
+ try {
6868
+ await Bun.$`mkdir -p ${cwd} ${agentDir} ${sessionDir}`.quiet();
6869
+ process.chdir(cwd);
6870
+ await runPiMain([
6871
+ "--offline",
6872
+ "--no-session",
6873
+ "--no-tools",
6874
+ "--no-builtin-tools",
6875
+ "--no-skills",
6876
+ "--no-prompt-templates",
6877
+ "--no-themes",
6878
+ "--no-context-files",
6879
+ "--no-approve"
6880
+ ], {
6881
+ extensionFactories: [
6882
+ createRigWorkerPiBridgeExtension({
6883
+ context,
6884
+ runId: input.runId,
6885
+ initialMessageSent: input.steered === true
6886
+ })
6887
+ ]
6888
+ });
6889
+ detached = true;
6890
+ } finally {
6891
+ process.chdir(previousCwd);
6892
+ restoreEnv();
6893
+ rmSync5(tempRoot, { recursive: true, force: true });
6894
+ }
6895
+ let run = { runId: input.runId, status: "unknown" };
6896
+ try {
6897
+ run = await getRunDetailsViaServer(context, input.runId);
6898
+ } catch {}
6899
+ return {
6900
+ run,
6901
+ logs: [],
6902
+ timeline: [],
6903
+ timelineCursor: null,
6904
+ steered: input.steered === true,
6905
+ detached,
6906
+ rendered: "actual bundled Pi frontend hosted Rig worker Pi bridge extension"
6907
+ };
6908
+ }
6909
+
6910
+ // packages/cli/src/commands/_operator-view.ts
6911
+ var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6390
6912
  function runStatusFromPayload(payload) {
6391
6913
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6392
6914
  return String(run.status ?? "unknown").toLowerCase();
@@ -6428,9 +6950,15 @@ async function readOperatorSnapshot(context, runId, options = {}) {
6428
6950
  async function attachRunOperatorView(context, input) {
6429
6951
  let steered = false;
6430
6952
  if (input.message?.trim()) {
6431
- await steerRunViaServer(context, input.runId, input.message.trim());
6953
+ await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
6432
6954
  steered = true;
6433
6955
  }
6956
+ if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
6957
+ return attachRunBundledPiFrontend(context, {
6958
+ runId: input.runId,
6959
+ steered
6960
+ });
6961
+ }
6434
6962
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
6435
6963
  let snapshot = await readOperatorSnapshot(context, input.runId);
6436
6964
  if (context.outputMode === "text") {
@@ -6438,7 +6966,7 @@ async function attachRunOperatorView(context, input) {
6438
6966
  surface.renderTimeline(snapshot.timeline);
6439
6967
  surface.renderLogs(snapshot.logs);
6440
6968
  if (steered)
6441
- surface.info("Steering message queued.");
6969
+ surface.info("Message submitted to worker Pi.");
6442
6970
  }
6443
6971
  let detached = false;
6444
6972
  let commandInput = null;
@@ -6457,7 +6985,7 @@ async function attachRunOperatorView(context, input) {
6457
6985
  }
6458
6986
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6459
6987
  let timelineCursor = snapshot.timelineCursor;
6460
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
6988
+ while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
6461
6989
  await Bun.sleep(pollMs);
6462
6990
  snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6463
6991
  timelineCursor = snapshot.timelineCursor;
@@ -6571,92 +7099,6 @@ function formatSubmittedRun(input) {
6571
7099
  `);
6572
7100
  }
6573
7101
 
6574
- // packages/cli/src/commands/_pi-session.ts
6575
- import { spawn as spawn2 } from "child_process";
6576
- function buildPiRigSessionEnv(input) {
6577
- return {
6578
- RIG_PROJECT_ROOT: input.projectRoot,
6579
- PROJECT_RIG_ROOT: input.projectRoot,
6580
- RIG_RUN_ID: input.runId,
6581
- RIG_SERVER_RUN_ID: input.runId,
6582
- RIG_RUNTIME_ADAPTER: "pi",
6583
- RIG_SERVER_URL: input.serverUrl,
6584
- RIG_SERVER_BASE_URL: input.serverUrl,
6585
- RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6586
- RIG_PI_OPERATOR_SESSION: "1",
6587
- ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6588
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6589
- };
6590
- }
6591
- function shellBinary(name) {
6592
- const explicit = process.env.RIG_PI_BINARY?.trim();
6593
- if (explicit)
6594
- return explicit;
6595
- return Bun.which(name) || name;
6596
- }
6597
- function buildPiRigSessionCommand(input) {
6598
- const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6599
- const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6600
- const initialCommand = `/rig attach ${input.runId}`;
6601
- return [
6602
- shellBinary("pi"),
6603
- "--no-extensions",
6604
- "--extension",
6605
- extensionSource,
6606
- initialCommand
6607
- ];
6608
- }
6609
- async function launchPiRigSession(context, input) {
6610
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6611
- return { launched: false, exitCode: null, command: [] };
6612
- }
6613
- if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6614
- return { launched: false, exitCode: null, command: [] };
6615
- }
6616
- const server = await ensureServerForCli(context.projectRoot);
6617
- const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6618
- const env = {
6619
- ...process.env,
6620
- ...buildPiRigSessionEnv({
6621
- projectRoot: context.projectRoot,
6622
- runId: input.runId,
6623
- taskId: input.taskId,
6624
- serverUrl: server.baseUrl,
6625
- authToken: server.authToken
6626
- })
6627
- };
6628
- process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6629
- `);
6630
- process.stdout.write(`Pi command: ${formatCommand(command)}
6631
- `);
6632
- const launchedAt = Date.now();
6633
- const child = spawn2(command[0], command.slice(1), {
6634
- cwd: context.projectRoot,
6635
- env,
6636
- stdio: "inherit"
6637
- });
6638
- const launchError = await new Promise((resolve19) => {
6639
- child.once("error", (error) => {
6640
- resolve19({ error: error.message });
6641
- });
6642
- child.once("close", (code) => resolve19({ code }));
6643
- });
6644
- if ("error" in launchError) {
6645
- process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6646
- `);
6647
- return { launched: false, exitCode: null, command, error: launchError.error };
6648
- }
6649
- const exitCode = launchError.code;
6650
- const elapsedMs = Date.now() - launchedAt;
6651
- if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6652
- const error = `Pi exited during startup with code ${exitCode}.`;
6653
- process.stderr.write(`${error} Falling back to Rig attach view.
6654
- `);
6655
- return { launched: false, exitCode, command, error };
6656
- }
6657
- return { launched: true, exitCode, command };
6658
- }
6659
-
6660
7102
  // packages/cli/src/commands/run.ts
6661
7103
  function normalizeRemoteRunDetails(payload) {
6662
7104
  const run = payload.run;
@@ -6881,20 +7323,13 @@ async function executeRun(context, args) {
6881
7323
  throw new CliError2("run attach requires a run id.", 2);
6882
7324
  }
6883
7325
  let steered = false;
6884
- const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
6885
- if (shouldTryPiAttach && messageOption.value?.trim()) {
7326
+ if (messageOption.value?.trim()) {
6886
7327
  await steerRunViaServer(context, runId, messageOption.value.trim());
6887
7328
  steered = true;
6888
7329
  }
6889
- if (shouldTryPiAttach) {
6890
- const piSession = await launchPiRigSession(context, { runId });
6891
- if (piSession.launched) {
6892
- return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
6893
- }
6894
- }
6895
7330
  const attached = await attachRunOperatorView(context, {
6896
7331
  runId,
6897
- message: shouldTryPiAttach ? null : messageOption.value ?? null,
7332
+ message: null,
6898
7333
  once: once.value,
6899
7334
  follow: follow.value,
6900
7335
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
@@ -7621,20 +8056,7 @@ async function executeTask(context, args, options) {
7621
8056
  let attachDetails = null;
7622
8057
  if (!detachResult.value && context.outputMode === "text") {
7623
8058
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7624
- if (runtimeAdapter === "pi") {
7625
- const piSession = await launchPiRigSession(context, {
7626
- runId: submitted.runId,
7627
- taskId: selectedTaskId,
7628
- title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7629
- runtimeAdapter
7630
- });
7631
- attachDetails = { mode: "pi", ...piSession };
7632
- if (!piSession.launched) {
7633
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7634
- }
7635
- } else {
7636
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7637
- }
8059
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7638
8060
  } else if (context.outputMode === "text") {
7639
8061
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7640
8062
  }
@@ -7721,7 +8143,7 @@ async function executeTask(context, args, options) {
7721
8143
  // packages/cli/src/commands/task-run-driver.ts
7722
8144
  import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7723
8145
  import { resolve as resolve20 } from "path";
7724
- import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
8146
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
7725
8147
  import { createInterface as createLineInterface } from "readline";
7726
8148
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7727
8149
  import {
@@ -7778,6 +8200,7 @@ function buildPiRigBridgeEnv(input) {
7778
8200
  RIG_SERVER_RUN_ID: input.runId,
7779
8201
  RIG_TASK_ID: input.taskId,
7780
8202
  RIG_RUNTIME_ADAPTER: "pi",
8203
+ RIG_STEERING_POLL_MS: "0",
7781
8204
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7782
8205
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7783
8206
  ...githubBridgeEnv(githubToken)
@@ -7949,7 +8372,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
7949
8372
  }
7950
8373
  function createCommandRunner(binary) {
7951
8374
  return async (args, options) => {
7952
- const child = spawn3(binary, [...args], {
8375
+ const child = spawn2(binary, [...args], {
7953
8376
  cwd: options?.cwd,
7954
8377
  stdio: ["ignore", "pipe", "pipe"]
7955
8378
  });
@@ -8373,6 +8796,69 @@ function appendAssistantTimelineFromRecord(input) {
8373
8796
  }
8374
8797
  return nextAssistantText;
8375
8798
  }
8799
+ function appendPiRpcProtocolLogFromRecord(input) {
8800
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8801
+ if (type === "response") {
8802
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
8803
+ const success = input.record.success !== false;
8804
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
8805
+ return true;
8806
+ }
8807
+ appendRunLog(input.projectRoot, input.runId, {
8808
+ id: input.nextRunLogId(),
8809
+ title: success ? "Pi RPC response" : "Pi RPC error",
8810
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
8811
+ tone: success ? "tool" : "error",
8812
+ status: input.status,
8813
+ payload: input.record,
8814
+ createdAt: new Date().toISOString()
8815
+ });
8816
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
8817
+ return true;
8818
+ }
8819
+ if (type !== "extension_ui_request")
8820
+ return false;
8821
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
8822
+ let title = "Pi UI event";
8823
+ let detail = method;
8824
+ let tone = "info";
8825
+ if (method === "notify") {
8826
+ title = "Pi notification";
8827
+ detail = String(input.record.message ?? "");
8828
+ tone = input.record.notifyType === "error" ? "error" : "info";
8829
+ } else if (method === "setStatus") {
8830
+ title = "Pi UI status";
8831
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
8832
+ tone = "tool";
8833
+ } else if (method === "setWidget") {
8834
+ title = "Pi UI widget";
8835
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
8836
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
8837
+ tone = "tool";
8838
+ } else if (method === "setTitle") {
8839
+ title = "Pi UI title";
8840
+ detail = String(input.record.title ?? "");
8841
+ tone = "tool";
8842
+ } else if (method === "set_editor_text") {
8843
+ title = "Pi editor update";
8844
+ detail = String(input.record.text ?? "").slice(0, 500);
8845
+ tone = "tool";
8846
+ } else {
8847
+ title = "Pi UI request";
8848
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
8849
+ }
8850
+ appendRunLog(input.projectRoot, input.runId, {
8851
+ id: input.nextRunLogId(),
8852
+ title,
8853
+ detail,
8854
+ tone,
8855
+ status: input.status,
8856
+ payload: input.record,
8857
+ createdAt: new Date().toISOString()
8858
+ });
8859
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
8860
+ return true;
8861
+ }
8376
8862
  function appendPiToolTimelineFromRecord(input) {
8377
8863
  const type = typeof input.record.type === "string" ? input.record.type : "";
8378
8864
  if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
@@ -8391,7 +8877,7 @@ function appendPiToolTimelineFromRecord(input) {
8391
8877
  }
8392
8878
  function isNonRenderablePiProtocolRecord(record) {
8393
8879
  const type = typeof record.type === "string" ? record.type : "";
8394
- return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
8880
+ 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");
8395
8881
  }
8396
8882
  function appendToolTimelineFromLog(input) {
8397
8883
  const title = typeof input.log.title === "string" ? input.log.title : "";
@@ -8554,11 +9040,7 @@ async function executeRigOwnedTaskRun(context, input) {
8554
9040
  ...input.model ? ["--model", input.model] : [],
8555
9041
  "--prompt"
8556
9042
  ] : input.runtimeAdapter === "pi" ? [
8557
- "--print",
8558
- "--verbose",
8559
- "--mode",
8560
- "json",
8561
- "--no-session",
9043
+ "__rig_pi_session_daemon__",
8562
9044
  ...input.model ? ["--model", input.model] : []
8563
9045
  ] : [
8564
9046
  "--print",
@@ -8655,7 +9137,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8655
9137
  projectRoot: context.projectRoot,
8656
9138
  runId: input.runId,
8657
9139
  stage,
8658
- 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,
9140
+ 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,
8659
9141
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
8660
9142
  });
8661
9143
  }
@@ -8696,6 +9178,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8696
9178
  let latestProviderCommand = null;
8697
9179
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8698
9180
  let snapshotSidecarPromise = null;
9181
+ let wrapperManagesRuntimeSnapshot = false;
8699
9182
  let dirtyBaselineApplied = false;
8700
9183
  const childEnv = {
8701
9184
  ...process.env,
@@ -8748,6 +9231,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8748
9231
  detail: detail ?? "Verifier review is running."
8749
9232
  });
8750
9233
  };
9234
+ const nextRunLogId = createRunLogIdFactory(input.runId);
8751
9235
  const handleWrapperEvent = (rawPayload) => {
8752
9236
  let event = null;
8753
9237
  try {
@@ -8763,6 +9247,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8763
9247
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
8764
9248
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
8765
9249
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
9250
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
8766
9251
  latestRuntimeBranch = runtimeId;
8767
9252
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
8768
9253
  runtimeId: runtimeId ?? null,
@@ -8800,7 +9285,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8800
9285
  });
8801
9286
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
8802
9287
  }
8803
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
9288
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
8804
9289
  snapshotSidecarPromise = (async () => {
8805
9290
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
8806
9291
  projectRoot: context.projectRoot,
@@ -8882,9 +9367,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8882
9367
  }
8883
9368
  return true;
8884
9369
  }
9370
+ 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") {
9371
+ 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";
9372
+ 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");
9373
+ appendRunLog(context.projectRoot, input.runId, {
9374
+ id: nextRunLogId(),
9375
+ title,
9376
+ detail,
9377
+ tone: event.type === "pi.session.error" ? "error" : "info",
9378
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9379
+ payload: {
9380
+ eventType: event.type,
9381
+ runId: typeof payload.runId === "string" ? payload.runId : null,
9382
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
9383
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
9384
+ },
9385
+ createdAt: new Date().toISOString()
9386
+ });
9387
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9388
+ return true;
9389
+ }
9390
+ if (event.type === "pi.session.ready") {
9391
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
9392
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
9393
+ if (privateMetadata) {
9394
+ patchAuthorityRun(context.projectRoot, input.runId, {
9395
+ piSession: publicMetadata,
9396
+ piSessionPrivate: privateMetadata
9397
+ });
9398
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
9399
+ appendRunLog(context.projectRoot, input.runId, {
9400
+ id: `log:${input.runId}:pi-session-ready`,
9401
+ title: "Worker Pi session ready",
9402
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
9403
+ tone: "info",
9404
+ status: "running",
9405
+ payload: {
9406
+ sessionId: sessionId || null,
9407
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
9408
+ },
9409
+ createdAt: new Date().toISOString()
9410
+ });
9411
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
9412
+ }
9413
+ return true;
9414
+ }
9415
+ 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") {
9416
+ 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";
9417
+ 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")}`;
9418
+ appendRunLog(context.projectRoot, input.runId, {
9419
+ id: nextRunLogId(),
9420
+ title,
9421
+ detail,
9422
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
9423
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9424
+ payload,
9425
+ createdAt: new Date().toISOString()
9426
+ });
9427
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9428
+ return true;
9429
+ }
8885
9430
  return false;
8886
9431
  };
8887
- const nextRunLogId = createRunLogIdFactory(input.runId);
8888
9432
  const handleAgentStdoutLine = (line) => {
8889
9433
  const trimmed = line.trim();
8890
9434
  if (!trimmed)
@@ -8918,6 +9462,15 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8918
9462
  try {
8919
9463
  const record = JSON.parse(trimmed);
8920
9464
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9465
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
9466
+ projectRoot: context.projectRoot,
9467
+ runId: input.runId,
9468
+ record,
9469
+ status: liveLogStatus,
9470
+ nextRunLogId
9471
+ })) {
9472
+ return;
9473
+ }
8921
9474
  if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
8922
9475
  emitServerRunEvent({ type: "timeline", runId: input.runId });
8923
9476
  return;
@@ -9063,7 +9616,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9063
9616
  }
9064
9617
  for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
9065
9618
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
9066
- const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9619
+ const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9067
9620
  cwd: context.projectRoot,
9068
9621
  env: childEnv,
9069
9622
  stdio: ["pipe", "pipe", "pipe"]
@@ -9390,7 +9943,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9390
9943
  startedAt,
9391
9944
  finishedAt: requestedAt
9392
9945
  });
9393
- return;
9946
+ process.exit(0);
9394
9947
  }
9395
9948
  const runPiPrFeedbackFix = async (message2) => {
9396
9949
  appendPiStageLog({
@@ -9410,7 +9963,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9410
9963
  });
9411
9964
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
9412
9965
  const feedbackCommand = buildHostAgentCommand(message2);
9413
- const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
9966
+ const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9414
9967
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
9415
9968
  env: childEnv,
9416
9969
  stdio: ["pipe", "pipe", "pipe"]