@h-rig/cli 0.0.6-alpha.20 → 0.0.6-alpha.22

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
@@ -6594,6 +6594,50 @@ async function promptForTaskSelection(question) {
6594
6594
 
6595
6595
  // packages/cli/src/commands/_operator-view.ts
6596
6596
  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() {
6620
+ try {
6621
+ return await import("@earendil-works/pi-tui");
6622
+ } 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)
6630
+ ]);
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
6638
+ };
6639
+ }
6640
+ }
6597
6641
  function runStatusFromPayload(payload) {
6598
6642
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6599
6643
  return String(run.status ?? "unknown").toLowerCase();
@@ -6632,12 +6676,201 @@ async function readOperatorSnapshot(context, runId, options = {}) {
6632
6676
  const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6633
6677
  return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6634
6678
  }
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
+ }
6635
6861
  async function attachRunOperatorView(context, input) {
6636
6862
  let steered = false;
6637
6863
  if (input.message?.trim()) {
6638
6864
  await steerRunViaServer(context, input.runId, input.message.trim());
6639
6865
  steered = true;
6640
6866
  }
6867
+ if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
6868
+ return attachRunPiTuiFrontend(context, {
6869
+ runId: input.runId,
6870
+ pollMs: input.pollMs,
6871
+ steered
6872
+ });
6873
+ }
6641
6874
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
6642
6875
  let snapshot = await readOperatorSnapshot(context, input.runId);
