@h-rig/cli 0.0.6-alpha.22 → 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/bin/rig.js CHANGED
@@ -2954,6 +2954,66 @@ async function steerRunViaServer(context, runId, message) {
2954
2954
  });
2955
2955
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
2956
2956
  }
2957
+ async function getRunPiSessionViaServer(context, runId) {
2958
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
2959
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2960
+ }
2961
+ async function getRunPiMessagesViaServer(context, runId) {
2962
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
2963
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
2964
+ }
2965
+ async function getRunPiStatusViaServer(context, runId) {
2966
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
2967
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
2968
+ }
2969
+ async function getRunPiCommandsViaServer(context, runId) {
2970
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
2971
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
2972
+ }
2973
+ async function sendRunPiPromptViaServer(context, runId, text2, streamingBehavior) {
2974
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
2975
+ method: "POST",
2976
+ headers: { "content-type": "application/json" },
2977
+ body: JSON.stringify({ text: text2, streamingBehavior })
2978
+ });
2979
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2980
+ }
2981
+ async function sendRunPiShellViaServer(context, runId, text2) {
2982
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
2983
+ method: "POST",
2984
+ headers: { "content-type": "application/json" },
2985
+ body: JSON.stringify({ text: text2 })
2986
+ });
2987
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
2988
+ }
2989
+ async function runRunPiCommandViaServer(context, runId, text2) {
2990
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
2991
+ method: "POST",
2992
+ headers: { "content-type": "application/json" },
2993
+ body: JSON.stringify({ text: text2 })
2994
+ });
2995
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
2996
+ }
2997
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
2998
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
2999
+ method: "POST",
3000
+ headers: { "content-type": "application/json" },
3001
+ body: JSON.stringify({ requestId, ...valueOrCancel })
3002
+ });
3003
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
3004
+ }
3005
+ async function abortRunPiViaServer(context, runId) {
3006
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
3007
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
3008
+ }
3009
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
3010
+ const server = await ensureServerForCli(context.projectRoot);
3011
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
3012
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
3013
+ if (server.authToken)
3014
+ url.searchParams.set("token", server.authToken);
3015
+ return url.toString();
3016
+ }
2957
3017
  async function submitTaskRunViaServer(context, input) {
2958
3018
  const isTaskRun = Boolean(input.taskId);
2959
3019
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -2986,183 +3046,6 @@ async function submitTaskRunViaServer(context, input) {
2986
3046
  return { runId };
2987
3047
  }
2988
3048
 
2989
- // packages/cli/src/commands/_pi-install.ts
2990
- import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
2991
- import { homedir as homedir3 } from "os";
2992
- import { resolve as resolve10 } from "path";
2993
- var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
2994
- var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
2995
- var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
2996
- export { default } from '@rig/pi-rig';
2997
- `;
2998
- async function defaultCommandRunner(command, options = {}) {
2999
- const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
3000
- const [stdout, stderr, exitCode] = await Promise.all([
3001
- new Response(proc.stdout).text(),
3002
- new Response(proc.stderr).text(),
3003
- proc.exited
3004
- ]);
3005
- return { exitCode, stdout, stderr };
3006
- }
3007
- function resolvePiRigExtensionPath(homeDir) {
3008
- return resolve10(homeDir, ".pi", "agent", "extensions", "pi-rig");
3009
- }
3010
- function resolvePiRigPackageSource(projectRoot, exists = existsSync6) {
3011
- const localPackage = resolve10(projectRoot, "packages", "pi-rig");
3012
- if (exists(resolve10(localPackage, "package.json")))
3013
- return localPackage;
3014
- return `npm:${PI_RIG_PACKAGE_NAME}`;
3015
- }
3016
- function resolvePiHomeDir(inputHomeDir) {
3017
- return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir3();
3018
- }
3019
- function piListContainsPiRig(output) {
3020
- return output.split(/\r?\n/).some((line) => {
3021
- const normalized = line.trim();
3022
- return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
3023
- });
3024
- }
3025
- async function safeRun(runner, command, options) {
3026
- try {
3027
- return await runner(command, options);
3028
- } catch (error) {
3029
- return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
3030
- }
3031
- }
3032
- function splitInstallCommand(value) {
3033
- return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
3034
- }
3035
- async function ensurePiBinaryAvailable(input) {
3036
- const current = await safeRun(input.runner, ["pi", "--version"]);
3037
- if (current.exitCode === 0) {
3038
- const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
3039
- if (updateCommand) {
3040
- const parts2 = splitInstallCommand(updateCommand);
3041
- if (parts2.length > 0)
3042
- await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3043
- }
3044
- return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
3045
- }
3046
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
3047
- const parts = splitInstallCommand(installCommand);
3048
- if (parts.length === 0) {
3049
- return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
3050
- }
3051
- const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
3052
- if (install.exitCode !== 0) {
3053
- return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
3054
- }
3055
- const next = await safeRun(input.runner, ["pi", "--version"]);
3056
- return {
3057
- ok: next.exitCode === 0,
3058
- installedOrUpdated: true,
3059
- detail: (next.stdout || next.stderr).trim() || undefined,
3060
- ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
3061
- };
3062
- }
3063
- function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync6) {
3064
- const extensionPath = resolvePiRigExtensionPath(homeDir);
3065
- const indexPath = resolve10(extensionPath, "index.ts");
3066
- if (!exists(indexPath))
3067
- return;
3068
- try {
3069
- const content = readFileSync4(indexPath, "utf8");
3070
- if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
3071
- rmSync3(extensionPath, { recursive: true, force: true });
3072
- }
3073
- } catch {}
3074
- }
3075
- async function checkPiRigInstall(input = {}) {
3076
- const home = resolvePiHomeDir(input.homeDir);
3077
- const extensionPath = resolvePiRigExtensionPath(home);
3078
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
3079
- return {
3080
- extensionPath,
3081
- pi: { ok: true, label: "pi", detail: "fake-pi" },
3082
- piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
3083
- };
3084
- }
3085
- const exists = input.exists ?? existsSync6;
3086
- const runner = input.commandRunner ?? defaultCommandRunner;
3087
- const piResult = await safeRun(runner, ["pi", "--version"]);
3088
- const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
3089
- const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
3090
- ${piListResult.stderr}`);
3091
- const legacyBridge = exists(resolve10(extensionPath, "index.ts"));
3092
- const hasPiRig = listedPiRig;
3093
- return {
3094
- extensionPath,
3095
- pi: {
3096
- ok: piResult.exitCode === 0,
3097
- label: "pi",
3098
- detail: (piResult.stdout || piResult.stderr).trim() || undefined,
3099
- hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
3100
- },
3101
- piRig: {
3102
- ok: hasPiRig,
3103
- label: "pi-rig global extension",
3104
- detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
3105
- hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
3106
- }
3107
- };
3108
- }
3109
- async function ensurePiRigInstalled(input) {
3110
- const home = resolvePiHomeDir(input.homeDir);
3111
- if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
3112
- const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
3113
- return { ...status2, installedPath: status2.extensionPath };
3114
- }
3115
- const runner = input.commandRunner ?? defaultCommandRunner;
3116
- const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
3117
- const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
3118
- extensionPath: resolvePiRigExtensionPath(home),
3119
- pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
3120
- piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
3121
- };
3122
- if (!piAvailable.ok) {
3123
- throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
3124
- }
3125
- const packageSource = resolvePiRigPackageSource(input.projectRoot);
3126
- removeManagedLegacyPiRigBridge(home);
3127
- const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
3128
- if (install.exitCode !== 0) {
3129
- throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
3130
- }
3131
- const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
3132
- return { ...next, installedPath: packageSource };
3133
- }
3134
- async function ensureRemotePiRigInstalled(input) {
3135
- const payload = await input.requestJson("/api/pi-rig/install", {
3136
- method: "POST",
3137
- headers: { "content-type": "application/json" },
3138
- body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
3139
- });
3140
- const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
3141
- const piOk = record.piOk === true || record.ok === true;
3142
- const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
3143
- const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
3144
- return {
3145
- remote: true,
3146
- extensionPath,
3147
- pi: {
3148
- ok: piOk,
3149
- label: "pi",
3150
- detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
3151
- hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
3152
- },
3153
- piRig: {
3154
- ok: piRigOk,
3155
- label: "pi-rig global extension",
3156
- detail: extensionPath,
3157
- hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
3158
- }
3159
- };
3160
- }
3161
- async function buildPiSetupChecks(input = {}) {
3162
- const status = await checkPiRigInstall(input);
3163
- return [status.pi, status.piRig];
3164
- }
3165
-
3166
3049
  // packages/cli/src/commands/_preflight.ts
