@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.
package/dist/src/index.js CHANGED
@@ -2950,6 +2950,66 @@ async function steerRunViaServer(context, runId, message) {
2950
2950
  });
2951
2951
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2952
2952
  }
2953
+ async function getRunPiSessionViaServer(context, runId) {
2954
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
2955
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2956
+ }
2957
+ async function getRunPiMessagesViaServer(context, runId) {
2958
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
2959
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
2960
+ }
2961
+ async function getRunPiStatusViaServer(context, runId) {
2962
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
2963
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2964
+ }
2965
+ async function getRunPiCommandsViaServer(context, runId) {
2966
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
2967
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
2968
+ }
2969
+ async function sendRunPiPromptViaServer(context, runId, text2, streamingBehavior) {
2970
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
2971
+ method: "POST",
2972
+ headers: { "content-type": "application/json" },
2973
+ body: JSON.stringify({ text: text2, streamingBehavior })
2974
+ });
2975
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2976
+ }
2977
+ async function sendRunPiShellViaServer(context, runId, text2) {
2978
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
2979
+ method: "POST",
2980
+ headers: { "content-type": "application/json" },
2981
+ body: JSON.stringify({ text: text2 })
2982
+ });
2983
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2984
+ }
2985
+ async function runRunPiCommandViaServer(context, runId, text2) {
2986
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
2987
+ method: "POST",
2988
+ headers: { "content-type": "application/json" },
2989
+ body: JSON.stringify({ text: text2 })
2990
+ });
2991
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
2992
+ }
2993
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
2994
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
2995
+ method: "POST",
2996
+ headers: { "content-type": "application/json" },
2997
+ body: JSON.stringify({ requestId, ...valueOrCancel })
2998
+ });
2999
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
3000
+ }
3001
+ async function abortRunPiViaServer(context, runId) {
3002
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
3003
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
3004
+ }
3005
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
3006
+ const server = await ensureServerForCli(context.projectRoot);
3007
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
3008
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
3009
+ if (server.authToken)
3010
+ url.searchParams.set("token", server.authToken);
3011
+ return url.toString();
3012
+ }
2953
3013
  async function submitTaskRunViaServer(context, input) {
2954
3014
  const isTaskRun = Boolean(input.taskId);
2955
3015
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -2982,183 +3042,6 @@ async function submitTaskRunViaServer(context, input) {
2982
3042
  return { runId };
2983
3043
  }
2984
3044
 
2985
- // packages/cli/src/commands/_pi-install.ts
2986
- import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2987
- import { homedir as homedir3 } from "os";
2988
- import { resolve as resolve10 } from "path";
2989
- var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2990
- var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2991
- var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2992
- export { default } from '@rig/pi-rig';
2993
- `;
2994
- async function defaultCommandRunner(command, options = {}) {
2995
- const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
2996
- const [stdout, stderr, exitCode] = await Promise.all([
2997
- new Response(proc.stdout).text(),
2998
- new Response(proc.stderr).text(),
2999
- proc.exited
3000
- ]);
3001
- return { exitCode, stdout, stderr };
3002
- }
3003
- function resolvePiRigExtensionPath(homeDir) {
3004
- return resolve10(homeDir, ".pi", "agent", "extensions", "pi-rig");
3005
- }
3006
- function resolvePiRigPackageSource(projectRoot, exists = existsSync6) {
3007
- const localPackage = resolve10(projectRoot, "packages", "pi-rig");
3008
- if (exists(resolve10(localPackage, "package.json")))
3009
- return localPackage;
3010
- return `npm:${PI_RIG_PACKAGE_NAME}`;
3011
- }
3012
- function resolvePiHomeDir(inputHomeDir) {
3013
- return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir3();
3014
- }
3015
- function piListContainsPiRig(output) {
3016
- return output.split(/\r?\n/).some((line) => {
3017
- const normalized = line.trim();
3018
- return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
3019
- });
3020
- }
3021
- async function safeRun(runner, command, options) {
3022
- try {
3023
- return await runner(command, options);
3024
- } catch (error) {
3025
- return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
3026
- }
3027
- }
3028
- function splitInstallCommand(value) {
3029
- return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
3030
- }
3031
- async function ensurePiBinaryAvailable(input) {
3032
- const current = await safeRun(input.runner, ["pi", "--version"]);
3033
- if (current.exitCode === 0) {
3034
- const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
3035
- if (updateCommand) {
3036
- const parts2 = splitInstallCommand(updateCommand);
3037
- if (parts2.length > 0)
3038
- await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3039
- }
3040
- return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
3041
- }
3042
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
3043
- const parts = splitInstallCommand(installCommand);
3044
- if (parts.length === 0) {
3045
- return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
3046
- }
3047
- const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3048
- if (install.exitCode !== 0) {
3049
- return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
3050
- }
3051
- const next = await safeRun(input.runner, ["pi", "--version"]);
3052
- return {
3053
- ok: next.exitCode === 0,
3054
- installedOrUpdated: true,
3055
- detail: (next.stdout || next.stderr).trim() || undefined,
3056
- ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
3057
- };
3058
- }
3059
- function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync6) {
3060
- const extensionPath = resolvePiRigExtensionPath(homeDir);
3061
- const indexPath = resolve10(extensionPath, "index.ts");
3062
- if (!exists(indexPath))
3063
- return;
3064
- try {
3065
- const content = readFileSync4(indexPath, "utf8");
3066
- if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
3067
- rmSync3(extensionPath, { recursive: true, force: true });
3068
- }
3069
- } catch {}
3070
- }
3071
- async function checkPiRigInstall(input = {}) {
3072
- const home = resolvePiHomeDir(input.homeDir);
3073
- const extensionPath = resolvePiRigExtensionPath(home);
3074
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
3075
- return {
3076
- extensionPath,
3077
- pi: { ok: true, label: "pi", detail: "fake-pi" },
3078
- piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
3079
- };
3080
- }
3081
- const exists = input.exists ?? existsSync6;
3082
- const runner = input.commandRunner ?? defaultCommandRunner;
3083
- const piResult = await safeRun(runner, ["pi", "--version"]);
3084
- const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
3085
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
3086
- ${piListResult.stderr}`);
3087
- const legacyBridge = exists(resolve10(extensionPath, "index.ts"));
3088
- const hasPiRig = listedPiRig;
3089
- return {
3090
- extensionPath,
3091
- pi: {
3092
- ok: piResult.exitCode === 0,
3093
- label: "pi",
3094
- detail: (piResult.stdout || piResult.stderr).trim() || undefined,
3095
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
3096
- },
3097
- piRig: {
3098
- ok: hasPiRig,
3099
- label: "pi-rig global extension",
3100
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
3101
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
3102
- }
3103
- };
3104
- }
3105
- async function ensurePiRigInstalled(input) {
3106
- const home = resolvePiHomeDir(input.homeDir);
3107
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
3108
- const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
3109
- return { ...status2, installedPath: status2.extensionPath };
3110
- }
3111
- const runner = input.commandRunner ?? defaultCommandRunner;
3112
- const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
3113
- const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
3114
- extensionPath: resolvePiRigExtensionPath(home),
3115
- pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
3116
- piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
3117
- };
3118
- if (!piAvailable.ok) {
3119
- throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
3120
- }
3121
- const packageSource = resolvePiRigPackageSource(input.projectRoot);
3122
- removeManagedLegacyPiRigBridge(home);
3123
- const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
3124
- if (install.exitCode !== 0) {
3125
- throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
3126
- }
3127
- const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
3128
- return { ...next, installedPath: packageSource };
3129
- }
3130
- async function ensureRemotePiRigInstalled(input) {
3131
- const payload = await input.requestJson("/api/pi-rig/install", {
3132
- method: "POST",
3133
- headers: { "content-type": "application/json" },
3134
- body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
3135
- });
3136
- const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
3137
- const piOk = record.piOk === true || record.ok === true;
3138
- const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
3139
- const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
3140
- return {
3141
- remote: true,
3142
- extensionPath,
3143
- pi: {
3144
- ok: piOk,
3145
- label: "pi",
3146
- detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
3147
- hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
3148
- },
3149
- piRig: {
3150
- ok: piRigOk,
3151
- label: "pi-rig global extension",
3152
- detail: extensionPath,
3153
- hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
3154
- }
3155
- };
3156
- }
3157
- async function buildPiSetupChecks(input = {}) {
3158
- const status = await checkPiRigInstall(input);
3159
- return [status.pi, status.piRig];
3160
- }
3161
-
3162
3045
  // packages/cli/src/commands/_preflight.ts