6643
6876
  if (context.outputMode === "text") {
@@ -6778,92 +7011,6 @@ function formatSubmittedRun(input) {
6778
7011
  `);
6779
7012
  }
6780
7013
 
6781
- // packages/cli/src/commands/_pi-session.ts
6782
- import { spawn as spawn2 } from "child_process";
6783
- function buildPiRigSessionEnv(input) {
6784
- return {
6785
- RIG_PROJECT_ROOT: input.projectRoot,
6786
- PROJECT_RIG_ROOT: input.projectRoot,
6787
- RIG_RUN_ID: input.runId,
6788
- RIG_SERVER_RUN_ID: input.runId,
6789
- RIG_RUNTIME_ADAPTER: "pi",
6790
- RIG_SERVER_URL: input.serverUrl,
6791
- RIG_SERVER_BASE_URL: input.serverUrl,
6792
- RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6793
- RIG_PI_OPERATOR_SESSION: "1",
6794
- ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6795
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6796
- };
6797
- }
6798
- function shellBinary(name) {
6799
- const explicit = process.env.RIG_PI_BINARY?.trim();
6800
- if (explicit)
6801
- return explicit;
6802
- return Bun.which(name) || name;
6803
- }
6804
- function buildPiRigSessionCommand(input) {
6805
- const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6806
- const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6807
- const initialCommand = `/rig attach ${input.runId}`;
6808
- return [
6809
- shellBinary("pi"),
6810
- "--no-extensions",
6811
- "--extension",
6812
- extensionSource,
6813
- initialCommand
6814
- ];
6815
- }
6816
- async function launchPiRigSession(context, input) {
6817
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6818
- return { launched: false, exitCode: null, command: [] };
6819
- }
6820
- if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6821
- return { launched: false, exitCode: null, command: [] };
6822
- }
6823
- const server = await ensureServerForCli(context.projectRoot);
6824
- const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6825
- const env = {
6826
- ...process.env,
6827
- ...buildPiRigSessionEnv({
6828
- projectRoot: context.projectRoot,
6829
- runId: input.runId,
6830
- taskId: input.taskId,
6831
- serverUrl: server.baseUrl,
6832
- authToken: server.authToken
6833
- })
6834
- };
6835
- process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6836
- `);
6837
- process.stdout.write(`Pi command: ${formatCommand(command)}
6838
- `);
6839
- const launchedAt = Date.now();
6840
- const child = spawn2(command[0], command.slice(1), {
6841
- cwd: context.projectRoot,
6842
- env,
6843
- stdio: "inherit"
6844
- });
6845
- const launchError = await new Promise((resolve20) => {
6846
- child.once("error", (error) => {
6847
- resolve20({ error: error.message });
6848
- });
6849
- child.once("close", (code) => resolve20({ code }));
6850
- });
6851
- if ("error" in launchError) {
6852
- process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6853
- `);
6854
- return { launched: false, exitCode: null, command, error: launchError.error };
6855
- }
6856
- const exitCode = launchError.code;
6857
- const elapsedMs = Date.now() - launchedAt;
6858
- if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6859
- const error = `Pi exited during startup with code ${exitCode}.`;
6860
- process.stderr.write(`${error} Falling back to Rig attach view.
6861
- `);
6862
- return { launched: false, exitCode, command, error };
6863
- }
6864
- return { launched: true, exitCode, command };
6865
- }
6866
-
6867
7014
  // packages/cli/src/commands/run.ts
6868
7015
  function normalizeRemoteRunDetails(payload) {
6869
7016
  const run = payload.run;
@@ -7088,20 +7235,13 @@ async function executeRun(context, args) {
7088
7235
  throw new CliError2("run attach requires a run id.", 2);
7089
7236
  }
7090
7237
  let steered = false;
7091
- const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
7092
- if (shouldTryPiAttach && messageOption.value?.trim()) {
7238
+ if (messageOption.value?.trim()) {
7093
7239
  await steerRunViaServer(context, runId, messageOption.value.trim());
7094
7240
  steered = true;
7095
7241
  }
7096
- if (shouldTryPiAttach) {
7097
- const piSession = await launchPiRigSession(context, { runId });
7098
- if (piSession.launched) {
7099
- return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
7100
- }
7101
- }
7102
7242
  const attached = await attachRunOperatorView(context, {
7103
7243
  runId,
7104
- message: shouldTryPiAttach ? null : messageOption.value ?? null,
7244
+ message: null,
7105
7245
  once: once.value,
7106
7246
  follow: follow.value,
7107
7247
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
@@ -7828,20 +7968,7 @@ async function executeTask(context, args, options) {
7828
7968
  let attachDetails = null;
7829
7969
  if (!detachResult.value && context.outputMode === "text") {
7830
7970
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7831
- if (runtimeAdapter === "pi") {
7832
- const piSession = await launchPiRigSession(context, {
7833
- runId: submitted.runId,
7834
- taskId: selectedTaskId,
7835
- title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7836
- runtimeAdapter
7837
- });
7838
- attachDetails = { mode: "pi", ...piSession };
7839
- if (!piSession.launched) {
7840
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7841
- }
7842
- } else {
7843
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7844
- }
7971
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7845
7972
  } else if (context.outputMode === "text") {
7846
7973
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7847
7974
  }
@@ -7928,7 +8055,7 @@ async function executeTask(context, args, options) {
7928
8055
  // packages/cli/src/commands/task-run-driver.ts
7929
8056
  import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7930
8057
  import { resolve as resolve21 } from "path";
7931
- import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
8058
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
7932
8059
  import { createInterface as createLineInterface } from "readline";
7933
8060
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7934
8061
  import {
@@ -7985,6 +8112,7 @@ function buildPiRigBridgeEnv(input) {
7985
8112
  RIG_SERVER_RUN_ID: input.runId,
7986
8113
  RIG_TASK_ID: input.taskId,
7987
8114
  RIG_RUNTIME_ADAPTER: "pi",
8115
+ RIG_STEERING_POLL_MS: "0",
7988
8116
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7989
8117
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7990
8118
  ...githubBridgeEnv(githubToken)
@@ -8156,7 +8284,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
8156
8284
  }
8157
8285
  function createCommandRunner(binary) {
8158
8286
  return async (args, options) => {
8159
- const child = spawn3(binary, [...args], {
8287
+ const child = spawn2(binary, [...args], {
8160
8288
  cwd: options?.cwd,
8161
8289
  stdio: ["ignore", "pipe", "pipe"]
8162
8290
  });
@@ -8580,6 +8708,69 @@ function appendAssistantTimelineFromRecord(input) {
8580
8708
  }
8581
8709
  return nextAssistantText;
8582
8710
  }
8711
+ function appendPiRpcProtocolLogFromRecord(input) {
8712
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8713
+ if (type === "response") {
8714
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
8715
+ const success = input.record.success !== false;
8716
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
8717
+ return true;
8718
+ }
8719
+ appendRunLog(input.projectRoot, input.runId, {
8720
+ id: input.nextRunLogId(),
8721
+ title: success ? "Pi RPC response" : "Pi RPC error",
8722
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
8723
+ tone: success ? "tool" : "error",
8724
+ status: input.status,
8725
+ payload: input.record,
8726
+ createdAt: new Date().toISOString()
8727
+ });
8728
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
8729
+ return true;
8730
+ }
8731
+ if (type !== "extension_ui_request")
8732
+ return false;
8733
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
8734
+ let title = "Pi UI event";
8735
+ let detail = method;
8736
+ let tone = "info";
8737
+ if (method === "notify") {
8738
+ title = "Pi notification";
8739
+ detail = String(input.record.message ?? "");
8740
+ tone = input.record.notifyType === "error" ? "error" : "info";
8741
+ } else if (method === "setStatus") {
8742
+ title = "Pi UI status";
8743
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
8744
+ tone = "tool";
8745
+ } else if (method === "setWidget") {
8746
+ title = "Pi UI widget";
8747
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
8748
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
8749
+ tone = "tool";
8750
+ } else if (method === "setTitle") {
8751
+ title = "Pi UI title";
8752
+ detail = String(input.record.title ?? "");
8753
+ tone = "tool";
8754
+ } else if (method === "set_editor_text") {
8755
+ title = "Pi editor update";
8756
+ detail = String(input.record.text ?? "").slice(0, 500);
8757
+ tone = "tool";
8758
+ } else {
8759
+ title = "Pi UI request";
8760
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
8761
+ }
8762
+ appendRunLog(input.projectRoot, input.runId, {
8763
+ id: input.nextRunLogId(),
8764
+ title,
8765
+ detail,
8766
+ tone,
8767
+ status: input.status,
8768
+ payload: input.record,
8769
+ createdAt: new Date().toISOString()
8770
+ });
8771
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
8772
+ return true;
8773
+ }
8583
8774
  function appendPiToolTimelineFromRecord(input) {
8584
8775
  const type = typeof input.record.type === "string" ? input.record.type : "";
8585
8776
  if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
@@ -8598,7 +8789,7 @@ function appendPiToolTimelineFromRecord(input) {
8598
8789
  }
8599
8790
  function isNonRenderablePiProtocolRecord(record) {
8600
8791
  const type = typeof record.type === "string" ? record.type : "";
8601
- 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");
8792
+ 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");
8602
8793
  }
8603
8794
  function appendToolTimelineFromLog(input) {
8604
8795
  const title = typeof input.log.title === "string" ? input.log.title : "";
@@ -8761,11 +8952,8 @@ async function executeRigOwnedTaskRun(context, input) {
8761
8952
  ...input.model ? ["--model", input.model] : [],
8762
8953
  "--prompt"
8763
8954
  ] : input.runtimeAdapter === "pi" ? [
8764
- "--print",
8765
- "--verbose",
8766
8955
  "--mode",
8767
- "json",
8768
- "--no-session",
8956
+ "rpc",
8769
8957
  ...input.model ? ["--model", input.model] : []
8770
8958
  ] : [
8771
8959
  "--print",
@@ -8862,7 +9050,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8862
9050
  projectRoot: context.projectRoot,
8863
9051
  runId: input.runId,
8864
9052
  stage,
8865
- 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,
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,
8866
9054
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
8867
9055
  });
8868
9056
  }
@@ -8955,6 +9143,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8955
9143
  detail: detail ?? "Verifier review is running."
8956
9144
  });
8957
9145
  };
9146
+ const nextRunLogId = createRunLogIdFactory(input.runId);
8958
9147
  const handleWrapperEvent = (rawPayload) => {
8959
9148
  let event = null;
8960
9149
  try {
@@ -9089,9 +9278,23 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9089
9278
  }
9090
9279
  return true;
9091
9280
  }
9281
+ 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
+ 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
+ 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")}`;
9284
+ appendRunLog(context.projectRoot, input.runId, {
9285
+ id: nextRunLogId(),
9286
+ title,
9287
+ detail,
9288
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
9289
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9290
+ payload,
9291
+ createdAt: new Date().toISOString()
9292
+ });
9293
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9294
+ return true;
9295
+ }
9092
9296
  return false;
9093
9297
  };