3167
3050
  function preflightCheck(id, label, status, detail, remediation) {
3168
3051
  return {
@@ -3320,14 +3203,7 @@ async function runFastTaskRunPreflight(context, options = {}) {
3320
3203
  }
3321
3204
  }
3322
3205
  if ((options.runtimeAdapter ?? "pi") === "pi") {
3323
- const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
3324
- ok: false,
3325
- label: "pi/pi-rig checks",
3326
- hint: message(error)
3327
- }]);
3328
- for (const pi of piChecks) {
3329
- 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.")));
3330
- }
3206
+ checks.push(preflightCheck("runtime", "worker Pi SDK session daemon", "pass", selectedServer?.connectionKind === "remote" ? "remote worker-owned runtime" : "bundled server-owned runtime"));
3331
3207
  } else {
3332
3208
  checks.push(preflightCheck("runtime", "runtime adapter", "pass", options.runtimeAdapter));
3333
3209
  }
@@ -3443,7 +3319,7 @@ async function executeQueue(context, args) {
3443
3319
  }
3444
3320
 
3445
3321
  // packages/cli/src/commands/agent.ts
3446
- import { resolve as resolve12 } from "path";
3322
+ import { resolve as resolve11 } from "path";
3447
3323
  import {
3448
3324
  agentId,
3449
3325
  cleanupAgentRuntime,
@@ -3453,8 +3329,8 @@ import {
3453
3329
  } from "@rig/runtime/control-plane/runtime/isolation";
3454
3330
 
3455
3331
  // packages/cli/src/commands/_authority-runs.ts
3456
- import { existsSync as existsSync7 } from "fs";
3457
- import { resolve as resolve11 } from "path";
3332
+ import { existsSync as existsSync6 } from "fs";
3333
+ import { resolve as resolve10 } from "path";
3458
3334
  import {
3459
3335
  readAuthorityRun,
3460
3336
  readJsonlFile as readJsonlFile2,
@@ -3476,8 +3352,8 @@ function normalizeRuntimeAdapter(value) {
3476
3352
  return "claude-code";
3477
3353
  }
3478
3354
  function readLatestBeadRecord(projectRoot, taskId) {
3479
- const issuesPath = resolve11(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3480
- if (!existsSync7(issuesPath)) {
3355
+ const issuesPath = resolve10(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
3356
+ if (!existsSync6(issuesPath)) {
3481
3357
  return null;
3482
3358
  }
3483
3359
  let latest = null;
@@ -3545,7 +3421,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3545
3421
  } else if ("errorText" in next) {
3546
3422
  delete next.errorText;
3547
3423
  }
3548
- writeJsonFile3(resolve11(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3424
+ writeJsonFile3(resolve10(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
3549
3425
  return next;
3550
3426
  }
3551
3427
 
@@ -3666,10 +3542,10 @@ async function executeAgent(context, args) {
3666
3542
  status: "running",
3667
3543
  startedAt: createdAt,
3668
3544
  worktreePath: runtime.workspaceDir,
3669
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3545
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3670
3546
  logRoot: runtime.logsDir,
3671
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3672
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3547
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3548
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3673
3549
  pid: process.pid
3674
3550
  });
3675
3551
  const result = await runInAgentRuntime({
@@ -3689,10 +3565,10 @@ async function executeAgent(context, args) {
3689
3565
  startedAt: createdAt,
3690
3566
  completedAt: failedAt,
3691
3567
  worktreePath: runtime.workspaceDir,
3692
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3568
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3693
3569
  logRoot: runtime.logsDir,
3694
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3695
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3570
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3571
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3696
3572
  pid: process.pid,
3697
3573
  errorText: result.stderr ? result.stderr.trim() : `Agent runtime command failed (${result.exitCode})`
3698
3574
  });
@@ -3709,10 +3585,10 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3709
3585
  startedAt: createdAt,
3710
3586
  completedAt,
3711
3587
  worktreePath: runtime.workspaceDir,
3712
- artifactRoot: resolve12(runtime.workspaceDir, "artifacts", taskId),
3588
+ artifactRoot: resolve11(runtime.workspaceDir, "artifacts", taskId),
3713
3589
  logRoot: runtime.logsDir,
3714
- sessionPath: resolve12(runtime.sessionDir, "session.json"),
3715
- sessionLogPath: resolve12(runtime.logsDir, "agent-stdout.log"),
3590
+ sessionPath: resolve11(runtime.sessionDir, "session.json"),
3591
+ sessionLogPath: resolve11(runtime.logsDir, "agent-stdout.log"),
3716
3592
  pid: process.pid
3717
3593
  });
3718
3594
  return {
@@ -3786,17 +3662,17 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
3786
3662
  import {
3787
3663
  chmodSync,
3788
3664
  copyFileSync as copyFileSync2,
3789
- existsSync as existsSync8,
3665
+ existsSync as existsSync7,
3790
3666
  mkdirSync as mkdirSync6,
3791
3667
  readdirSync,
3792
3668
  readlinkSync,
3793
- rmSync as rmSync4,
3669
+ rmSync as rmSync3,
3794
3670
  statSync,
3795
3671
  symlinkSync,
3796
3672
  unlinkSync
3797
3673
  } from "fs";
3798
- import { homedir as homedir4 } from "os";
3799
- import { resolve as resolve13 } from "path";
3674
+ import { homedir as homedir3 } from "os";
3675
+ import { resolve as resolve12 } from "path";
3800
3676
  import { buildBinary as buildBinary2 } from "@rig/runtime/control-plane/runtime/isolation";
3801
3677
  import {
3802
3678
  computeRuntimeImageFingerprint,
@@ -3815,13 +3691,13 @@ async function runQuietBinaryProbe(binaryPath, args, cwd) {
3815
3691
 
3816
3692
  // packages/cli/src/commands/dist.ts
3817
3693
  function collectRigValidatorBuildTargets(input) {
3818
- const validatorsRoot = resolve13(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3819
- if (!existsSync8(validatorsRoot))
3694
+ const validatorsRoot = resolve12(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
3695
+ if (!existsSync7(validatorsRoot))
3820
3696
  return [];
3821
3697
  const targets = [];
3822
3698
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
3823
3699
  for (const category of categories) {
3824
- const categoryDir = resolve13(validatorsRoot, category.name);
3700
+ const categoryDir = resolve12(validatorsRoot, category.name);
3825
3701
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
3826
3702
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
3827
3703
  continue;
@@ -3830,7 +3706,7 @@ function collectRigValidatorBuildTargets(input) {
3830
3706
  continue;
3831
3707
  targets.push({
3832
3708
  source: `packages/runtime/src/control-plane/validators/${category.name}/${entry.name}`,
3833
- dest: resolve13(input.imageDir, `bin/validators/${category.name}-${check}`),
3709
+ dest: resolve12(input.imageDir, `bin/validators/${category.name}-${check}`),
3834
3710
  cwd: input.hostProjectRoot
3835
3711
  });
3836
3712
  }
@@ -3839,16 +3715,16 @@ function collectRigValidatorBuildTargets(input) {
3839
3715
  }
3840
3716
  async function findLatestDistBinary(projectRoot) {
3841
3717
  const distRoot = resolveControlPlaneHostDistDir(projectRoot);
3842
- if (!existsSync8(distRoot)) {
3718
+ if (!existsSync7(distRoot)) {
3843
3719
  return null;
3844
3720
  }
3845
3721
  const entries = readdirSync(distRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("rig-")).map((entry) => ({
3846
3722
  name: entry.name,
3847
- mtimeMs: statSync(resolve13(distRoot, entry.name)).mtimeMs
3723
+ mtimeMs: statSync(resolve12(distRoot, entry.name)).mtimeMs
3848
3724
  })).sort((a, b) => b.mtimeMs - a.mtimeMs || b.name.localeCompare(a.name));
3849
3725
  for (const { name } of entries) {
3850
- const candidate = resolve13(distRoot, name, "bin", "rig");
3851
- if (existsSync8(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3726
+ const candidate = resolve12(distRoot, name, "bin", "rig");
3727
+ if (existsSync7(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
3852
3728
  return candidate;
3853
3729
  }
3854
3730
  }
@@ -3860,7 +3736,7 @@ async function isRunnableRigBinary(binaryPath, projectRoot) {
3860
3736
  async function runDistDoctor(projectRoot) {
3861
3737
  const bunPath = Bun.which("bun");
3862
3738
  const rigPath = Bun.which("rig");
3863
- const userBinDir = resolve13(homedir4(), ".local/bin");
3739
+ const userBinDir = resolve12(homedir3(), ".local/bin");
3864
3740
  const userBinInPath = (process.env.PATH || "").split(":").filter(Boolean).includes(userBinDir);
3865
3741
  let rigRunnable = false;
3866
3742
  if (rigPath) {
@@ -3908,15 +3784,15 @@ async function executeDist(context, args) {
3908
3784
  let source = await findLatestDistBinary(context.projectRoot);
3909
3785
  let buildDir = null;
3910
3786
  if (!source) {
3911
- buildDir = resolve13(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3787
+ buildDir = resolve12(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
3912
3788
  await context.runCommand(["bun", "run", "packages/cli/bin/build-rig-binaries.ts", "--output-dir", buildDir]);
3913
- source = resolve13(buildDir, "bin", "rig");
3789
+ source = resolve12(buildDir, "bin", "rig");
3914
3790
  }
3915
- if (!existsSync8(source)) {
3791
+ if (!existsSync7(source)) {
3916
3792
  throw new CliError2(`Unable to locate rig binary at ${source}.`, 2);
3917
3793
  }
3918
- const installedPath = resolve13(installDir, "rig");
3919
- if (existsSync8(installedPath)) {
3794
+ const installedPath = resolve12(installDir, "rig");
3795
+ if (existsSync7(installedPath)) {
3920
3796
  unlinkSync(installedPath);
3921
3797
  }
3922
3798
  copyFileSync2(source, installedPath);
@@ -3958,22 +3834,22 @@ async function executeDist(context, args) {
3958
3834
  requireNoExtraArgs(rest, "bun run rig dist rebuild-agent");
3959
3835
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
3960
3836
  const currentId = computeRuntimeImageId(fp);
3961
- const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3837
+ const imagesDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
3962
3838
  mkdirSync6(imagesDir, { recursive: true });
3963
3839
  let pruned = 0;
3964
3840
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
3965
3841
  if (entry.isDirectory() && entry.name !== currentId) {
3966
- rmSync4(resolve13(imagesDir, entry.name), { recursive: true, force: true });
3842
+ rmSync3(resolve12(imagesDir, entry.name), { recursive: true, force: true });
3967
3843
  pruned++;
3968
3844
  }
3969
3845
  }
3970
3846
  if (pruned > 0 && context.outputMode === "text") {
3971
3847
  console.log(`Pruned ${pruned} stale image(s).`);
3972
3848
  }
3973
- const imageDir = resolve13(imagesDir, currentId);
3974
- mkdirSync6(resolve13(imageDir, "bin/hooks"), { recursive: true });
3975
- mkdirSync6(resolve13(imageDir, "bin/plugins"), { recursive: true });
3976
- mkdirSync6(resolve13(imageDir, "bin/validators"), { recursive: true });
3849
+ const imageDir = resolve12(imagesDir, currentId);
3850
+ mkdirSync6(resolve12(imageDir, "bin/hooks"), { recursive: true });
3851
+ mkdirSync6(resolve12(imageDir, "bin/plugins"), { recursive: true });
3852
+ mkdirSync6(resolve12(imageDir, "bin/validators"), { recursive: true });
3977
3853
  const hookNames = [
3978
3854
  "scope-guard",
3979
3855
  "import-guard",
@@ -3987,25 +3863,25 @@ async function executeDist(context, args) {
3987
3863
  ];
3988
3864
  const targets = [];
3989
3865
  const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || context.projectRoot;
3990
- targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve13(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3991
- targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3866
+ targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve12(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
3867
+ targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
3992
3868
  for (const hookName of hookNames) {
3993
3869
  const src = `packages/runtime/src/control-plane/hooks/${hookName}.ts`;
3994
- targets.push({ source: src, dest: resolve13(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3995
- targets.push({ source: src, dest: resolve13(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3870
+ targets.push({ source: src, dest: resolve12(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
3871
+ targets.push({ source: src, dest: resolve12(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
3996
3872
  }
3997
- const pluginsDir = resolve13(context.projectRoot, "rig/plugins");
3998
- const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3999
- const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
4000
- const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
3873
+ const pluginsDir = resolve12(context.projectRoot, "rig/plugins");
3874
+ const binPluginsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
3875
+ const validatorsRoot = resolve12(hostProjectRoot, "packages/runtime/src/control-plane/validators");
3876
+ const binValidatorsDir = resolve12(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
4001
3877
  mkdirSync6(binPluginsDir, { recursive: true });
4002
3878
  mkdirSync6(binValidatorsDir, { recursive: true });
4003
- if (existsSync8(pluginsDir)) {
3879
+ if (existsSync7(pluginsDir)) {
4004
3880
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
4005
3881
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
4006
3882
  if (!m)
4007
3883
  continue;
4008
- targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve13(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
3884
+ targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve12(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
4009
3885
  }
4010
3886
  }
4011
3887
  targets.push(...collectRigValidatorBuildTargets({ contextProjectRoot: context.projectRoot, hostProjectRoot, imageDir }));
@@ -4016,17 +3892,17 @@ async function executeDist(context, args) {
4016
3892
  const isValidator = dest.includes("/bin/validators/");
4017
3893
  await buildBinary2(source, dest, cwd, isValidator ? { AGENT_BUN_PATH: Bun.which("bun") || "bun" } : { AGENT_PROJECT_ROOT: context.projectRoot });
4018
3894
  }
4019
- if (existsSync8(pluginsDir)) {
3895
+ if (existsSync7(pluginsDir)) {
4020
3896
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
4021
3897
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
4022
3898
  if (!m)
4023
3899
  continue;
4024
3900
  const pluginName = m[1];
4025
- const imageBin = resolve13(imageDir, `bin/plugins/${pluginName}`);
3901
+ const imageBin = resolve12(imageDir, `bin/plugins/${pluginName}`);
4026
3902
  if (!pluginName)
4027
3903
  continue;
4028
- const symlinkPath = resolve13(binPluginsDir, pluginName);
4029
- if (existsSync8(imageBin)) {
3904
+ const symlinkPath = resolve12(binPluginsDir, pluginName);
3905
+ if (existsSync7(imageBin)) {
4030
3906
  try {
4031
3907
  unlinkSync(symlinkPath);
4032
3908
  } catch {}
@@ -4034,10 +3910,10 @@ async function executeDist(context, args) {
4034
3910
  }
4035
3911
  }
4036
3912
  }
4037
- if (existsSync8(validatorsRoot)) {
3913
+ if (existsSync7(validatorsRoot)) {
4038
3914
  const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
4039
3915
  for (const category of categories) {
4040
- const categoryDir = resolve13(validatorsRoot, category.name);
3916
+ const categoryDir = resolve12(validatorsRoot, category.name);
4041
3917
  for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
4042
3918
  if (!entry.isFile() || !entry.name.endsWith(".ts"))
4043
3919
  continue;
@@ -4045,9 +3921,9 @@ async function executeDist(context, args) {
4045
3921
  if (!check || check === "index" || check === "shared")
4046
3922
  continue;
4047
3923
  const validatorName = `${category.name}-${check}`;
4048
- const imageBin = resolve13(imageDir, `bin/validators/${validatorName}`);
4049
- const symlinkPath = resolve13(binValidatorsDir, validatorName);
4050
- if (existsSync8(imageBin)) {
3924
+ const imageBin = resolve12(imageDir, `bin/validators/${validatorName}`);
3925
+ const symlinkPath = resolve12(binValidatorsDir, validatorName);
3926
+ if (existsSync7(imageBin)) {
4051
3927
  try {
4052
3928
  unlinkSync(symlinkPath);
4053
3929
  } catch {}
@@ -4056,18 +3932,18 @@ async function executeDist(context, args) {
4056
3932
  }
4057
3933
  }
4058
3934
  }
4059
- const agentsDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
4060
- if (existsSync8(agentsDir)) {
3935
+ const agentsDir = resolve12(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
3936
+ if (existsSync7(agentsDir)) {
4061
3937
  let relinkCount = 0;
4062
3938
  for (const agentEntry of readdirSync(agentsDir, { withFileTypes: true })) {
4063
3939
  if (!agentEntry.isDirectory())
4064
3940
  continue;
4065
- const agentBinDir = resolve13(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
4066
- if (!existsSync8(agentBinDir))
3941
+ const agentBinDir = resolve12(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
3942
+ if (!existsSync7(agentBinDir))
4067
3943
  continue;
4068
3944
  const walkDir = (dir) => {
4069
3945
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
4070
- const fullPath = resolve13(dir, entry.name);
3946
+ const fullPath = resolve12(dir, entry.name);
4071
3947
  if (entry.isDirectory()) {
4072
3948
  walkDir(fullPath);
4073
3949
  } else if (entry.isSymbolicLink()) {
@@ -4101,7 +3977,7 @@ async function executeDist(context, args) {
4101
3977
 
4102
3978
  // packages/cli/src/commands/inbox.ts
4103
3979
  import { writeFileSync as writeFileSync4 } from "fs";
4104
- import { resolve as resolve14 } from "path";
3980
+ import { resolve as resolve13 } from "path";
4105
3981
  import {
4106
3982
  listAuthorityRuns,
4107
3983
  readJsonlFile as readJsonlFile3,
@@ -4118,7 +3994,7 @@ async function executeInbox(context, args) {
4118
3994
  pending = task.rest;
4119
3995
  requireNoExtraArgs(pending, "bun run rig inbox approvals [--run <id>] [--task <id>]");
4120
3996
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
4121
- const approvals = runs.flatMap((entry) => readJsonlFile3(resolve14(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
3997
+ const approvals = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "approvals.jsonl")).map((record) => ({
4122
3998
  runId: entry.runId,
4123
3999
  record
4124
4000
  })));
@@ -4146,7 +4022,7 @@ async function executeInbox(context, args) {
4146
4022
  if (decision.value !== "approve" && decision.value !== "reject") {
4147
4023
  throw new CliError2("decision must be approve or reject.");
4148
4024
  }
4149
- const approvalsPath = resolve14(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
4025
+ const approvalsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "approvals.jsonl");
4150
4026
  const approvals = readJsonlFile3(approvalsPath);
4151
4027
  const resolvedAt = new Date().toISOString();
4152
4028
  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);
@@ -4163,7 +4039,7 @@ async function executeInbox(context, args) {
4163
4039
  pending = task.rest;
4164
4040
  requireNoExtraArgs(pending, "bun run rig inbox inputs [--run <id>] [--task <id>]");
4165
4041
  const runs = listAuthorityRuns(context.projectRoot).filter((entry) => (!run.value || entry.runId === run.value) && (!task.value || entry.taskId === task.value));
4166
- const requests = runs.flatMap((entry) => readJsonlFile3(resolve14(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
4042
+ const requests = runs.flatMap((entry) => readJsonlFile3(resolve13(resolveAuthorityRunDir2(context.projectRoot, entry.runId), "user-input.jsonl")).map((record) => ({
4167
4043
  runId: entry.runId,
4168
4044
  record
4169
4045
  })));
@@ -4205,7 +4081,7 @@ async function executeInbox(context, args) {
4205
4081
  const [key, ...restValue] = entry.split("=");
4206
4082
  return [key, restValue.join("=")];
4207
4083
  }));
4208
- const requestsPath = resolve14(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
4084
+ const requestsPath = resolve13(resolveAuthorityRunDir2(context.projectRoot, run.value), "user-input.jsonl");
4209
4085
  const requests = readJsonlFile3(requestsPath);
4210
4086
  const resolvedAt = new Date().toISOString();
4211
4087
  const next = requests.map((entry) => entry.requestId === request.value || entry.id === request.value ? { ...entry, status: "resolved", answers: parsedAnswers, respondedAt: resolvedAt, resolvedAt } : entry);
@@ -4218,13 +4094,190 @@ async function executeInbox(context, args) {
4218
4094
  throw new CliError2(`Unknown inbox command: ${command}`);
4219
4095
  }
4220
4096
  }
4221
-
4222
- // packages/cli/src/commands/init.ts
4223
- import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4224
- import { spawnSync } from "child_process";
4225
- import { resolve as resolve17 } from "path";
4226
- import { buildRigInitConfigSource } from "@rig/core";
4227
- import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4097
+
4098
+ // packages/cli/src/commands/init.ts
4099
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4100
+ import { spawnSync } from "child_process";
4101
+ import { resolve as resolve17 } from "path";
4102
+ import { buildRigInitConfigSource } from "@rig/core";
4103
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
4104
+
4105
+ // packages/cli/src/commands/_pi-install.ts
4106
+ import { existsSync as existsSync8, readFileSync as readFileSync4, rmSync as rmSync4 } from "fs";
4107
+ import { homedir as homedir4 } from "os";
4108
+ import { resolve as resolve14 } from "path";
4109
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
4110
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
4111
+ var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
4112
+ export { default } from '@rig/pi-rig';
4113
+ `;
4114
+ async function defaultCommandRunner(command, options = {}) {
4115
+ const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
4116
+ const [stdout, stderr, exitCode] = await Promise.all([
4117
+ new Response(proc.stdout).text(),
4118
+ new Response(proc.stderr).text(),
4119
+ proc.exited
4120
+ ]);
4121
+ return { exitCode, stdout, stderr };
4122
+ }
4123
+ function resolvePiRigExtensionPath(homeDir) {
4124
+ return resolve14(homeDir, ".pi", "agent", "extensions", "pi-rig");
4125
+ }
4126
+ function resolvePiRigPackageSource(projectRoot, exists = existsSync8) {
4127
+ const localPackage = resolve14(projectRoot, "packages", "pi-rig");
4128
+ if (exists(resolve14(localPackage, "package.json")))
4129
+ return localPackage;
4130
+ return `npm:${PI_RIG_PACKAGE_NAME}`;
4131
+ }
4132
+ function resolvePiHomeDir(inputHomeDir) {
4133
+ return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir4();
4134
+ }
4135
+ function piListContainsPiRig(output) {
4136
+ return output.split(/\r?\n/).some((line) => {
4137
+ const normalized = line.trim();
4138
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
4139
+ });
4140
+ }
4141
+ async function safeRun(runner, command, options) {
4142
+ try {
4143
+ return await runner(command, options);
4144
+ } catch (error) {
4145
+ return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
4146
+ }
4147
+ }
4148
+ function splitInstallCommand(value) {
4149
+ return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
4150
+ }
4151
+ async function ensurePiBinaryAvailable(input) {
4152
+ const current = await safeRun(input.runner, ["pi", "--version"]);
4153
+ if (current.exitCode === 0) {
4154
+ const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
4155
+ if (updateCommand) {
4156
+ const parts2 = splitInstallCommand(updateCommand);
4157
+ if (parts2.length > 0)
4158
+ await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
4159
+ }
4160
+ return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
4161
+ }
4162
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
4163
+ const parts = splitInstallCommand(installCommand);
4164
+ if (parts.length === 0) {
4165
+ return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
4166
+ }
4167
+ const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
4168
+ if (install.exitCode !== 0) {
4169
+ return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
4170
+ }
4171
+ const next = await safeRun(input.runner, ["pi", "--version"]);
4172
+ return {
4173
+ ok: next.exitCode === 0,
4174
+ installedOrUpdated: true,
4175
+ detail: (next.stdout || next.stderr).trim() || undefined,
4176
+ ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
4177
+ };
4178
+ }
4179
+ function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync8) {
4180
+ const extensionPath = resolvePiRigExtensionPath(homeDir);
4181
+ const indexPath = resolve14(extensionPath, "index.ts");
4182
+ if (!exists(indexPath))
4183
+ return;
4184
+ try {
4185
+ const content = readFileSync4(indexPath, "utf8");
4186
+ if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
4187
+ rmSync4(extensionPath, { recursive: true, force: true });
4188
+ }
4189
+ } catch {}
4190
+ }
4191
+ async function checkPiRigInstall(input = {}) {
4192
+ const home = resolvePiHomeDir(input.homeDir);
4193
+ const extensionPath = resolvePiRigExtensionPath(home);
4194
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
4195
+ return {
4196
+ extensionPath,
4197
+ pi: { ok: true, label: "pi", detail: "fake-pi" },
4198
+ piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
4199
+ };
4200
+ }
4201
+ const exists = input.exists ?? existsSync8;
4202
+ const runner = input.commandRunner ?? defaultCommandRunner;
4203
+ const piResult = await safeRun(runner, ["pi", "--version"]);
4204
+ const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
4205
+ const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
4206
+ ${piListResult.stderr}`);
4207
+ const legacyBridge = exists(resolve14(extensionPath, "index.ts"));
4208
+ const hasPiRig = listedPiRig;
4209
+ return {
4210
+ extensionPath,
4211
+ pi: {
4212
+ ok: piResult.exitCode === 0,
4213
+ label: "pi",
4214
+ detail: (piResult.stdout || piResult.stderr).trim() || undefined,
4215
+ hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
4216
+ },
4217
+ piRig: {
4218
+ ok: hasPiRig,
4219
+ label: "pi-rig global extension",
4220
+ detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
4221
+ hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
4222
+ }
4223
+ };
4224
+ }
4225
+ async function ensurePiRigInstalled(input) {
4226
+ const home = resolvePiHomeDir(input.homeDir);
4227
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
4228
+ const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
4229
+ return { ...status2, installedPath: status2.extensionPath };
4230
+ }
4231
+ const runner = input.commandRunner ?? defaultCommandRunner;
4232
+ const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
4233
+ const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
4234
+ extensionPath: resolvePiRigExtensionPath(home),
4235
+ pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
4236
+ piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
4237
+ };
4238
+ if (!piAvailable.ok) {
4239
+ throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
4240
+ }
4241
+ const packageSource = resolvePiRigPackageSource(input.projectRoot);
4242
+ removeManagedLegacyPiRigBridge(home);
4243
+ const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
4244
+ if (install.exitCode !== 0) {
4245
+ throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
4246
+ }
4247
+ const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
4248
+ return { ...next, installedPath: packageSource };
4249
+ }
4250
+ async function ensureRemotePiRigInstalled(input) {
4251
+ const payload = await input.requestJson("/api/pi-rig/install", {
4252
+ method: "POST",
4253
+ headers: { "content-type": "application/json" },
4254
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
4255
+ });
4256
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
4257
+ const piOk = record.piOk === true || record.ok === true;
4258
+ const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
4259
+ const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
4260
+ return {
4261
+ remote: true,
4262
+ extensionPath,
4263
+ pi: {
4264
+ ok: piOk,
4265
+ label: "pi",
4266
+ detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
4267
+ hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
4268
+ },
4269
+ piRig: {
4270
+ ok: piRigOk,
4271
+ label: "pi-rig global extension",
4272
+ detail: extensionPath,
4273
+ hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
4274
+ }
4275
+ };
4276
+ }
4277
+ async function buildPiSetupChecks(input = {}) {
4278
+ const status = await checkPiRigInstall(input);
4279
+ return [status.pi, status.piRig];
4280
+ }
4228
4281
 
4229
4282
  // packages/cli/src/commands/_snapshot-upload.ts
4230
4283
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
@@ -6592,52 +6645,477 @@ async function promptForTaskSelection(question) {
6592
6645
  }
6593
6646
  }
6594
6647
 
6595
- // packages/cli/src/commands/_operator-view.ts
6648
+ // packages/cli/src/commands/_pi-frontend.ts
6649
+ import { mkdtempSync, rmSync as rmSync5 } from "fs";
6650
+ import { tmpdir } from "os";
6651
+ import { join as join2 } from "path";
6652
+ import { main as runPiMain } from "@earendil-works/pi-coding-agent";
6653
+
6654
+ // packages/cli/src/commands/_pi-worker-bridge-extension.ts
6596
6655
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6597
- var CANONICAL_STAGES2 = [
6598
- "Connect",
6599
- "GitHub/task sync",
6600
- "Prepare workspace",
6601
- "Launch Pi",
6602
- "Plan",
6603
- "Implement",
6604
- "Validate",
6605
- "Commit",
6606
- "Open PR",
6607
- "Review/CI",
6608
- "Merge",
6609
- "Complete"
6610
- ];
6611
- var GREEN = "\x1B[32m";
6612
- var BLUE = "\x1B[34m";
6613
- var MAGENTA = "\x1B[35m";
6614
- var YELLOW = "\x1B[33m";
6615
- var RED = "\x1B[31m";
6616
- var DIM = "\x1B[2m";
6617
- var BOLD = "\x1B[1m";
6618
- var RESET = "\x1B[0m";
6619
- async function loadPiTuiRuntime() {
6656
+ var MAX_TRANSCRIPT_LINES = 120;
6657
+ function recordOf(value) {
6658
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6659
+ }
6660
+ function asText(value) {
6661
+ if (typeof value === "string")
6662
+ return value;
6663
+ if (value === null || value === undefined)
6664
+ return "";
6665
+ if (typeof value === "number" || typeof value === "boolean")
6666
+ return String(value);
6620
6667
  try {
6621
- return await import("@earendil-works/pi-tui");
6668
+ return JSON.stringify(value);
6622
6669
  } catch {
6623
- const base = new URL("../../../pi/packages/tui/src/", import.meta.url);
6624
- const [tui, input, terminal, keys, utils] = await Promise.all([
6625
- import(new URL("tui.ts", base).href),
6626
- import(new URL("components/input.ts", base).href),
6627
- import(new URL("terminal.ts", base).href),
6628
- import(new URL("keys.ts", base).href),
6629
- import(new URL("utils.ts", base).href)
6670
+ return String(value);
6671
+ }
6672
+ }
6673
+ function textFromContent(content) {
6674
+ if (typeof content === "string")
6675
+ return content;
6676
+ if (!Array.isArray(content))
6677
+ return asText(content);
6678
+ return content.flatMap((part) => {
6679
+ const item = recordOf(part);
6680
+ if (!item)
6681
+ return [];
6682
+ if (typeof item.text === "string")
6683
+ return [item.text];
6684
+ if (typeof item.content === "string")
6685
+ return [item.content];
6686
+ if (item.type === "toolCall")
6687
+ return [`\u23FA ${String(item.name ?? "tool")} ${asText(item.arguments ?? "")}`.trim()];
6688
+ if (item.type === "toolResult")
6689
+ return [`\u21B3 ${asText(item.content ?? item.result ?? "")}`.trim()];
6690
+ return [];
6691
+ }).join(`
6692
+ `);
6693
+ }
6694
+ function appendTranscript(state, label, text2) {
6695
+ const trimmed = text2.trimEnd();
6696
+ if (!trimmed)
6697
+ return;
6698
+ const lines = trimmed.split(/\r?\n/);
6699
+ state.transcript.push(`${label}: ${lines[0] ?? ""}`);
6700
+ for (const line of lines.slice(1))
6701
+ state.transcript.push(` ${line}`);
6702
+ if (state.transcript.length > MAX_TRANSCRIPT_LINES) {
6703
+ state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
6704
+ }
6705
+ }
6706
+ function parseExtensionUiRequest(value) {
6707
+ const request = recordOf(value) ?? {};
6708
+ const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
6709
+ const method = String(request.method ?? request.type ?? "input");
6710
+ const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
6711
+ const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
6712
+ const options = rawOptions.map((option) => {
6713
+ const record = recordOf(option);
6714
+ return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
6715
+ }).filter(Boolean);
6716
+ return { requestId, method, prompt, options };
6717
+ }
6718
+ function renderBridgeWidget(state) {
6719
+ const statusParts = [
6720
+ state.wsConnected ? "live WS" : "WS pending",
6721
+ state.status,
6722
+ state.model,
6723
+ state.cwd
6724
+ ].filter(Boolean);
6725
+ const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
6726
+ if (state.activity)
6727
+ lines.push(state.activity);
6728
+ if (state.commands.length > 0) {
6729
+ lines.push(`Worker commands: ${state.commands.slice(0, 10).join(", ")}${state.commands.length > 10 ? ", \u2026" : ""}`);
6730
+ }
6731
+ lines.push("");
6732
+ if (state.transcript.length > 0) {
6733
+ lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
6734
+ } else {
6735
+ lines.push("Waiting for worker Pi daemon transcript\u2026");
6736
+ }
6737
+ if (state.pendingUi) {
6738
+ lines.push("");
6739
+ lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
6740
+ lines.push(state.pendingUi.prompt);
6741
+ state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
6742
+ lines.push("Reply in the Pi editor. /cancel cancels this request.");
6743
+ }
6744
+ return lines;
6745
+ }
6746
+ function updatePiUi(ctx, state) {
6747
+ ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
6748
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
6749
+ ctx.ui.setWorkingVisible(false);
6750
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
6751
+ }
6752
+ function applyStatus(state, payload) {
6753
+ const status = recordOf(payload.status) ?? payload;
6754
+ state.streaming = status.isStreaming === true || status.isCompacting === true || status.isBashRunning === true;
6755
+ state.cwd = typeof status.cwd === "string" ? status.cwd : state.cwd;
6756
+ state.model = typeof status.model === "string" ? status.model : state.model;
6757
+ const pending = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
6758
+ state.status = `${state.streaming ? "streaming" : "idle"}${pending ? ` \xB7 ${pending} queued` : ""}`;
6759
+ }
6760
+ function applyMessage(state, message2) {
6761
+ const record = recordOf(message2);
6762
+ if (!record)
6763
+ return;
6764
+ const role = String(record.role ?? "system");
6765
+ const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
6766
+ appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
6767
+ }
6768
+ function applyPiEvent(state, eventValue) {
6769
+ const event = recordOf(eventValue);
6770
+ if (!event)
6771
+ return;
6772
+ const type = String(event.type ?? "event");
6773
+ if (type === "agent_start") {
6774
+ state.streaming = true;
6775
+ state.status = "streaming";
6776
+ return;
6777
+ }
6778
+ if (type === "agent_end") {
6779
+ state.streaming = false;
6780
+ state.status = "idle";
6781
+ appendTranscript(state, "System", "Agent turn complete.");
6782
+ return;
6783
+ }
6784
+ if (type === "message_start" || type === "message_end" || type === "turn_end") {
6785
+ applyMessage(state, event.message);
6786
+ return;
6787
+ }
6788
+ if (type === "message_update") {
6789
+ const assistantEvent = recordOf(event.assistantMessageEvent);
6790
+ const delta = typeof assistantEvent?.delta === "string" ? assistantEvent.delta : typeof assistantEvent?.text === "string" ? assistantEvent.text : "";
6791
+ if (delta)
6792
+ appendTranscript(state, assistantEvent?.type === "thinking_delta" ? "Thinking" : "Pi", delta);
6793
+ return;
6794
+ }
6795
+ if (type === "tool_execution_start") {
6796
+ appendTranscript(state, "Tool", `${String(event.toolName ?? "tool")} ${asText(event.args ?? "")}`.trim());
6797
+ return;
6798
+ }
6799
+ if (type === "tool_execution_update") {
6800
+ appendTranscript(state, "Tool", asText(event.partialResult ?? ""));
6801
+ return;
6802
+ }
6803
+ if (type === "tool_execution_end") {
6804
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
6805
+ return;
6806
+ }
6807
+ if (type === "queue_update") {
6808
+ const steering = Array.isArray(event.steering) ? event.steering.length : 0;
6809
+ const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
6810
+ state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
6811
+ }
6812
+ }
6813
+ function applyUiEvent(state, value) {
6814
+ const event = recordOf(value);
6815
+ if (!event)
6816
+ return;
6817
+ const type = String(event.type ?? "ui");
6818
+ if (type === "shell.start")
6819
+ appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
6820
+ else if (type === "shell.chunk")
6821
+ appendTranscript(state, "Tool", asText(event.chunk));
6822
+ else if (type === "shell.end")
6823
+ appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
6824
+ else
6825
+ appendTranscript(state, "System", `${type}: ${asText(event)}`);
6826
+ }
6827
+ function applyEnvelope(state, envelopeValue) {
6828
+ const envelope = recordOf(envelopeValue);
6829
+ if (!envelope)
6830
+ return;
6831
+ const type = String(envelope.type ?? "");
6832
+ if (type === "ready") {
6833
+ const metadata = recordOf(envelope.metadata);
6834
+ state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
6835
+ state.status = "worker Pi daemon ready";
6836
+ appendTranscript(state, "System", "Connected to worker Pi daemon.");
6837
+ } else if (type === "status.update") {
6838
+ applyStatus(state, envelope);
6839
+ } else if (type === "activity.update") {
6840
+ const activity = recordOf(envelope.activity);
6841
+ state.activity = [activity?.label, activity?.detail].map(asText).filter(Boolean).join(" \u2014 ");
6842
+ } else if (type === "extension_ui_request") {
6843
+ state.pendingUi = parseExtensionUiRequest(envelope.request);
6844
+ appendTranscript(state, "System", `Extension UI request: ${state.pendingUi.prompt}`);
6845
+ } else if (type === "pi.ui_event") {
6846
+ applyUiEvent(state, envelope.event);
6847
+ } else if (type === "pi.event") {
6848
+ applyPiEvent(state, envelope.event);
6849
+ } else if (type === "error") {
6850
+ appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
6851
+ }
6852
+ }
6853
+ async function waitForWorkerReady(options, ctx, state) {
6854
+ while (true) {
6855
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
6856
+ ready: false,
6857
+ status: error instanceof Error ? error.message : String(error),
6858
+ retryAfterMs: 1000
6859
+ }));
6860
+ if (session.ready === false) {
6861
+ const status = String(session.status ?? "starting");
6862
+ state.status = `waiting for worker Pi daemon \xB7 ${status}`;
6863
+ updatePiUi(ctx, state);
6864
+ if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
6865
+ appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
6866
+ return false;
6867
+ }
6868
+ await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
6869
+ continue;
6870
+ }
6871
+ const sessionRecord = recordOf(session) ?? {};
6872
+ applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
6873
+ updatePiUi(ctx, state);
6874
+ return true;
6875
+ }
6876
+ }
6877
+ function parseWsPayload(message2) {
6878
+ if (typeof message2.data === "string")
6879
+ return JSON.parse(message2.data);
6880
+ return JSON.parse(Buffer.from(message2.data).toString("utf8"));
6881
+ }
6882
+ async function connectWorkerStream(options, ctx, state) {
6883
+ const ready = await waitForWorkerReady(options, ctx, state);
6884
+ if (!ready)
6885
+ return;
6886
+ let catchupDone = false;
6887
+ const buffered = [];
6888
+ const wsUrl = await buildRunPiEventsWebSocketUrl(options.context, options.runId);
6889
+ const socket = new WebSocket(wsUrl);
6890
+ const closePromise = new Promise((resolve20) => {
6891
+ socket.onopen = () => {
6892
+ state.wsConnected = true;
6893
+ state.status = "live worker Pi WebSocket connected";
6894
+ updatePiUi(ctx, state);
6895
+ };
6896
+ socket.onmessage = (message2) => {
6897
+ try {
6898
+ const payload = parseWsPayload(message2);
6899
+ if (!catchupDone)
6900
+ buffered.push(payload);
6901
+ else {
6902
+ applyEnvelope(state, payload);
6903
+ updatePiUi(ctx, state);
6904
+ }
6905
+ } catch (error) {
6906
+ appendTranscript(state, "Error", `Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
6907
+ updatePiUi(ctx, state);
6908
+ }
6909
+ };
6910
+ socket.onerror = () => socket.close();
6911
+ socket.onclose = () => {
6912
+ state.wsConnected = false;
6913
+ state.status = "worker Pi WebSocket disconnected";
6914
+ updatePiUi(ctx, state);
6915
+ resolve20();
6916
+ };
6917
+ });
6918
+ try {
6919
+ const [messagesPayload, statusPayload, commandsPayload] = await Promise.all([
6920
+ getRunPiMessagesViaServer(options.context, options.runId),
6921
+ getRunPiStatusViaServer(options.context, options.runId),
6922
+ getRunPiCommandsViaServer(options.context, options.runId)
6630
6923
  ]);
6631
- return {
6632
- Container: tui.Container,
6633
- TUI: tui.TUI,
6634
- Input: input.Input,
6635
- ProcessTerminal: terminal.ProcessTerminal,
6636
- matchesKey: keys.matchesKey,
6637
- truncateToWidth: utils.truncateToWidth
6924
+ const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
6925
+ for (const message2 of messages)
6926
+ applyMessage(state, message2);
6927
+ applyStatus(state, statusPayload);
6928
+ const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
6929
+ state.commands = commands.flatMap((command) => {
6930
+ const record = recordOf(command);
6931
+ return typeof record?.name === "string" ? [`/${record.name}`] : [];
6932
+ });
6933
+ catchupDone = true;
6934
+ for (const payload of buffered.splice(0))
6935
+ applyEnvelope(state, payload);
6936
+ updatePiUi(ctx, state);
6937
+ } catch (error) {
6938
+ appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
6939
+ catchupDone = true;
6940
+ updatePiUi(ctx, state);
6941
+ }
6942
+ await closePromise;
6943
+ }
6944
+ async function answerPendingUi(options, state, line) {
6945
+ const pending = state.pendingUi;
6946
+ if (!pending)
6947
+ return false;
6948
+ if (line === "/cancel") {
6949
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { cancelled: true });
6950
+ } else if (pending.method === "confirm") {
6951
+ const confirmed = /^(y|yes|true|1)$/i.test(line);
6952
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: confirmed, confirmed });
6953
+ } else if (pending.options.length > 0 && /^\d+$/.test(line)) {
6954
+ const selected = pending.options[Math.max(0, Number(line) - 1)] ?? line;
6955
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: selected });
6956
+ } else {
6957
+ await respondRunPiExtensionUiViaServer(options.context, options.runId, pending.requestId, { value: line });
6958
+ }
6959
+ appendTranscript(state, "System", `Responded to extension UI request ${pending.requestId}.`);
6960
+ state.pendingUi = null;
6961
+ return true;
6962
+ }
6963
+ async function routeInput(options, ctx, state, line) {
6964
+ const text2 = line.trim();
6965
+ if (!text2)
6966
+ return;
6967
+ if (await answerPendingUi(options, state, text2)) {
6968
+ updatePiUi(ctx, state);
6969
+ return;
6970
+ }
6971
+ if (text2 === "/detach" || text2 === "/quit" || text2 === "/q") {
6972
+ appendTranscript(state, "System", "Detached locally; worker Pi daemon continues.");
6973
+ updatePiUi(ctx, state);
6974
+ ctx.shutdown();
6975
+ return;
6976
+ }
6977
+ if (text2 === "/stop") {
6978
+ await abortRunPiViaServer(options.context, options.runId);
6979
+ appendTranscript(state, "System", "Stop requested for worker Pi daemon.");
6980
+ updatePiUi(ctx, state);
6981
+ ctx.shutdown();
6982
+ return;
6983
+ }
6984
+ if (text2.startsWith("!")) {
6985
+ appendTranscript(state, "You", text2);
6986
+ await sendRunPiShellViaServer(options.context, options.runId, text2);
6987
+ } else if (text2.startsWith("/")) {
6988
+ appendTranscript(state, "You", text2);
6989
+ const result = await runRunPiCommandViaServer(options.context, options.runId, text2);
6990
+ const message2 = typeof result.message === "string" ? result.message : "worker command accepted";
6991
+ appendTranscript(state, "System", message2);
6992
+ } else {
6993
+ appendTranscript(state, "You", text2);
6994
+ await sendRunPiPromptViaServer(options.context, options.runId, text2, state.streaming ? "steer" : undefined);
6995
+ }
6996
+ updatePiUi(ctx, state);
6997
+ }
6998
+ function createRigWorkerPiBridgeExtension(options) {
6999
+ return (pi) => {
7000
+ const state = {
7001
+ transcript: [],
7002
+ status: "starting worker Pi daemon bridge",
7003
+ activity: "",
7004
+ cwd: "",
7005
+ model: "",
7006
+ commands: [],
7007
+ streaming: false,
7008
+ pendingUi: null,
7009
+ wsConnected: false
6638
7010
  };
7011
+ if (options.initialMessageSent)
7012
+ appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
7013
+ pi.on("session_start", async (_event, ctx) => {
7014
+ updatePiUi(ctx, state);
7015
+ ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
7016
+ ctx.ui.onTerminalInput((data) => {
7017
+ if (data.includes("\x04")) {
7018
+ ctx.shutdown();
7019
+ return { consume: true };
7020
+ }
7021
+ if (!data.includes("\r") && !data.includes(`
7022
+ `))
7023
+ return;
7024
+ const inlineText = data.replace(/[\r\n]+/g, "").trim();
7025
+ const editorText = ctx.ui.getEditorText().trim();
7026
+ const text2 = [editorText, inlineText].filter(Boolean).join(" ").trim();
7027
+ if (!text2)
7028
+ return;
7029
+ ctx.ui.setEditorText("");
7030
+ routeInput(options, ctx, state, text2).catch((error) => {
7031
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
7032
+ updatePiUi(ctx, state);
7033
+ });
7034
+ return { consume: true };
7035
+ });
7036
+ connectWorkerStream(options, ctx, state).catch((error) => {
7037
+ appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
7038
+ updatePiUi(ctx, state);
7039
+ });
7040
+ });
7041
+ pi.on("session_shutdown", () => {});
7042
+ };
7043
+ }
7044
+
7045
+ // packages/cli/src/commands/_pi-frontend.ts
7046
+ function setTemporaryEnv(updates) {
7047
+ const previous = new Map;
7048
+ for (const [key, value] of Object.entries(updates)) {
7049
+ previous.set(key, process.env[key]);
7050
+ process.env[key] = value;
7051
+ }
7052
+ return () => {
7053
+ for (const [key, value] of previous) {
7054
+ if (value === undefined)
7055
+ delete process.env[key];
7056
+ else
7057
+ process.env[key] = value;
7058
+ }
7059
+ };
7060
+ }
7061
+ async function attachRunBundledPiFrontend(context, input) {
7062
+ const tempRoot = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-"));
7063
+ const cwd = join2(tempRoot, "workspace");
7064
+ const agentDir = join2(tempRoot, "agent");
7065
+ const sessionDir = join2(tempRoot, "sessions");
7066
+ const previousCwd = process.cwd();
7067
+ const restoreEnv = setTemporaryEnv({
7068
+ PI_CODING_AGENT_DIR: agentDir,
7069
+ PI_CODING_AGENT_SESSION_DIR: sessionDir,
7070
+ PI_OFFLINE: "1",
7071
+ PI_SKIP_VERSION_CHECK: "1"
7072
+ });
7073
+ let detached = false;
7074
+ try {
7075
+ await Bun.$`mkdir -p ${cwd} ${agentDir} ${sessionDir}`.quiet();
7076
+ process.chdir(cwd);
7077
+ await runPiMain([
7078
+ "--offline",
7079
+ "--no-session",
7080
+ "--no-tools",
7081
+ "--no-builtin-tools",
7082
+ "--no-skills",
7083
+ "--no-prompt-templates",
7084
+ "--no-themes",
7085
+ "--no-context-files",
7086
+ "--no-approve"
7087
+ ], {
7088
+ extensionFactories: [
7089
+ createRigWorkerPiBridgeExtension({
7090
+ context,
7091
+ runId: input.runId,
7092
+ initialMessageSent: input.steered === true
7093
+ })
7094
+ ]
7095
+ });
7096
+ detached = true;
7097
+ } finally {
7098
+ process.chdir(previousCwd);
7099
+ restoreEnv();
7100
+ rmSync5(tempRoot, { recursive: true, force: true });
6639
7101
  }
7102
+ let run = { runId: input.runId, status: "unknown" };
7103
+ try {
7104
+ run = await getRunDetailsViaServer(context, input.runId);
7105
+ } catch {}
7106
+ return {
7107
+ run,
7108
+ logs: [],
7109
+ timeline: [],
7110
+ timelineCursor: null,
7111
+ steered: input.steered === true,
7112
+ detached,
7113
+ rendered: "actual bundled Pi frontend hosted Rig worker Pi bridge extension"
7114
+ };
6640
7115
  }
7116
+
7117
+ // packages/cli/src/commands/_operator-view.ts
7118
+ var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6641
7119
  function runStatusFromPayload(payload) {
6642
7120
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6643
7121
  return String(run.status ?? "unknown").toLowerCase();
@@ -6676,198 +7154,15 @@ async function readOperatorSnapshot(context, runId, options = {}) {
6676
7154
  const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6677
7155
  return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6678
7156
  }
6679
- function unwrapRun(runPayload) {
6680
- return runPayload.run && typeof runPayload.run === "object" && !Array.isArray(runPayload.run) ? runPayload.run : runPayload;
6681
- }
6682
- function logDetail2(log3) {
6683
- return typeof log3.detail === "string" ? log3.detail.trim() : "";
6684
- }
6685
- function logTitle(log3) {
6686
- return typeof log3.title === "string" ? log3.title.trim() : "";
6687
- }
6688
- function renderAssistantTextFromTimeline(entries) {
6689
- const assistant = entries.filter((entry) => entry.type === "assistant_message" && typeof entry.text === "string").at(-1);
6690
- const text2 = typeof assistant?.text === "string" ? assistant.text.trimEnd() : "";
6691
- if (!text2)
6692
- return [];
6693
- return [`${BLUE}${BOLD}Remote Pi assistant${RESET}`, ...text2.split(/\r?\n/).slice(-18)];
6694
- }
6695
- function renderToolLines(entries) {
6696
- return entries.filter((entry) => entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call").slice(-8).map((entry) => `${DIM}[tool]${RESET} ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
6697
- }
6698
- function renderStageLines(logs) {
6699
- const latestByStage = new Map;
6700
- for (const log3 of logs) {
6701
- const title = logTitle(log3).toLowerCase();
6702
- const stageName = String(log3.stage ?? "").toLowerCase();
6703
- const stage = CANONICAL_STAGES2.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6704
- if (stage)
6705
- latestByStage.set(stage, log3);
6706
- }
6707
- return CANONICAL_STAGES2.map((stage) => {
6708
- const log3 = latestByStage.get(stage);
6709
- const status = String(log3?.status ?? "pending");
6710
- const detail = log3 ? logDetail2(log3) : "";
6711
- const color = status === "completed" ? GREEN : status === "failed" || status === "rejected" ? RED : status === "pending" ? DIM : YELLOW;
6712
- const mark = status === "completed" ? "\u2713" : status === "pending" ? "\xB7" : status === "failed" ? "\u2717" : "\u25B6";
6713
- return `${color}${mark} ${stage}${RESET}${detail ? ` ${DIM}\u2014 ${detail.slice(0, 140)}${RESET}` : ""}`;
6714
- });
6715
- }
6716
- function renderEventLines(logs) {
6717
- return logs.filter((log3) => !CANONICAL_STAGES2.some((stage) => stage.toLowerCase() === logTitle(log3).toLowerCase())).slice(-12).flatMap((log3) => {
6718
- const title = logTitle(log3) || "Rig event";
6719
- const detail = logDetail2(log3);
6720
- if (!detail)
6721
- return [];
6722
- const tone = String(log3.tone ?? "");
6723
- const color = tone === "error" ? RED : tone === "tool" ? MAGENTA : DIM;
6724
- return [`${color}[${title}]${RESET} ${detail.slice(0, 220)}`];
6725
- });
6726
- }
6727
-
6728
- class RigRunComponent {
6729
- truncateToWidth;
6730
- snapshot = { run: {}, logs: [], timeline: [] };
6731
- localEvents = [];
6732
- constructor(truncateToWidth) {
6733
- this.truncateToWidth = truncateToWidth;
6734
- }
6735
- update(snapshot) {
6736
- this.snapshot = snapshot;
6737
- }
6738
- addLocalEvent(message2) {
6739
- this.localEvents.push(`${new Date().toLocaleTimeString()} ${message2}`);
6740
- this.localEvents = this.localEvents.slice(-8);
6741
- }
6742
- invalidate() {}
6743
- render(width) {
6744
- const run = unwrapRun(this.snapshot.run);
6745
- const runId = String(run.runId ?? run.id ?? "run");
6746
- const status = String(run.status ?? "unknown");
6747
- const worker = typeof run.worktreePath === "string" && run.worktreePath.trim() ? run.worktreePath.trim() : typeof run.projectRoot === "string" && run.projectRoot.trim() ? run.projectRoot.trim() : "worker workspace pending";
6748
- const lines = [
6749
- `${BOLD}Rig Pi frontend${RESET} ${DIM}(local Pi TUI \u2192 Rig server \u2192 worker Pi backend)${RESET}`,
6750
- `${BOLD}${runId}${RESET} \xB7 ${status} \xB7 ${DIM}${worker}${RESET}`,
6751
- "",
6752
- `${BOLD}Rig flow${RESET}`,
6753
- ...renderStageLines(this.snapshot.logs ?? []),
6754
- "",
6755
- ...renderAssistantTextFromTimeline(this.snapshot.timeline ?? []),
6756
- ...renderToolLines(this.snapshot.timeline ?? []),
6757
- "",
6758
- `${BOLD}Rig / Pi events${RESET}`,
6759
- ...renderEventLines(this.snapshot.logs ?? []),
6760
- ...this.localEvents.map((event) => `${GREEN}[frontend]${RESET} ${event}`),
6761
- ""
6762
- ];
6763
- return lines.slice(-42).map((line) => this.truncateToWidth(line, Math.max(10, width)));
6764
- }
6765
- }
6766
-
6767
- class RigInputComponent {
6768
- matchesKey;
6769
- truncateToWidth;
6770
- input;
6771
- status = "Type text, /skill:..., or remote Pi slash commands. Local: /stop /detach.";
6772
- constructor(InputCtor, matchesKey, truncateToWidth, onSubmit, onEscape) {
6773
- this.matchesKey = matchesKey;
6774
- this.truncateToWidth = truncateToWidth;
6775
- this.input = new InputCtor;
6776
- this.input.onSubmit = (value) => {
6777
- const text2 = value.trim();
6778
- this.input.setValue("");
6779
- if (text2)
6780
- Promise.resolve(onSubmit(text2));
6781
- };
6782
- this.input.onEscape = onEscape;
6783
- }
6784
- handleInput(data) {
6785
- if (this.matchesKey(data, "ctrl+d")) {
6786
- this.input.onEscape?.();
6787
- return;
6788
- }
6789
- this.input.handleInput?.(data);
6790
- }
6791
- setStatus(status) {
6792
- this.status = status;
6793
- }
6794
- invalidate() {
6795
- this.input.invalidate();
6796
- }
6797
- render(width) {
6798
- return [
6799
- `${DIM}${this.truncateToWidth(this.status, Math.max(10, width))}${RESET}`,
6800
- `${GREEN}${BOLD}You \u2192 worker Pi:${RESET}`,
6801
- ...this.input.render(width)
6802
- ];
6803
- }
6804
- }
6805
- async function attachRunPiTuiFrontend(context, input) {
6806
- const piTui = await loadPiTuiRuntime();
6807
- const terminal = new piTui.ProcessTerminal;
6808
- const tui = new piTui.TUI(terminal);
6809
- const root = new piTui.Container;
6810
- const runView = new RigRunComponent(piTui.truncateToWidth);
6811
- let detached = false;
6812
- let steered = input.steered === true;
6813
- let latest = await readOperatorSnapshot(context, input.runId);
6814
- let timelineCursor = latest.timelineCursor;
6815
- runView.update(latest);
6816
- if (steered)
6817
- runView.addLocalEvent("initial message queued to worker Pi.");
6818
- const stop = () => {
6819
- detached = true;
6820
- tui.stop();
6821
- };
6822
- const inputView = new RigInputComponent(piTui.Input, piTui.matchesKey, piTui.truncateToWidth, async (line) => {
6823
- if (line === "/detach" || line === "/quit" || line === "/q") {
6824
- runView.addLocalEvent("detached from run.");
6825
- stop();
6826
- return;
6827
- }
6828
- if (line === "/stop") {
6829
- await stopRunViaServer(context, input.runId);
6830
- runView.addLocalEvent("stop requested.");
6831
- stop();
6832
- return;
6833
- }
6834
- await steerRunViaServer(context, input.runId, line);
6835
- steered = true;
6836
- runView.addLocalEvent(`queued to worker Pi: ${line.slice(0, 160)}`);
6837
- tui.requestRender();
6838
- }, stop);
6839
- root.addChild(runView);
6840
- root.addChild(inputView);
6841
- tui.addChild(root);
6842
- tui.setFocus(inputView.input);
6843
- tui.start();
6844
- tui.requestRender(true);
6845
- const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 1000));
6846
- try {
6847
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(latest.run))) {
6848
- await Bun.sleep(pollMs);
6849
- latest = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6850
- timelineCursor = latest.timelineCursor;
6851
- runView.update(latest);
6852
- inputView.setStatus(`Remote worker ${runStatusFromPayload(latest.run)}. Input is forwarded to worker Pi; /stop /detach are local controls.`);
6853
- tui.requestRender();
6854
- }
6855
- } finally {
6856
- if (!detached)
6857
- tui.stop();
6858
- }
6859
- return { ...latest, timelineCursor, steered, detached, rendered: renderOperatorSnapshot(latest) };
6860
- }
6861
7157
  async function attachRunOperatorView(context, input) {
6862
7158
  let steered = false;
6863
7159
  if (input.message?.trim()) {
6864
- await steerRunViaServer(context, input.runId, input.message.trim());
7160
+ await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
6865
7161
  steered = true;
6866
7162
  }
6867
7163
  if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
6868
- return attachRunPiTuiFrontend(context, {
7164
+ return attachRunBundledPiFrontend(context, {
6869
7165
  runId: input.runId,
6870
- pollMs: input.pollMs,
6871
7166
  steered
6872
7167
  });
6873
7168
  }
@@ -6878,7 +7173,7 @@ async function attachRunOperatorView(context, input) {
6878
7173
  surface.renderTimeline(snapshot.timeline);
6879
7174
  surface.renderLogs(snapshot.logs);
6880
7175
  if (steered)
6881
- surface.info("Steering message queued.");
7176
+ surface.info("Message submitted to worker Pi.");
6882
7177
  }
6883
7178
  let detached = false;
6884
7179
  let commandInput = null;
@@ -6897,7 +7192,7 @@ async function attachRunOperatorView(context, input) {
6897
7192
  }
6898
7193
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
6899
7194
  let timelineCursor = snapshot.timelineCursor;
6900
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
7195
+ while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
6901
7196
  await Bun.sleep(pollMs);
6902
7197
  snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6903
7198
  timelineCursor = snapshot.timelineCursor;
@@ -8952,8 +9247,7 @@ async function executeRigOwnedTaskRun(context, input) {
8952
9247
  ...input.model ? ["--model", input.model] : [],
8953
9248
  "--prompt"
8954
9249
  ] : input.runtimeAdapter === "pi" ? [
8955
- "--mode",
8956
- "rpc",
9250
+ "__rig_pi_session_daemon__",
8957
9251
  ...input.model ? ["--model", input.model] : []
8958
9252
  ] : [
8959
9253
  "--print",
@@ -9050,7 +9344,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9050
9344
  projectRoot: context.projectRoot,
9051
9345
  runId: input.runId,
9052
9346
  stage,
9053
- detail: stage === "Launch Pi" ? "Pi RPC 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,
9347
+ 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,
9054
9348
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
9055
9349
  });
9056
9350
  }
@@ -9091,6 +9385,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9091
9385
  let latestProviderCommand = null;
9092
9386
  let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
9093
9387
  let snapshotSidecarPromise = null;
9388
+ let wrapperManagesRuntimeSnapshot = false;
9094
9389
  let dirtyBaselineApplied = false;
9095
9390
  const childEnv = {
9096
9391
  ...process.env,
@@ -9159,6 +9454,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9159
9454
  latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
9160
9455
  latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
9161
9456
  const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
9457
+ wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
9162
9458
  latestRuntimeBranch = runtimeId;
9163
9459
  provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
9164
9460
  runtimeId: runtimeId ?? null,
@@ -9196,7 +9492,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9196
9492
  });
9197
9493
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
9198
9494
  }
9199
- if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
9495
+ if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
9200
9496
  snapshotSidecarPromise = (async () => {
9201
9497
  const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
9202
9498
  projectRoot: context.projectRoot,
@@ -9278,6 +9574,51 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9278
9574
  }
9279
9575
  return true;
9280
9576
  }
9577
+ 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") {
9578
+ 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";
9579
+ 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");
9580
+ appendRunLog(context.projectRoot, input.runId, {
9581
+ id: nextRunLogId(),
9582
+ title,
9583
+ detail,
9584
+ tone: event.type === "pi.session.error" ? "error" : "info",
9585
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9586
+ payload: {
9587
+ eventType: event.type,
9588
+ runId: typeof payload.runId === "string" ? payload.runId : null,
9589
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
9590
+ sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
9591
+ },
9592
+ createdAt: new Date().toISOString()
9593
+ });
9594
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9595
+ return true;
9596
+ }
9597
+ if (event.type === "pi.session.ready") {
9598
+ const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
9599
+ const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
9600
+ if (privateMetadata) {
9601
+ patchAuthorityRun(context.projectRoot, input.runId, {
9602
+ piSession: publicMetadata,
9603
+ piSessionPrivate: privateMetadata
9604
+ });
9605
+ const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
9606
+ appendRunLog(context.projectRoot, input.runId, {
9607
+ id: `log:${input.runId}:pi-session-ready`,
9608
+ title: "Worker Pi session ready",
9609
+ detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
9610
+ tone: "info",
9611
+ status: "running",
9612
+ payload: {
9613
+ sessionId: sessionId || null,
9614
+ runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
9615
+ },
9616
+ createdAt: new Date().toISOString()
9617
+ });
9618
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
9619
+ }
9620
+ return true;
9621
+ }
9281
9622
  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") {
9282
9623
  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";
9283
9624
  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")}`;
@@ -9809,7 +10150,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9809
10150
  startedAt,
9810
10151
  finishedAt: requestedAt
9811
10152
  });
9812
- return;
10153
+ process.exit(0);
9813
10154
  }
9814
10155
  const runPiPrFeedbackFix = async (message2) => {
9815
10156
  appendPiStageLog({