3163
3046
  function preflightCheck(id, label, status, detail, remediation) {
3164
3047
  return {
@@ -3316,14 +3199,7 @@ async function runFastTaskRunPreflight(context, options = {}) {
3316
3199
  }
3317
3200
  }
3318
3201
  if ((options.runtimeAdapter ?? "pi") === "pi") {
3319
- const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
3320
- ok: false,
3321
- label: "pi/pi-rig checks",
3322
- hint: message(error)
3323
- }]);
3324
- for (const pi of piChecks) {
3325
- 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.")));
3326
- }
3202
+ checks.push(preflightCheck("runtime", "worker Pi SDK session daemon", "pass", selectedServer?.connectionKind === "remote" ? "remote worker-owned runtime" : "bundled server-owned runtime"));
3327
3203
  } else {
3328
3204
  checks.push(preflightCheck("runtime", "runtime adapter", "pass", options.runtimeAdapter));
3329
3205
  }
@@ -3439,7 +3315,7 @@ async function executeQueue(context, args) {
3439
3315
  }
3440
3316
 
3441
3317
  // packages/cli/src/commands/agent.ts
3442
- import { resolve as resolve12 } from "path";
3318
+ import { resolve as resolve11 } from "path";
3443
3319
  import {
3444
3320
  agentId,
3445
3321
  cleanupAgentRuntime,
@@ -3449,8 +3325,8 @@ import {
3449
3325
  } from "@rig/runtime/control-plane/runtime/isolation";
3450
3326
 
3451
3327
  // packages/cli/src/commands/_authority-runs.ts
3452
- import { existsSync as existsSync7 } from "fs";
3453
- import { resolve as resolve11 } from "path";
3328
+ import { existsSync as existsSync6 } from "fs";
3329
+ import { resolve as resolve10 } from "path";
3454
3330
  import {
3455
3331
  readAuthorityRun,
3456
3332
  readJsonlFile as readJsonlFile2,
@@ -3472,8 +3348,8 @@ function normalizeRuntimeAdapter(value) {
3472
3348
  return "claude-code";
3473
3349
  }
3474
3350
  function readLatestBeadRecord(projectRoot, taskId) {
3475
- const issuesPath = resolve11(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3476
- if (!existsSync7(issuesPath)) {
3351
+ const issuesPath = resolve10(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3352
+ if (!existsSync6(issuesPath)) {
3477
3353
  return null;
3478
3354
  }
3479
3355
  let latest = null;
@@ -3541,7 +3417,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3541
3417
  } else if ("errorText" in next) {
3542
3418
  delete next.errorText;
3543
3419
  }
3544
- writeJsonFile3(resolve11(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3420
+ writeJsonFile3(resolve10(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3545
3421
  return next;
3546
3422
  }
3547
3423
 
@@ -3662,10 +3538,10 @@ async function executeAgent(context, args) {
3662
3538
  status: "running",
3663
3539
  startedAt: createdAt,
3664
3540
  worktreePath: runtime.workspaceDir,
3665
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3541
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3666
3542
  logRoot: runtime.logsDir,
3667
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3668
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3543
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3544
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3669
3545
  pid: process.pid
3670
3546
  });
3671
3547
  const result = await runInAgentRuntime({
@@ -3685,10 +3561,10 @@ async function executeAgent(context, args) {
3685
3561
  startedAt: createdAt,
3686
3562
  completedAt: failedAt,
3687
3563
  worktreePath: runtime.workspaceDir,
3688
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3564
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3689
3565
  logRoot: runtime.logsDir,
3690
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3691
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3566
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3567
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3692
3568
  pid: process.pid,
3693
3569
  errorText: result.stderr ? result.stderr.trim() : `Agent runtime command failed (${result.exitCode})`
3694
3570
  });
@@ -3705,10 +3581,10 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3705
3581
  startedAt: createdAt,
3706
3582
  completedAt,
3707
3583
  worktreePath: runtime.workspaceDir,
3708
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3584
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3709
3585
  logRoot: runtime.logsDir,
3710
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3711
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3586
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3587
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3712
3588
  pid: process.pid
3713
3589
  });
3714
3590
  return {
@@ -3782,17 +3658,17 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3782
3658
  import {
3783
3659
  chmodSync,
3784
3660
  copyFileSync as copyFileSync2,
3785
- existsSync as existsSync8,
3661
+ existsSync as existsSync7,
3786
3662
  mkdirSync as mkdirSync6,
3787
3663
  readdirSync,
3788
3664
  readlinkSync,
3789
- rmSync as rmSync4,
3665
+ rmSync as rmSync3,
3790
3666
  statSync,
3791
3667
  symlinkSync,
3792
3668
  unlinkSync
3793
3669
  } from "fs";
3794
- import { homedir as homedir4 } from "os";
3795
- import { resolve as resolve13 } from "path";
3670
+ import { homedir as homedir3 } from "os";
3671
+ import { resolve as resolve12 } from "path";
3796
3672
  import { buildBinary as buildBinary2 } from "@rig/runtime/control-plane/runtime/isolation";
3797
3673
  import {
3798
3674
  computeRuntimeImageFingerprint,
@@ -3811,13 +3687,13 @@ async function runQuietBinaryProbe(binaryPath, args, cwd) {
3811
3687
 
3812
3688
  // packages/cli/src/commands/dist.ts
3813
3689
  function collectRigValidatorBuildTargets(input) {
3814
- const validatorsRoot = resolve13(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3815
- if (!existsSync8(validatorsRoot))
3690
+ const validatorsRoot = resolve12(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3691
+ if (!existsSync7(validatorsRoot))
3816
3692
  return [];
3817
3693
  const targets = [];
3818
3694
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
3819
3695
  for (const category of categories) {
3820
- const categoryDir = resolve13(validatorsRoot, category.name);
3696
+ const categoryDir = resolve12(validatorsRoot, category.name);
3821
3697
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
3822
3698
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
3823
3699
  continue;
@@ -3826,7 +3702,7 @@ function collectRigValidatorBuildTargets(input) {
3826
3702
  continue;
3827
3703
  targets.push({
3828
3704
  source: `packages/runtime/src/control-plane/validators/${category.name}/${entry.name}`,
3829
- dest: resolve13(input.imageDir, `bin/validators/${category.name}-${check}`),
3705
+ dest: resolve12(input.imageDir, `bin/validators/${category.name}-${check}`),
3830
3706
  cwd: input.hostProjectRoot
3831
3707
  });
3832
3708
  }
@@ -3835,16 +3711,16 @@ function collectRigValidatorBuildTargets(input) {
3835
3711
  }
3836
3712
  async function findLatestDistBinary(projectRoot) {
3837
3713
  const distRoot = resolveControlPlaneHostDistDir(projectRoot);
3838
- if (!existsSync8(distRoot)) {
3714
+ if (!existsSync7(distRoot)) {
3839
3715
  return null;
3840
3716
  }
3841
3717
  const entries = readdirSync(distRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("rig-")).map((entry) => ({
3842
3718
  name: entry.name,
3843
- mtimeMs: statSync(resolve13(distRoot, entry.name)).mtimeMs
3719
+ mtimeMs: statSync(resolve12(distRoot, entry.name)).mtimeMs
3844
3720
  })).sort((a, b) => b.mtimeMs - a.mtimeMs || b.name.localeCompare(a.name));
3845
3721
  for (const { name } of entries) {
3846
- const candidate = resolve13(distRoot, name, "bin", "rig");
3847
- if (existsSync8(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3722
+ const candidate = resolve12(distRoot, name, "bin", "rig");
3723
+ if (existsSync7(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3848
3724
  return candidate;
3849
3725
  }
3850
3726
  }
@@ -3856,7 +3732,7 @@ async function isRunnableRigBinary(binaryPath, projectRoot) {
3856
3732
  async function runDistDoctor(projectRoot) {
3857
3733
  const bunPath = Bun.which("bun");
3858
3734
  const rigPath = Bun.which("rig");
3859
- const userBinDir = resolve13(homedir4(), ".local/bin");
3735
+ const userBinDir = resolve12(homedir3(), ".local/bin");
3860
3736
  const userBinInPath = (process.env.PATH || "").split(":").filter(Boolean).includes(userBinDir);
3861
3737
  let rigRunnable = false;
3862
3738
  if (rigPath) {
@@ -3904,15 +3780,15 @@ async function executeDist(context, args) {
3904
3780
  let source = await findLatestDistBinary(context.projectRoot);
3905
3781
  let buildDir = null;
3906
3782
  if (!source) {
3907
- buildDir = resolve13(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3783
+ buildDir = resolve12(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3908
3784
  await context.runCommand(["bun", "run", "packages/cli/bin/build-rig-binaries.ts", "--output-dir", buildDir]);
3909
- source = resolve13(buildDir, "bin", "rig");
3785
+ source = resolve12(buildDir, "bin", "rig");
3910
3786
  }
3911
- if (!existsSync8(source)) {
3787
+ if (!existsSync7(source)) {
3912
3788
  throw new CliError2(`Unable to locate rig binary at ${source}.`, 2);
3913
3789
  }
3914
- const installedPath = resolve13(installDir, "rig");
3915
- if (existsSync8(installedPath)) {
3790
+ const installedPath = resolve12(installDir, "rig");
3791
+ if (existsSync7(installedPath)) {
3916
3792
  unlinkSync(installedPath);
3917
3793
  }
3918
3794
  copyFileSync2(source, installedPath);
@@ -3954,22 +3830,22 @@ async function executeDist(context, args) {
3954
3830
  requireNoExtraArgs(rest, "bun run rig dist rebuild-agent");
3955
3831
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
3956
3832
  const currentId = computeRuntimeImageId(fp);
3957
- const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3833
+ const imagesDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3958
3834
  mkdirSync6(imagesDir, { recursive: true });
3959
3835
  let pruned = 0;
3960
3836
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
3961
3837
  if (entry.isDirectory() && entry.name !== currentId) {
3962
- rmSync4(resolve13(imagesDir, entry.name), { recursive: true, force: true });
3838
+ rmSync3(resolve12(imagesDir, entry.name), { recursive: true, force: true });
3963
3839
  pruned++;
3964
3840
  }
3965
3841
  }
3966
3842
  if (pruned > 0 && context.outputMode === "text") {
3967
3843
  console.log(`Pruned ${pruned} stale image(s).`);
3968
3844
  }
3969
- const imageDir = resolve13(imagesDir, currentId);
3970
- mkdirSync6(resolve13(imageDir, "bin/hooks"), { recursive: true });
3971
- mkdirSync6(resolve13(imageDir, "bin/plugins"), { recursive: true });
3972
- mkdirSync6(resolve13(imageDir, "bin/validators"), { recursive: true });
3845
+ const imageDir = resolve12(imagesDir, currentId);
3846
+ mkdirSync6(resolve12(imageDir, "bin/hooks"), { recursive: true });
3847
+ mkdirSync6(resolve12(imageDir, "bin/plugins"), { recursive: true });
3848
+ mkdirSync6(resolve12(imageDir, "bin/validators"), { recursive: true });
3973
3849
  const hookNames = [
3974
3850
  "scope-guard",
3975
3851
  "import-guard",
@@ -3983,25 +3859,25 @@ async function executeDist(context, args) {
3983
3859
  ];
3984
3860
  const targets = [];
3985
3861
  const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || context.projectRoot;
3986
- targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve13(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3987
- targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3862
+ targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve12(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3863
+ targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3988
3864
  for (const hookName of hookNames) {
3989
3865
  const src = `packages/runtime/src/control-plane/hooks/${hookName}.ts`;
3990
- targets.push({ source: src, dest: resolve13(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3991
- targets.push({ source: src, dest: resolve13(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3866
+ targets.push({ source: src, dest: resolve12(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3867
+ targets.push({ source: src, dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3992
3868
  }
3993
- const pluginsDir = resolve13(context.projectRoot, "rig/plugins");
3994
- const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3995
- const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
3996
- const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
3869
+ const pluginsDir = resolve12(context.projectRoot, "rig/plugins");
3870
+ const binPluginsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3871
+ const validatorsRoot = resolve12(hostProjectRoot, "packages/runtime/src/control-plane/validators");
3872
+ const binValidatorsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
3997
3873
  mkdirSync6(binPluginsDir, { recursive: true });
3998
3874
  mkdirSync6(binValidatorsDir, { recursive: true });
3999
- if (existsSync8(pluginsDir)) {
3875
+ if (existsSync7(pluginsDir)) {
4000
3876
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
4001
3877
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
4002
3878
  if (!m)
4003
3879
  continue;
4004
- targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve13(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
3880
+ targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve12(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
4005
3881
  }
4006
3882
  }
4007
3883
  targets.push(...collectRigValidatorBuildTargets({ contextProjectRoot: context.projectRoot, hostProjectRoot, imageDir }));
@@ -4012,17 +3888,17 @@ async function executeDist(context, args) {
4012
3888
  const isValidator = dest.includes("/bin/validators/");
4013
3889
  await buildBinary2(source, dest, cwd, isValidator ? { AGENT_BUN_PATH: Bun.which("bun") || "bun" } : { AGENT_PROJECT_ROOT: context.projectRoot });
4014
3890
  }
4015
- if (existsSync8(pluginsDir)) {
3891
+ if (existsSync7(pluginsDir)) {
4016
3892
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
4017
3893
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
4018
3894
  if (!m)
4019
3895
  continue;
4020
3896
  const pluginName = m[1];
4021
- const imageBin = resolve13(imageDir, `bin/plugins/${pluginName}`);
3897
+ const imageBin = resolve12(imageDir, `bin/plugins/${pluginName}`);
4022
3898
  if (!pluginName)
4023
3899
  continue;
4024
- const symlinkPath = resolve13(binPluginsDir, pluginName);
4025
- if (existsSync8(imageBin)) {
3900
+ const symlinkPath = resolve12(binPluginsDir, pluginName);
3901
+ if (existsSync7(imageBin)) {
4026
3902
  try {
4027
3903
  unlinkSync(symlinkPath);
4028
3904
  } catch {}
@@ -4030,10 +3906,10 @@ async function executeDist(context, args) {
4030
3906
  }
4031
3907
  }
4032
3908
  }
4033
- if (existsSync8(validatorsRoot)) {
3909
+ if (existsSync7(validatorsRoot)) {
4034
3910
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
4035
3911
  for (const category of categories) {
4036
- const categoryDir = resolve13(validatorsRoot, category.name);
3912
+ const categoryDir = resolve12(validatorsRoot, category.name);
4037
3913
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
4038
3914
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
4039
3915
  continue;
@@ -4041,9 +3917,9 @@ async function executeDist(context, args) {
4041
3917
  if (!check || check === "index" || check === "shared")
4042
3918
  continue;
4043
3919
  const validatorName = `${category.name}-${check}`;
4044
- const imageBin = resolve13(imageDir, `bin/validators/${validatorName}`);
4045
- const symlinkPath = resolve13(binValidatorsDir, validatorName);
4046
- if (existsSync8(imageBin)) {
3920
+ const imageBin = resolve12(imageDir, `bin/validators/${validatorName}`);
3921
+ const symlinkPath = resolve12(binValidatorsDir, validatorName);
3922
+ if (existsSync7(imageBin)) {
4047
3923
  try {
4048
3924
  unlinkSync(symlinkPath);
4049
3925
  } catch {}
@@ -4052,18 +3928,18 @@ async function executeDist(context, args) {
4052
3928
  }
4053
3929
  }
4054
3930
  }
4055
- const agentsDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
4056
- if (existsSync8(agentsDir)) {
3931
+ const agentsDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
3932
+ if (existsSync7(agentsDir)) {
4057
3933
  let relinkCount = 0;
4058
3934
  for (const agentEntry of readdirSync(agentsDir, { withFileTypes: true })) {
4059
3935
  if (!agentEntry.isDirectory())
4060
3936
  continue;
4061
- const agentBinDir = resolve13(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
4062
- if (!existsSync8(agentBinDir))
3937
+ const agentBinDir = resolve12(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
3938
+ if (!existsSync7(agentBinDir))
4063
3939
  continue;
4064
3940
  const walkDir = (dir) => {
4065
3941
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
4066
- const fullPath = resolve13(dir, entry.name);
3942
+ const fullPath = resolve12(dir, entry.name);
4067
3943
  if (entry.isDirectory()) {
4068
3944
  walkDir(fullPath);
4069
3945
  } else if (entry.isSymbolicLink()) {
@@ -4097,7 +3973,7 @@ async function executeDist(context, args) {
4097
3973
 
4098
3974
  // packages/cli/src/commands/inbox.ts
4099
3975
  import { writeFileSync as writeFileSync4 } from "fs";
4100
- import { resolve as resolve14 } from "path";
3976
+ import { resolve as resolve13 } from "path";
4101
3977
  import {
4102
3978
  listAuthorityRuns,
4103
3979
  readJsonlFile as readJsonlFile3,
@@ -4114,7 +3990,7 @@ async function executeInbox(context, args) {
4114
3990
  pending = task.rest;
4115
3991
  requireNoExtraArgs(pending, "bun run rig inbox approvals [--run <id>] [--task <id>]");
4116
3992
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
4117
- const approvals = runs.flatMap((entry) => readJsonlFile3(resolve14(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
3993
+ const approvals = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
4118
3994
  runId: entry.runId,
4119
3995
  record
4120
3996
  })));
@@ -4142,7 +4018,7 @@ async function executeInbox(context, args) {
4142
4018
  if (decision.value !== "approve" && decision.value !== "reject") {
4143
4019
  throw new CliError2("decision must be approve or reject.");
4144
4020
  }
4145
- const approvalsPath = resolve14(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
4021
+ const approvalsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
4146
4022
  const approvals = readJsonlFile3(approvalsPath);
4147
4023
  const resolvedAt = new Date().toISOString();
4148
4024
  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);
@@ -4159,7 +4035,7 @@ async function executeInbox(context, args) {
4159
4035
  pending = task.rest;
4160
4036
  requireNoExtraArgs(pending, "bun run rig inbox inputs [--run <id>] [--task <id>]");
4161
4037
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
4162
- const requests = runs.flatMap((entry) => readJsonlFile3(resolve14(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
4038
+ const requests = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
4163
4039
  runId: entry.runId,
4164
4040
  record
4165
4041
  })));
@@ -4201,7 +4077,7 @@ async function executeInbox(context, args) {
4201
4077
  const [key, ...restValue] = entry.split("=");
4202
4078
  return [key, restValue.join("=")];
4203
4079
  }));
4204
- const requestsPath = resolve14(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
4080
+ const requestsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
4205
4081
  const requests = readJsonlFile3(requestsPath);
4206
4082
  const resolvedAt = new Date().toISOString();
4207
4083
  const next = requests.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", answers: parsedAnswers, respondedAt: resolvedAt, resolvedAt } : entry);
@@ -4214,13 +4090,190 @@ async function executeInbox(context, args) {
4214
4090
  throw new CliError2(`Unknown inbox command: ${command}`);
4215
4091
  }
4216
4092
  }
4217
-
4218
- // packages/cli/src/commands/init.ts
4219
- import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4220
- import { spawnSync } from "child_process";
4221
- import { resolve as resolve17 } from "path";
4222
- import { buildRigInitConfigSource } from "@rig/core";
4223
- import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4093
+
4094
+ // packages/cli/src/commands/init.ts
4095
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4096
+ import { spawnSync } from "child_process";
4097
+ import { resolve as resolve17 } from "path";
4098
+ import { buildRigInitConfigSource } from "@rig/core";
4099
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4100
+
4101
+ // packages/cli/src/commands/_pi-install.ts
4102
+ import { existsSync as existsSync8, readFileSync as readFileSync4, rmSync as rmSync4 } from "fs";
4103
+ import { homedir as homedir4 } from "os";
4104
+ import { resolve as resolve14 } from "path";
4105
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
4106
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
4107
+ var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
4108
+ export { default } from '@rig/pi-rig';
4109
+ `;
4110
+ async function defaultCommandRunner(command, options = {}) {
4111
+ const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
4112
+ const [stdout, stderr, exitCode] = await Promise.all([
4113
+ new Response(proc.stdout).text(),
4114
+ new Response(proc.stderr).text(),
4115
+ proc.exited
4116
+ ]);
4117
+ return { exitCode, stdout, stderr };
4118
+ }
4119
+ function resolvePiRigExtensionPath(homeDir) {
4120
+ return resolve14(homeDir, ".pi", "agent", "extensions", "pi-rig");
4121
+ }
4122
+ function resolvePiRigPackageSource(projectRoot, exists = existsSync8) {
4123
+ const localPackage = resolve14(projectRoot, "packages", "pi-rig");
4124
+ if (exists(resolve14(localPackage, "package.json")))
4125
+ return localPackage;
4126
+ return `npm:${PI_RIG_PACKAGE_NAME}`;
4127
+ }
4128
+ function resolvePiHomeDir(inputHomeDir) {
4129
+ return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir4();
4130
+ }
4131
+ function piListContainsPiRig(output) {
4132
+ return output.split(/\r?\n/).some((line) => {
4133
+ const normalized = line.trim();
4134
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
4135
+ });
4136
+ }
4137
+ async function safeRun(runner, command, options) {
4138
+ try {
4139
+ return await runner(command, options);
4140
+ } catch (error) {
4141
+ return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
4142
+ }
4143
+ }
4144
+ function splitInstallCommand(value) {
4145
+ return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
4146
+ }
4147
+ async function ensurePiBinaryAvailable(input) {
4148
+ const current = await safeRun(input.runner, ["pi", "--version"]);
4149
+ if (current.exitCode === 0) {
4150
+ const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
4151
+ if (updateCommand) {
4152
+ const parts2 = splitInstallCommand(updateCommand);
4153
+ if (parts2.length > 0)
4154
+ await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
4155
+ }
4156
+ return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
4157
+ }
4158
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
4159
+ const parts = splitInstallCommand(installCommand);
4160
+ if (parts.length === 0) {
4161
+ return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
4162
+ }
4163
+ const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
4164
+ if (install.exitCode !== 0) {
4165
+ return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
4166
+ }
4167
+ const next = await safeRun(input.runner, ["pi", "--version"]);
4168
+ return {
4169
+ ok: next.exitCode === 0,
4170
+ installedOrUpdated: true,
4171
+ detail: (next.stdout || next.stderr).trim() || undefined,
4172
+ ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
4173
+ };
4174
+ }
4175
+ function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync8) {
4176
+ const extensionPath = resolvePiRigExtensionPath(homeDir);
4177
+ const indexPath = resolve14(extensionPath, "index.ts");
4178
+ if (!exists(indexPath))
4179
+ return;
4180
+ try {
4181
+ const content = readFileSync4(indexPath, "utf8");
4182
+ if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
4183
+ rmSync4(extensionPath, { recursive: true, force: true });
4184
+ }
4185
+ } catch {}
4186
+ }
4187
+ async function checkPiRigInstall(input = {}) {
4188
+ const home = resolvePiHomeDir(input.homeDir);
4189
+ const extensionPath = resolvePiRigExtensionPath(home);
4190
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
4191
+ return {
4192
+ extensionPath,
4193
+ pi: { ok: true, label: "pi", detail: "fake-pi" },
4194
+ piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
4195
+ };
4196
+ }
4197
+ const exists = input.exists ?? existsSync8;
4198
+ const runner = input.commandRunner ?? defaultCommandRunner;
4199
+ const piResult = await safeRun(runner, ["pi", "--version"]);
4200
+ const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
4201
+ const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
4202
+ ${piListResult.stderr}`);
4203
+ const legacyBridge = exists(resolve14(extensionPath, "index.ts"));
4204
+ const hasPiRig = listedPiRig;
4205
+ return {
4206
+ extensionPath,
4207
+ pi: {
4208
+ ok: piResult.exitCode === 0,
4209
+ label: "pi",
4210
+ detail: (piResult.stdout || piResult.stderr).trim() || undefined,
4211
+ hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
4212
+ },
4213
+ piRig: {
4214
+ ok: hasPiRig,
4215
+ label: "pi-rig global extension",
4216
+ detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
4217
+ hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
4218
+ }
4219
+ };
4220
+ }
4221
+ async function ensurePiRigInstalled(input) {
4222
+ const home = resolvePiHomeDir(input.homeDir);
4223
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
4224
+ const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
4225
+ return { ...status2, installedPath: status2.extensionPath };
4226
+ }
4227
+ const runner = input.commandRunner ?? defaultCommandRunner;
4228
+ const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
4229
+ const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
4230
+ extensionPath: resolvePiRigExtensionPath(home),
4231
+ pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
4232
+ piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
4233
+ };
4234
+ if (!piAvailable.ok) {
4235
+ throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
4236
+ }
4237
+ const packageSource = resolvePiRigPackageSource(input.projectRoot);
4238
+ removeManagedLegacyPiRigBridge(home);
4239
+ const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
4240
+ if (install.exitCode !== 0) {
4241
+ throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
4242
+ }
4243
+ const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
4244
+ return { ...next, installedPath: packageSource };
4245
+ }
4246
+ async function ensureRemotePiRigInstalled(input) {
4247
+ const payload = await input.requestJson("/api/pi-rig/install", {
4248
+ method: "POST",
4249
+ headers: { "content-type": "application/json" },
4250
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
4251
+ });
4252
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
4253
+ const piOk = record.piOk === true || record.ok === true;
4254
+ const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
4255
+ const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
4256
+ return {
4257
+ remote: true,
4258
+ extensionPath,
4259
+ pi: {
4260
+ ok: piOk,
4261
+ label: "pi",
4262
+ detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
4263
+ hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
4264
+ },
4265
+ piRig: {
4266
+ ok: piRigOk,
4267
+ label: "pi-rig global extension",
4268
+ detail: extensionPath,
4269
+ hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
4270
+ }
4271
+ };
4272
+ }
4273
+ async function buildPiSetupChecks(input = {}) {
4274
+ const status = await checkPiRigInstall(input);
4275
+ return [status.pi, status.piRig];
4276
+ }
4224
4277
 
4225
4278
  // packages/cli/src/commands/_snapshot-upload.ts
4226
4279
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -6588,8 +6641,477 @@ async function promptForTaskSelection(question) {
6588
6641
  }
6589
6642
  }
6590
6643
 
6591
- // packages/cli/src/commands/_operator-view.ts
6644
+ // packages/cli/src/commands/_pi-frontend.ts
6645
+ import { mkdtempSync, rmSync as rmSync5 } from "fs";
6646
+ import { tmpdir } from "os";
6647
+ import { join as join2 } from "path";
6648
+ import { main as runPiMain } from "@earendil-works/pi-coding-agent";
6649
+
6650
+ // packages/cli/src/commands/_pi-worker-bridge-extension.ts
6592
6651
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6652
+ var MAX_TRANSCRIPT_LINES = 120;
6653
+ function recordOf(value) {
6654
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6655
+ }
6656
+ function asText(value) {
6657
+ if (typeof value === "string")
6658
+ return value;
6659
+ if (value === null || value === undefined)
6660
+ return "";
6661
+ if (typeof value === "number" || typeof value === "boolean")
6662
+ return String(value);
6663
+ try {
6664
+ return JSON.stringify(value);
6665
+ } catch {
6666
+ return String(value);
6667
+ }
6668
+ }
6669
+ function textFromContent(content) {
6670
+ if (typeof content === "string")
6671
+ return content;
6672
+ if (!Array.isArray(content))
6673
+ return asText(content);
6674
+ return content.flatMap((part) => {
6675
+ const item = recordOf(part);
6676
+ if (!item)
6677
+ return [];
6678
+ if (typeof item.text === "string")
6679
+ return [item.text];
6680
+ if (typeof item.content === "string")
6681
+ return [item.content];
6682
+ if (item.type === "toolCall")
6683
+ return [`\u23FA ${String(item.name ?? "tool")} ${asText(item.arguments ?? "")}`.trim()];
6684
+ if (item.type === "toolResult")
6685
+ return [`\u21B3 ${asText(item.content ?? item.result ?? "")}`.trim()];
6686
+ return [];
6687
+ }).join(`
6688
+ `);
6689
+ }
6690
+ function appendTranscript(state, label, text2) {
6691
+ const trimmed = text2.trimEnd();
6692
+ if (!trimmed)
6693
+ return;
6694
+ const lines = trimmed.split(/\r?\n/);
6695
+ state.transcript.push(`${label}: ${lines[0] ?? ""}`);
6696
+ for (const line of lines.slice(1))
6697
+ state.transcript.push(` ${line}`);
6698
+ if (state.transcript.length > MAX_TRANSCRIPT_LINES) {
6699
+ state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
6700
+ }
6701
+ }
6702
+ function parseExtensionUiRequest(value) {
6703
+ const request = recordOf(value) ?? {};
6704
+ const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
6705
+ const method = String(request.method ?? request.type ?? "input");
6706
+ const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
6707
+ const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
6708
+ const options = rawOptions.map((option) => {
6709
+ const record = recordOf(option);
6710
+ return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
6711
+ }).filter(Boolean);
6712
+ return { requestId, method, prompt, options };
6713
+ }
6714
+ function renderBridgeWidget(state) {
6715
+ const statusParts = [
6716
+ state.wsConnected ? "live WS" : "WS pending",
6717
+ state.status,
6718
+ state.model,
6719
+ state.cwd
6720
+ ].filter(Boolean);
6721
+ const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
6722
+ if (state.activity)
6723
+ lines.push(state.activity);
6724
+ if (state.commands.length > 0) {
6725
+ lines.push(`Worker commands: ${state.commands.slice(0, 10).join(", ")}${state.commands.length > 10 ? ", \u2026" : ""}`);
6726
+ }
6727
+ lines.push("");
6728
+ if (state.transcript.length > 0) {
6729
+ lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
6730
+ } else {
6731
+ lines.push("Waiting for worker Pi daemon transcript\u2026");
6732
+ }
6733
+ if (state.pendingUi) {
6734
+ lines.push("");
6735
+ lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
6736
+ lines.push(state.pendingUi.prompt);
6737
+ state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
6738
+ lines.push("Reply in the Pi editor. /cancel cancels this request.");
6739
+ }
6740
+ return lines;
6741
+ }
6742
+ function updatePiUi(ctx, state) {
6743
+ ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
6744
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
6745
+ ctx.ui.setWorkingVisible(false);
6746
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
6747
+ }
6748
+ function applyStatus(state, payload) {
6749
+ const status = recordOf(payload.status) ?? payload;
6750
+ state.streaming = status.isStreaming === true || status.isCompacting === true || status.isBashRunning === true;
6751
+ state.cwd = typeof status.cwd === "string" ? status.cwd : state.cwd;
6752
+ state.model = typeof status.model === "string" ? status.model : state.model;
6753
+ const pending = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
6754
+ state.status = `${state.streaming ? "streaming" : "idle"}${pending ? ` \xB7 ${pending} queued` : ""}`;
6755
+ }
6756
+ function applyMessage(state, message2) {
6757
+ const record = recordOf(message2);
6758
+ if (!record)
6759
+ return;
6760
+ const role = String(record.role ?? "system");
6761
+ const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
6762
+ appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
6763
+ }
6764
+ function applyPiEvent(state, eventValue) {
6765
+ const event = recordOf(eventValue);
6766
+ if (!event)
6767
+ return;
6768
+ const type = String(event.type ?? "event");
6769
+ if (type === "agent_start") {
6770
+ state.streaming = true;
6771
+ state.status = "streaming";
6772
+ return;
6773
+ }
6774
+ if (type === "agent_end") {
6775
+ state.streaming = false;
6776
+ state.status = "idle";
6777
+ appendTranscript(state, "System", "Agent turn complete.");
6778
+ return;
6779
+ }
6780
+ if (type === "message_start" || type === "message_end" || type === "turn_end") {
6781
+ applyMessage(state, event.message);
6782
+ return;
6783
+ }
6784
+ if (type === "message_update") {
6785
+ const assistantEvent = recordOf(event.assistantMessageEvent);
6786
+ const delta = typeof assistantEvent?.delta === "string" ? assistantEvent.delta : typeof assistantEvent?.text === "string" ? assistantEvent.text : "";
6787
+ if (delta)
6788
+ appendTranscript(state, assistantEvent?.type === "thinking_delta" ? "Thinking" : "Pi", delta);
6789
+ return;
6790
+ }
6791
+ if (type === "tool_execution_start") {
6792
+ appendTranscript(state, "Tool", `${String(event.toolName ?? "tool")} ${asText(event.args ?? "")}`.trim());
6793
+ return;
6794
+ }
6795
+ if (type === "tool_execution_update") {
6796
+ appendTranscript(state, "Tool", asText(event.partialResult ?? ""));
6797
+ return;
6798
+ }
6799
+ if (type === "tool_execution_end") {
6800
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
6801
+ return;
6802
+ }
6803
+ if (type === "queue_update") {
6804
+ const steering = Array.isArray(event.steering) ? event.steering.length : 0;
6805
+ const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
6806
+ state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
6807
+ }
6808
+ }
6809
+ function applyUiEvent(state, value) {
6810
+ const event = recordOf(value);
6811
+ if (!event)
6812
+ return;
6813
+ const type = String(event.type ?? "ui");
6814
+ if (type === "shell.start")
6815
+ appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
6816
+ else if (type === "shell.chunk")
6817
+ appendTranscript(state, "Tool", asText(event.chunk));
6818
+ else if (type === "shell.end")
6819
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
6820
+ else
6821
+ appendTranscript(state, "System", `${type}: ${asText(event)}`);
6822
+ }
6823
+ function applyEnvelope(state, envelopeValue) {
6824
+ const envelope = recordOf(envelopeValue);
6825
+ if (!envelope)
6826
+ return;
6827
+ const type = String(envelope.type ?? "");
6828
+ if (type === "ready") {
6829
+ const metadata = recordOf(envelope.metadata);
6830
+ state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
6831
+ state.status = "worker Pi daemon ready";
6832
+ appendTranscript(state, "System", "Connected to worker Pi daemon.");
6833
+ } else if (type === "status.update") {
6834
+ applyStatus(state, envelope);
6835
+ } else if (type === "activity.update") {
6836
+ const activity = recordOf(envelope.activity);
6837
+ state.activity = [activity?.label, activity?.detail].map(asText).filter(Boolean).join(" \u2014 ");
6838
+ } else if (type === "extension_ui_request") {
6839
+ state.pendingUi = parseExtensionUiRequest(envelope.request);
6840
+ appendTranscript(state, "System", `Extension UI request: ${state.pendingUi.prompt}`);
6841
+ } else if (type === "pi.ui_event") {
6842
+ applyUiEvent(state, envelope.event);
6843
+ } else if (type === "pi.event") {
6844
+ applyPiEvent(state, envelope.event);
6845
+ } else if (type === "error") {
6846
+ appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
6847
+ }
6848
+ }
6849
+ async function waitForWorkerReady(options, ctx, state) {
6850
+ while (true) {
6851
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
6852
+ ready: false,
6853
+ status: error instanceof Error ? error.message : String(error),
6854
+ retryAfterMs: 1000
6855
+ }));
6856
+ if (session.ready === false) {
6857
+ const status = String(session.status ?? "starting");
6858
+ state.status = `waiting for worker Pi daemon \xB7 ${status}`;
6859
+ updatePiUi(ctx, state);
6860
+ if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
6861
+ appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
6862
+ return false;
6863
+ }
6864
+ await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
6865
+ continue;
6866
+ }
6867
+ const sessionRecord = recordOf(session) ?? {};
6868
+ applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
6869
+ updatePiUi(ctx, state);
6870
+ return true;
6871
+ }
6872
+ }
6873
+ function parseWsPayload(message2) {
6874
+ if (typeof message2.data === "string")
6875
+ return JSON.parse(message2.data);
6876
+ return JSON.parse(Buffer.from(message2.data).toString("utf8"));
6877
+ }
6878
+ async function connectWorkerStream(options, ctx, state) {
6879
+ const ready = await waitForWorkerReady(options, ctx, state);
6880
+ if (!ready)
6881
+ return;
6882
+ let catchupDone = false;
6883
+ const buffered = [];
6884
+ const wsUrl = await buildRunPiEventsWebSocketUrl(options.context, options.runId);
6885
+ const socket = new WebSocket(wsUrl);
6886
+ const closePromise = new Promise((resolve20) => {
6887
+ socket.onopen = () => {
6888
+ state.wsConnected = true;
6889
+ state.status = "live worker Pi WebSocket connected";
6890
+ updatePiUi(ctx, state);
6891
+ };
6892
+ socket.onmessage = (message2) => {
6893
+ try {
6894
+ const payload = parseWsPayload(message2);
6895
+ if (!catchupDone)
6896
+ buffered.push(payload);
6897
+ else {
6898
+ applyEnvelope(state, payload);
6899
+ updatePiUi(ctx, state);
6900
+ }
6901
+ } catch (error) {
6902
+ appendTranscript(state, "Error", `Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
6903
+ updatePiUi(ctx, state);
6904
+ }
6905
+ };
6906
+ socket.onerror = () => socket.close();
6907
+ socket.onclose = () => {
6908
+ state.wsConnected = false;
6909
+ state.status = "worker Pi WebSocket disconnected";
6910
+ updatePiUi(ctx, state);
6911
+ resolve20();
6912
+ };
6913
+ });
6914
+ try {
6915
+ const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
6916
+ getRunPiMessagesViaServer(options.context, options.runId),
6917
+ getRunPiStatusViaServer(options.context, options.runId),
6918
+ getRunPiCommandsViaServer(options.context, options.runId)
6919
+ ]);
6920
+ const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
6921
+ for (const message2 of messages)
6922
+ applyMessage(state, message2);
6923
+ applyStatus(state, statusPayload);
6924
+ const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
6925
+ state.commands = commands.flatMap((command) => {
6926
+ const record = recordOf(command);
6927
+ return typeof record?.name === "string" ? [`/${record.name}`] : [];
6928
+ });
6929
+ catchupDone = true;
6930
+ for (const payload of buffered.splice(0))
6931
+ applyEnvelope(state, payload);
6932
+ updatePiUi(ctx, state);
6933
+ } catch (error) {
6934
+ appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
6935
+ catchupDone = true;
6936
+ updatePiUi(ctx, state);
6937
+ }
6938
+ await closePromise;
6939
+ }
6940
+ async function answerPendingUi(options, state, line) {
6941
+ const pending = state.pendingUi;
6942
+ if (!pending)
6943
+ return false;
6944
+ if (line === "/cancel") {
6945
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { cancelled: true });
6946
+ } else if (pending.method === "confirm") {
6947
+ const confirmed = /^(y|yes|true|1)$/i.test(line);
6948
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: confirmed, confirmed });
6949
+ } else if (pending.options.length > 0 && /^\d+$/.test(line)) {
6950
+ const selected = pending.options[Math.max(0, Number(line) - 1)] ?? line;
6951
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: selected });
6952
+ } else {
6953
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: line });
6954
+ }
6955
+ appendTranscript(state, "System", `Responded to extension UI request ${pending.requestId}.`);
6956
+ state.pendingUi = null;
6957
+ return true;
6958
+ }
6959
+ async function routeInput(options, ctx, state, line) {
6960
+ const text2 = line.trim();
6961
+ if (!text2)
6962
+ return;
6963
+ if (await answerPendingUi(options, state, text2)) {
6964
+ updatePiUi(ctx, state);
6965
+ return;
6966
+ }
6967
+ if (text2 === "/detach" || text2 === "/quit" || text2 === "/q") {
6968
+ appendTranscript(state, "System", "Detached locally; worker Pi daemon continues.");
6969
+ updatePiUi(ctx, state);
6970
+ ctx.shutdown();
6971
+ return;
6972
+ }
6973
+ if (text2 === "/stop") {
6974
+ await abortRunPiViaServer(options.context, options.runId);
6975
+ appendTranscript(state, "System", "Stop requested for worker Pi daemon.");
6976
+ updatePiUi(ctx, state);
6977
+ ctx.shutdown();
6978
+ return;
6979
+ }
6980
+ if (text2.startsWith("!")) {
6981
+ appendTranscript(state, "You", text2);
6982
+ await sendRunPiShellViaServer(options.context, options.runId, text2);
6983
+ } else if (text2.startsWith("/")) {
6984
+ appendTranscript(state, "You", text2);
6985
+ const result = await runRunPiCommandViaServer(options.context, options.runId, text2);
6986
+ const message2 = typeof result.message === "string" ? result.message : "worker command accepted";
6987
+ appendTranscript(state, "System", message2);
6988
+ } else {
6989
+ appendTranscript(state, "You", text2);
6990
+ await sendRunPiPromptViaServer(options.context, options.runId, text2, state.streaming ? "steer" : undefined);
6991
+ }
6992
+ updatePiUi(ctx, state);
6993
+ }
6994
+ function createRigWorkerPiBridgeExtension(options) {
6995
+ return (pi) => {
6996
+ const state = {
6997
+ transcript: [],
6998
+ status: "starting worker Pi daemon bridge",
6999
+ activity: "",
7000
+ cwd: "",
7001
+ model: "",
7002
+ commands: [],
7003
+ streaming: false,
7004
+ pendingUi: null,
7005
+ wsConnected: false
7006
+ };
7007
+ if (options.initialMessageSent)
7008
+ appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
7009
+ pi.on("session_start", async (_event, ctx) => {
7010
+ updatePiUi(ctx, state);
7011
+ ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
7012
+ ctx.ui.onTerminalInput((data) => {
7013
+ if (data.includes("\x04")) {
7014
+ ctx.shutdown();
7015
+ return { consume: true };
7016
+ }
7017
+ if (!data.includes("\r") && !data.includes(`
7018
+ `))
7019
+ return;
7020
+ const inlineText = data.replace(/[\r\n]+/g, "").trim();
7021
+ const editorText = ctx.ui.getEditorText().trim();
7022
+ const text2 = [editorText, inlineText].filter(Boolean).join(" ").trim();
7023
+ if (!text2)
7024
+ return;
7025
+ ctx.ui.setEditorText("");
7026
+ routeInput(options, ctx, state, text2).catch((error) => {
7027
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
7028
+ updatePiUi(ctx, state);
7029
+ });
7030
+ return { consume: true };
7031
+ });
7032
+ connectWorkerStream(options, ctx, state).catch((error) => {
7033
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
7034
+ updatePiUi(ctx, state);
7035
+ });
7036
+ });
7037
+ pi.on("session_shutdown", () => {});
7038
+ };
7039
+ }
7040
+
7041
+ // packages/cli/src/commands/_pi-frontend.ts
7042
+ function setTemporaryEnv(updates) {
7043
+ const previous = new Map;
7044
+ for (const [key, value] of Object.entries(updates)) {
7045
+ previous.set(key, process.env[key]);
7046
+ process.env[key] = value;
7047
+ }
7048
+ return () => {
7049
+ for (const [key, value] of previous) {
7050
+ if (value === undefined)
7051
+ delete process.env[key];
7052
+ else
7053
+ process.env[key] = value;
7054
+ }
7055
+ };
7056
+ }
7057
+ async function attachRunBundledPiFrontend(context, input) {
7058
+ const tempRoot = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-"));
7059
+ const cwd = join2(tempRoot, "workspace");
7060
+ const agentDir = join2(tempRoot, "agent");
7061
+ const sessionDir = join2(tempRoot, "sessions");
7062
+ const previousCwd = process.cwd();
7063
+ const restoreEnv = setTemporaryEnv({
7064
+ PI_CODING_AGENT_DIR: agentDir,
7065
+ PI_CODING_AGENT_SESSION_DIR: sessionDir,
7066
+ PI_OFFLINE: "1",
7067
+ PI_SKIP_VERSION_CHECK: "1"
7068
+ });
7069
+ let detached = false;
7070
+ try {
7071
+ await Bun.$`mkdir -p ${cwd} ${agentDir} ${sessionDir}`.quiet();
7072
+ process.chdir(cwd);
7073
+ await runPiMain([
7074
+ "--offline",
7075
+ "--no-session",
7076
+ "--no-tools",
7077
+ "--no-builtin-tools",
7078
+ "--no-skills",
7079
+ "--no-prompt-templates",
7080
+ "--no-themes",
7081
+ "--no-context-files",
7082
+ "--no-approve"
7083
+ ], {
7084
+ extensionFactories: [
7085
+ createRigWorkerPiBridgeExtension({
7086
+ context,
7087
+ runId: input.runId,
7088
+ initialMessageSent: input.steered === true
7089
+ })
7090
+ ]
7091
+ });
7092
+ detached = true;
7093
+ } finally {
7094
+ process.chdir(previousCwd);
7095
+ restoreEnv();
7096
+ rmSync5(tempRoot, { recursive: true, force: true });
7097
+ }
7098
+ let run = { runId: input.runId, status: "unknown" };
7099
+ try {
7100
+ run = await getRunDetailsViaServer(context, input.runId);
7101
+ } catch {}
7102
+ return {
7103
+ run,
7104
+ logs: [],
7105
+ timeline: [],
7106
+ timelineCursor: null,
7107
+ steered: input.steered === true,
7108
+ detached,
7109
+ rendered: "actual bundled Pi frontend hosted Rig worker Pi bridge extension"
7110
+ };
7111
+ }
7112
+
7113
+ // packages/cli/src/commands/_operator-view.ts
7114
+ var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6593
7115
  function runStatusFromPayload(payload) {
6594
7116
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6595
7117
  return String(run.status ?? "unknown").toLowerCase();
@@ -6631,9 +7153,15 @@ async function readOperatorSnapshot(context, runId, options = {}) {
6631
7153
  async function attachRunOperatorView(context, input) {
6632
7154
  let steered = false;
6633
7155
  if (input.message?.trim()) {
6634
- await steerRunViaServer(context, input.runId, input.message.trim());
7156
+ await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
6635
7157
  steered = true;
6636
7158
  }
7159
+ if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
7160
+ return attachRunBundledPiFrontend(context, {
7161
+ runId: input.runId,
7162
+ steered
7163
+ });
7164
+ }
6637
7165
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
6638
7166
  let snapshot = await readOperatorSnapshot(context, input.runId);
6639
7167
  if (context.outputMode === "text") {
@@ -6641,7 +7169,7 @@ async function attachRunOperatorView(context, input) {
6641
7169
  surface.renderTimeline(snapshot.timeline);
6642
7170
  surface.renderLogs(snapshot.logs);
6643
7171
  if (steered)
6644
- surface.info("Steering message queued.");
7172
+ surface.info("Message submitted to worker Pi.");
6645
7173
  }
6646
7174
  let detached = false;
6647
7175
  let commandInput = null;
@@ -6660,7 +7188,7 @@ async function attachRunOperatorView(context, input) {
6660
7188
  }
6661
7189
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6662
7190
  let timelineCursor = snapshot.timelineCursor;
6663
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
7191
+ while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
6664
7192
  await Bun.sleep(pollMs);
6665
7193
  snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6666
7194
  timelineCursor = snapshot.timelineCursor;
@@ -6774,92 +7302,6 @@ function formatSubmittedRun(input) {
6774
7302
  `);
6775
7303
  }
6776
7304
 
6777
- // packages/cli/src/commands/_pi-session.ts
6778
- import { spawn as spawn2 } from "child_process";
6779
- function buildPiRigSessionEnv(input) {
6780
- return {
6781
- RIG_PROJECT_ROOT: input.projectRoot,
6782
- PROJECT_RIG_ROOT: input.projectRoot,
6783
- RIG_RUN_ID: input.runId,
6784
- RIG_SERVER_RUN_ID: input.runId,
6785
- RIG_RUNTIME_ADAPTER: "pi",
6786
- RIG_SERVER_URL: input.serverUrl,
6787
- RIG_SERVER_BASE_URL: input.serverUrl,
6788
- RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6789
- RIG_PI_OPERATOR_SESSION: "1",
6790
- ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6791
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6792
- };
6793
- }
6794
- function shellBinary(name) {
6795
- const explicit = process.env.RIG_PI_BINARY?.trim();
6796
- if (explicit)
6797
- return explicit;
6798
- return Bun.which(name) || name;
6799
- }
6800
- function buildPiRigSessionCommand(input) {
6801
- const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6802
- const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6803
- const initialCommand = `/rig attach ${input.runId}`;
6804
- return [
6805
- shellBinary("pi"),
6806
- "--no-extensions",
6807
- "--extension",
6808
- extensionSource,
6809
- initialCommand
6810
- ];
6811
- }
6812
- async function launchPiRigSession(context, input) {
6813
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6814
- return { launched: false, exitCode: null, command: [] };
6815
- }
6816
- if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6817
- return { launched: false, exitCode: null, command: [] };
6818
- }
6819
- const server = await ensureServerForCli(context.projectRoot);
6820
- const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6821
- const env = {
6822
- ...process.env,
6823
- ...buildPiRigSessionEnv({
6824
- projectRoot: context.projectRoot,
6825
- runId: input.runId,
6826
- taskId: input.taskId,
6827
- serverUrl: server.baseUrl,
6828
- authToken: server.authToken
6829
- })
6830
- };
6831
- process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6832
- `);
6833
- process.stdout.write(`Pi command: ${formatCommand(command)}
6834
- `);
6835
- const launchedAt = Date.now();
6836
- const child = spawn2(command[0], command.slice(1), {
6837
- cwd: context.projectRoot,
6838
- env,
6839
- stdio: "inherit"
6840
- });
6841
- const launchError = await new Promise((resolve20) => {
6842
- child.once("error", (error) => {
6843
- resolve20({ error: error.message });
6844
- });
6845
- child.once("close", (code) => resolve20({ code }));
6846
- });
6847
- if ("error" in launchError) {
6848
- process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6849
- `);
6850
- return { launched: false, exitCode: null, command, error: launchError.error };
6851
- }
6852
- const exitCode = launchError.code;
6853
- const elapsedMs = Date.now() - launchedAt;
6854
- if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6855
- const error = `Pi exited during startup with code ${exitCode}.`;
6856
- process.stderr.write(`${error} Falling back to Rig attach view.
6857
- `);
6858
- return { launched: false, exitCode, command, error };
6859
- }
6860
- return { launched: true, exitCode, command };
6861
- }
6862
-
6863
7305
  // packages/cli/src/commands/run.ts
6864
7306
  function normalizeRemoteRunDetails(payload) {
6865
7307
  const run = payload.run;
@@ -7084,20 +7526,13 @@ async function executeRun(context, args) {
7084
7526
  throw new CliError2("run attach requires a run id.", 2);
7085
7527
  }
7086
7528
  let steered = false;
7087
- const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
7088
- if (shouldTryPiAttach && messageOption.value?.trim()) {
7529
+ if (messageOption.value?.trim()) {
7089
7530
  await steerRunViaServer(context, runId, messageOption.value.trim());
7090
7531
  steered = true;
7091
7532
  }
7092
- if (shouldTryPiAttach) {
7093
- const piSession = await launchPiRigSession(context, { runId });
7094
- if (piSession.launched) {
7095
- return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
7096
- }
7097
- }
7098
7533
  const attached = await attachRunOperatorView(context, {
7099
7534
  runId,
7100
- message: shouldTryPiAttach ? null : messageOption.value ?? null,
7535
+ message: null,
7101
7536
  once: once.value,
7102
7537
  follow: follow.value,
7103
7538
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
@@ -7824,20 +8259,7 @@ async function executeTask(context, args, options) {
7824
8259
  let attachDetails = null;
7825
8260
  if (!detachResult.value && context.outputMode === "text") {
7826
8261
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7827
- if (runtimeAdapter === "pi") {
7828
- const piSession = await launchPiRigSession(context, {
7829
- runId: submitted.runId,
7830
- taskId: selectedTaskId,
7831
- title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7832
- runtimeAdapter
7833
- });
7834
- attachDetails = { mode: "pi", ...piSession };
7835
- if (!piSession.launched) {
7836
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7837
- }
7838
- } else {
7839
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7840
- }
8262
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7841
8263
  } else if (context.outputMode === "text") {
7842
8264
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7843
8265
  }
@@ -7924,7 +8346,7 @@ async function executeTask(context, args, options) {
7924
8346
  // packages/cli/src/commands/task-run-driver.ts
7925
8347
  import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7926
8348
  import { resolve as resolve21 } from "path";
7927
- import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
8349
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
7928
8350
  import { createInterface as createLineInterface } from "readline";
7929
8351
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7930
8352
  import {
@@ -7981,6 +8403,7 @@ function buildPiRigBridgeEnv(input) {
7981
8403
  RIG_SERVER_RUN_ID: input.runId,
7982
8404
  RIG_TASK_ID: input.taskId,
7983
8405
  RIG_RUNTIME_ADAPTER: "pi",
8406
+ RIG_STEERING_POLL_MS: "0",
7984
8407
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7985
8408
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7986
8409
  ...githubBridgeEnv(githubToken)
@@ -8152,7 +8575,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
8152
8575
  }
8153
8576
  function createCommandRunner(binary) {
8154
8577
  return async (args, options) => {
8155
- const child = spawn3(binary, [...args], {
8578
+ const child = spawn2(binary, [...args], {
8156
8579
  cwd: options?.cwd,
8157
8580
  stdio: ["ignore", "pipe", "pipe"]
8158
8581
  });
@@ -8576,6 +8999,69 @@ function appendAssistantTimelineFromRecord(input) {
8576
8999
  }
8577
9000
  return nextAssistantText;
8578
9001
  }
9002
+ function appendPiRpcProtocolLogFromRecord(input) {
9003
+ const type = typeof input.record.type === "string" ? input.record.type : "";
9004
+ if (type === "response") {
9005
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
9006
+ const success = input.record.success !== false;
9007
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
9008
+ return true;
9009
+ }
9010
+ appendRunLog(input.projectRoot, input.runId, {
9011
+ id: input.nextRunLogId(),
9012
+ title: success ? "Pi RPC response" : "Pi RPC error",
9013
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
9014
+ tone: success ? "tool" : "error",
9015
+ status: input.status,
9016
+ payload: input.record,
9017
+ createdAt: new Date().toISOString()
9018
+ });
9019
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
9020
+ return true;
9021
+ }
9022
+ if (type !== "extension_ui_request")
9023
+ return false;
9024
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
9025
+ let title = "Pi UI event";
9026
+ let detail = method;
9027
+ let tone = "info";
9028
+ if (method === "notify") {
9029
+ title = "Pi notification";
9030
+ detail = String(input.record.message ?? "");
9031
+ tone = input.record.notifyType === "error" ? "error" : "info";
9032
+ } else if (method === "setStatus") {
9033
+ title = "Pi UI status";
9034
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
9035
+ tone = "tool";
9036
+ } else if (method === "setWidget") {
9037
+ title = "Pi UI widget";
9038
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
9039
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
9040
+ tone = "tool";
9041
+ } else if (method === "setTitle") {
9042
+ title = "Pi UI title";
9043
+ detail = String(input.record.title ?? "");
9044
+ tone = "tool";
9045
+ } else if (method === "set_editor_text") {
9046
+ title = "Pi editor update";
9047
+ detail = String(input.record.text ?? "").slice(0, 500);
9048
+ tone = "tool";
9049
+ } else {
9050
+ title = "Pi UI request";
9051
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
9052
+ }
9053
+ appendRunLog(input.projectRoot, input.runId, {
9054
+ id: input.nextRunLogId(),
9055
+ title,
9056
+ detail,
9057
+ tone,
9058
+ status: input.status,
9059
+ payload: input.record,
9060
+ createdAt: new Date().toISOString()
9061
+ });
9062
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9063
+ return true;
9064
+ }
8579
9065
  function appendPiToolTimelineFromRecord(input) {
8580
9066
  const type = typeof input.record.type === "string" ? input.record.type : "";
8581
9067
  if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
@@ -8594,7 +9080,7 @@ function appendPiToolTimelineFromRecord(input) {
8594
9080
  }
8595
9081
  function isNonRenderablePiProtocolRecord(record) {
8596
9082
  const type = typeof record.type === "string" ? record.type : "";
8597
- 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");
9083
+ 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");
8598
9084
  }
8599
9085
  function appendToolTimelineFromLog(input) {
8600
9086
  const title = typeof input.log.title === "string" ? input.log.title : "";
@@ -8757,11 +9243,7 @@ async function executeRigOwnedTaskRun(context, input) {
8757
9243
  ...input.model ? ["--model", input.model] : [],
8758
9244
  "--prompt"
8759
9245
  ] : input.runtimeAdapter === "pi" ? [
8760
- "--print",
8761
- "--verbose",
8762
- "--mode",
8763
- "json",
8764
- "--no-session",
9246
+ "__rig_pi_session_daemon__",
8765
9247
  ...input.model ? ["--model", input.model] : []
8766
9248
  ] : [
8767
9249
  "--print",
@@ -8858,7 +9340,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8858
9340
  projectRoot: context.projectRoot,
8859
9341
  runId: input.runId,
8860
9342
  stage,
8861
- 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,
9343
+ 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,
8862
9344
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
8863
9345
  });
8864
9346
  }
@@ -8899,6 +9381,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8899
9381
  let latestProviderCommand = null;
8900
9382
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8901
9383
  let snapshotSidecarPromise = null;
9384
+ let wrapperManagesRuntimeSnapshot = false;
8902
9385
  let dirtyBaselineApplied = false;
8903
9386
  const childEnv = {
8904
9387
  ...process.env,
@@ -8951,6 +9434,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8951
9434
  detail: detail ?? "Verifier review is running."
8952
9435
  });
8953
9436
  };
9437
+ const nextRunLogId = createRunLogIdFactory(input.runId);
8954
9438
  const handleWrapperEvent = (rawPayload) => {
8955
9439
  let event = null;
8956
9440
  try {
@@ -8966,6 +9450,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8966
9450
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
8967
9451
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
8968
9452
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
9453
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
8969
9454
  latestRuntimeBranch = runtimeId;
8970
9455
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
8971
9456
  runtimeId: runtimeId ?? null,
@@ -9003,7 +9488,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9003
9488
  });
9004
9489
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
9005
9490
  }
9006
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
9491
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
9007
9492
  snapshotSidecarPromise = (async () => {
9008
9493
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
9009
9494
  projectRoot: context.projectRoot,
@@ -9085,9 +9570,68 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9085
9570
  }
9086
9571
  return true;
9087
9572
  }
9573
+ 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") {
9574
+ 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";
9575
+ 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");
9576
+ appendRunLog(context.projectRoot, input.runId, {
9577
+ id: nextRunLogId(),
9578
+ title,
9579
+ detail,
9580
+ tone: event.type === "pi.session.error" ? "error" : "info",
9581
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9582
+ payload: {
9583
+ eventType: event.type,
9584
+ runId: typeof payload.runId === "string" ? payload.runId : null,
9585
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
9586
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
9587
+ },
9588
+ createdAt: new Date().toISOString()
9589
+ });
9590
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9591
+ return true;
9592
+ }
9593
+ if (event.type === "pi.session.ready") {
9594
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
9595
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
9596
+ if (privateMetadata) {
9597
+ patchAuthorityRun(context.projectRoot, input.runId, {
9598
+ piSession: publicMetadata,
9599
+ piSessionPrivate: privateMetadata
9600
+ });
9601
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
9602
+ appendRunLog(context.projectRoot, input.runId, {
9603
+ id: `log:${input.runId}:pi-session-ready`,
9604
+ title: "Worker Pi session ready",
9605
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
9606
+ tone: "info",
9607
+ status: "running",
9608
+ payload: {
9609
+ sessionId: sessionId || null,
9610
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
9611
+ },
9612
+ createdAt: new Date().toISOString()
9613
+ });
9614
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
9615
+ }
9616
+ return true;
9617
+ }
9618
+ 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") {
9619
+ 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";
9620
+ 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")}`;
9621
+ appendRunLog(context.projectRoot, input.runId, {
9622
+ id: nextRunLogId(),
9623
+ title,
9624
+ detail,
9625
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
9626
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9627
+ payload,
9628
+ createdAt: new Date().toISOString()
9629
+ });
9630
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9631
+ return true;
9632
+ }
9088
9633
  return false;
9089
9634
  };
9090
- const nextRunLogId = createRunLogIdFactory(input.runId);
9091
9635
  const handleAgentStdoutLine = (line) => {
9092
9636
  const trimmed = line.trim();
9093
9637
  if (!trimmed)
@@ -9121,6 +9665,15 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9121
9665
  try {
9122
9666
  const record = JSON.parse(trimmed);
9123
9667
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9668
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
9669
+ projectRoot: context.projectRoot,
9670
+ runId: input.runId,
9671
+ record,
9672
+ status: liveLogStatus,
9673
+ nextRunLogId
9674
+ })) {
9675
+ return;
9676
+ }
9124
9677
  if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
9125
9678
  emitServerRunEvent({ type: "timeline", runId: input.runId });
9126
9679
  return;
@@ -9266,7 +9819,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9266
9819
  }
9267
9820
  for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
9268
9821
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
9269
- const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9822
+ const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9270
9823
  cwd: context.projectRoot,
9271
9824
  env: childEnv,
9272
9825
  stdio: ["pipe", "pipe", "pipe"]
@@ -9593,7 +10146,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9593
10146
  startedAt,
9594
10147
  finishedAt: requestedAt
9595
10148
  });
9596
- return;
10149
+ process.exit(0);
9597
10150
  }
9598
10151
  const runPiPrFeedbackFix = async (message2) => {
9599
10152
  appendPiStageLog({
@@ -9613,7 +10166,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9613
10166
  });
9614
10167
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
9615
10168
  const feedbackCommand = buildHostAgentCommand(message2);
9616
- const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
10169
+ const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9617
10170
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
9618
10171
  env: childEnv,
9619
10172
  stdio: ["pipe", "pipe", "pipe"]