9094
- const nextRunLogId = createRunLogIdFactory(input.runId);
9095
9298
  const handleAgentStdoutLine = (line) => {
9096
9299
  const trimmed = line.trim();
9097
9300
  if (!trimmed)
@@ -9125,6 +9328,15 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9125
9328
  try {
9126
9329
  const record = JSON.parse(trimmed);
9127
9330
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9331
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
9332
+ projectRoot: context.projectRoot,
9333
+ runId: input.runId,
9334
+ record,
9335
+ status: liveLogStatus,
9336
+ nextRunLogId
9337
+ })) {
9338
+ return;
9339
+ }
9128
9340
  if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
9129
9341
  emitServerRunEvent({ type: "timeline", runId: input.runId });
9130
9342
  return;
@@ -9270,7 +9482,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9270
9482
  }
9271
9483
  for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
9272
9484
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
9273
- const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9485
+ const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9274
9486
  cwd: context.projectRoot,
9275
9487
  env: childEnv,
9276
9488
  stdio: ["pipe", "pipe", "pipe"]
@@ -9617,7 +9829,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9617
9829
  });
9618
9830
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
9619
9831
  const feedbackCommand = buildHostAgentCommand(message2);
9620
- const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
9832
+ const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9621
9833
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
9622
9834
  env: childEnv,
9623
9835
  stdio: ["pipe", "pipe", "pipe"]