@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.
@@ -6387,6 +6387,50 @@ async function promptForTaskSelection(question) {
6387
6387
 
6388
6388
  // packages/cli/src/commands/_operator-view.ts
6389
6389
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
6390
+ var CANONICAL_STAGES2 = [
6391
+ "Connect",
6392
+ "GitHub/task sync",
6393
+ "Prepare workspace",
6394
+ "Launch Pi",
6395
+ "Plan",
6396
+ "Implement",
6397
+ "Validate",
6398
+ "Commit",
6399
+ "Open PR",
6400
+ "Review/CI",
6401
+ "Merge",
6402
+ "Complete"
6403
+ ];
6404
+ var GREEN = "\x1B[32m";
6405
+ var BLUE = "\x1B[34m";
6406
+ var MAGENTA = "\x1B[35m";
6407
+ var YELLOW = "\x1B[33m";
6408
+ var RED = "\x1B[31m";
6409
+ var DIM = "\x1B[2m";
6410
+ var BOLD = "\x1B[1m";
6411
+ var RESET = "\x1B[0m";
6412
+ async function loadPiTuiRuntime() {
6413
+ try {
6414
+ return await import("@earendil-works/pi-tui");
6415
+ } catch {
6416
+ const base = new URL("../../../pi/packages/tui/src/", import.meta.url);
6417
+ const [tui, input, terminal, keys, utils] = await Promise.all([
6418
+ import(new URL("tui.ts", base).href),
6419
+ import(new URL("components/input.ts", base).href),
6420
+ import(new URL("terminal.ts", base).href),
6421
+ import(new URL("keys.ts", base).href),
6422
+ import(new URL("utils.ts", base).href)
6423
+ ]);
6424
+ return {
6425
+ Container: tui.Container,
6426
+ TUI: tui.TUI,
6427
+ Input: input.Input,
6428
+ ProcessTerminal: terminal.ProcessTerminal,
6429
+ matchesKey: keys.matchesKey,
6430
+ truncateToWidth: utils.truncateToWidth
6431
+ };
6432
+ }
6433
+ }
6390
6434
  function runStatusFromPayload(payload) {
6391
6435
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
6392
6436
  return String(run.status ?? "unknown").toLowerCase();
@@ -6425,12 +6469,201 @@ async function readOperatorSnapshot(context, runId, options = {}) {
6425
6469
  const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
6426
6470
  return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
6427
6471
  }
6472
+ function unwrapRun(runPayload) {
6473
+ return runPayload.run && typeof runPayload.run === "object" && !Array.isArray(runPayload.run) ? runPayload.run : runPayload;
6474
+ }
6475
+ function logDetail2(log3) {
6476
+ return typeof log3.detail === "string" ? log3.detail.trim() : "";
6477
+ }
6478
+ function logTitle(log3) {
6479
+ return typeof log3.title === "string" ? log3.title.trim() : "";
6480
+ }
6481
+ function renderAssistantTextFromTimeline(entries) {
6482
+ const assistant = entries.filter((entry) => entry.type === "assistant_message" && typeof entry.text === "string").at(-1);
6483
+ const text2 = typeof assistant?.text === "string" ? assistant.text.trimEnd() : "";
6484
+ if (!text2)
6485
+ return [];
6486
+ return [`${BLUE}${BOLD}Remote Pi assistant${RESET}`, ...text2.split(/\r?\n/).slice(-18)];
6487
+ }
6488
+ function renderToolLines(entries) {
6489
+ 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());
6490
+ }
6491
+ function renderStageLines(logs) {
6492
+ const latestByStage = new Map;
6493
+ for (const log3 of logs) {
6494
+ const title = logTitle(log3).toLowerCase();
6495
+ const stageName = String(log3.stage ?? "").toLowerCase();
6496
+ const stage = CANONICAL_STAGES2.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
6497
+ if (stage)
6498
+ latestByStage.set(stage, log3);
6499
+ }
6500
+ return CANONICAL_STAGES2.map((stage) => {
6501
+ const log3 = latestByStage.get(stage);
6502
+ const status = String(log3?.status ?? "pending");
6503
+ const detail = log3 ? logDetail2(log3) : "";
6504
+ const color = status === "completed" ? GREEN : status === "failed" || status === "rejected" ? RED : status === "pending" ? DIM : YELLOW;
6505
+ const mark = status === "completed" ? "\u2713" : status === "pending" ? "\xB7" : status === "failed" ? "\u2717" : "\u25B6";
6506
+ return `${color}${mark} ${stage}${RESET}${detail ? ` ${DIM}\u2014 ${detail.slice(0, 140)}${RESET}` : ""}`;
6507
+ });
6508
+ }
6509
+ function renderEventLines(logs) {
6510
+ return logs.filter((log3) => !CANONICAL_STAGES2.some((stage) => stage.toLowerCase() === logTitle(log3).toLowerCase())).slice(-12).flatMap((log3) => {
6511
+ const title = logTitle(log3) || "Rig event";
6512
+ const detail = logDetail2(log3);
6513
+ if (!detail)
6514
+ return [];
6515
+ const tone = String(log3.tone ?? "");
6516
+ const color = tone === "error" ? RED : tone === "tool" ? MAGENTA : DIM;
6517
+ return [`${color}[${title}]${RESET} ${detail.slice(0, 220)}`];
6518
+ });
6519
+ }
6520
+
6521
+ class RigRunComponent {
6522
+ truncateToWidth;
6523
+ snapshot = { run: {}, logs: [], timeline: [] };
6524
+ localEvents = [];
6525
+ constructor(truncateToWidth) {
6526
+ this.truncateToWidth = truncateToWidth;
6527
+ }
6528
+ update(snapshot) {
6529
+ this.snapshot = snapshot;
6530
+ }
6531
+ addLocalEvent(message2) {
6532
+ this.localEvents.push(`${new Date().toLocaleTimeString()} ${message2}`);
6533
+ this.localEvents = this.localEvents.slice(-8);
6534
+ }
6535
+ invalidate() {}
6536
+ render(width) {
6537
+ const run = unwrapRun(this.snapshot.run);
6538
+ const runId = String(run.runId ?? run.id ?? "run");
6539
+ const status = String(run.status ?? "unknown");
6540
+ 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";
6541
+ const lines = [
6542
+ `${BOLD}Rig Pi frontend${RESET} ${DIM}(local Pi TUI \u2192 Rig server \u2192 worker Pi backend)${RESET}`,
6543
+ `${BOLD}${runId}${RESET} \xB7 ${status} \xB7 ${DIM}${worker}${RESET}`,
6544
+ "",
6545
+ `${BOLD}Rig flow${RESET}`,
6546
+ ...renderStageLines(this.snapshot.logs ?? []),
6547
+ "",
6548
+ ...renderAssistantTextFromTimeline(this.snapshot.timeline ?? []),
6549
+ ...renderToolLines(this.snapshot.timeline ?? []),
6550
+ "",
6551
+ `${BOLD}Rig / Pi events${RESET}`,
6552
+ ...renderEventLines(this.snapshot.logs ?? []),
6553
+ ...this.localEvents.map((event) => `${GREEN}[frontend]${RESET} ${event}`),
6554
+ ""
6555
+ ];
6556
+ return lines.slice(-42).map((line) => this.truncateToWidth(line, Math.max(10, width)));
6557
+ }
6558
+ }
6559
+
6560
+ class RigInputComponent {
6561
+ matchesKey;
6562
+ truncateToWidth;
6563
+ input;
6564
+ status = "Type text, /skill:..., or remote Pi slash commands. Local: /stop /detach.";
6565
+ constructor(InputCtor, matchesKey, truncateToWidth, onSubmit, onEscape) {
6566
+ this.matchesKey = matchesKey;
6567
+ this.truncateToWidth = truncateToWidth;
6568
+ this.input = new InputCtor;
6569
+ this.input.onSubmit = (value) => {
6570
+ const text2 = value.trim();
6571
+ this.input.setValue("");
6572
+ if (text2)
6573
+ Promise.resolve(onSubmit(text2));
6574
+ };
6575
+ this.input.onEscape = onEscape;
6576
+ }
6577
+ handleInput(data) {
6578
+ if (this.matchesKey(data, "ctrl+d")) {
6579
+ this.input.onEscape?.();
6580
+ return;
6581
+ }
6582
+ this.input.handleInput?.(data);
6583
+ }
6584
+ setStatus(status) {
6585
+ this.status = status;
6586
+ }
6587
+ invalidate() {
6588
+ this.input.invalidate();
6589
+ }
6590
+ render(width) {
6591
+ return [
6592
+ `${DIM}${this.truncateToWidth(this.status, Math.max(10, width))}${RESET}`,
6593
+ `${GREEN}${BOLD}You \u2192 worker Pi:${RESET}`,
6594
+ ...this.input.render(width)
6595
+ ];
6596
+ }
6597
+ }
6598
+ async function attachRunPiTuiFrontend(context, input) {
6599
+ const piTui = await loadPiTuiRuntime();
6600
+ const terminal = new piTui.ProcessTerminal;
6601
+ const tui = new piTui.TUI(terminal);
6602
+ const root = new piTui.Container;
6603
+ const runView = new RigRunComponent(piTui.truncateToWidth);
6604
+ let detached = false;
6605
+ let steered = input.steered === true;
6606
+ let latest = await readOperatorSnapshot(context, input.runId);
6607
+ let timelineCursor = latest.timelineCursor;
6608
+ runView.update(latest);
6609
+ if (steered)
6610
+ runView.addLocalEvent("initial message queued to worker Pi.");
6611
+ const stop = () => {
6612
+ detached = true;
6613
+ tui.stop();
6614
+ };
6615
+ const inputView = new RigInputComponent(piTui.Input, piTui.matchesKey, piTui.truncateToWidth, async (line) => {
6616
+ if (line === "/detach" || line === "/quit" || line === "/q") {
6617
+ runView.addLocalEvent("detached from run.");
6618
+ stop();
6619
+ return;
6620
+ }
6621
+ if (line === "/stop") {
6622
+ await stopRunViaServer(context, input.runId);
6623
+ runView.addLocalEvent("stop requested.");
6624
+ stop();
6625
+ return;
6626
+ }
6627
+ await steerRunViaServer(context, input.runId, line);
6628
+ steered = true;
6629
+ runView.addLocalEvent(`queued to worker Pi: ${line.slice(0, 160)}`);
6630
+ tui.requestRender();
6631
+ }, stop);
6632
+ root.addChild(runView);
6633
+ root.addChild(inputView);
6634
+ tui.addChild(root);
6635
+ tui.setFocus(inputView.input);
6636
+ tui.start();
6637
+ tui.requestRender(true);
6638
+ const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 1000));
6639
+ try {
6640
+ while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(latest.run))) {
6641
+ await Bun.sleep(pollMs);
6642
+ latest = await readOperatorSnapshot(context, input.runId, { timelineCursor });
6643
+ timelineCursor = latest.timelineCursor;
6644
+ runView.update(latest);
6645
+ inputView.setStatus(`Remote worker ${runStatusFromPayload(latest.run)}. Input is forwarded to worker Pi; /stop /detach are local controls.`);
6646
+ tui.requestRender();
6647
+ }
6648
+ } finally {
6649
+ if (!detached)
6650
+ tui.stop();
6651
+ }
6652
+ return { ...latest, timelineCursor, steered, detached, rendered: renderOperatorSnapshot(latest) };
6653
+ }
6428
6654
  async function attachRunOperatorView(context, input) {
6429
6655
  let steered = false;
6430
6656
  if (input.message?.trim()) {
6431
6657
  await steerRunViaServer(context, input.runId, input.message.trim());
6432
6658
  steered = true;
6433
6659
  }
6660
+ if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
6661
+ return attachRunPiTuiFrontend(context, {
6662
+ runId: input.runId,
6663
+ pollMs: input.pollMs,
6664
+ steered
6665
+ });
6666
+ }
6434
6667
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
6435
6668
  let snapshot = await readOperatorSnapshot(context, input.runId);
6436
6669
  if (context.outputMode === "text") {
@@ -6571,92 +6804,6 @@ function formatSubmittedRun(input) {
6571
6804
  `);
6572
6805
  }
6573
6806
 
6574
- // packages/cli/src/commands/_pi-session.ts
6575
- import { spawn as spawn2 } from "child_process";
6576
- function buildPiRigSessionEnv(input) {
6577
- return {
6578
- RIG_PROJECT_ROOT: input.projectRoot,
6579
- PROJECT_RIG_ROOT: input.projectRoot,
6580
- RIG_RUN_ID: input.runId,
6581
- RIG_SERVER_RUN_ID: input.runId,
6582
- RIG_RUNTIME_ADAPTER: "pi",
6583
- RIG_SERVER_URL: input.serverUrl,
6584
- RIG_SERVER_BASE_URL: input.serverUrl,
6585
- RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
6586
- RIG_PI_OPERATOR_SESSION: "1",
6587
- ...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
6588
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
6589
- };
6590
- }
6591
- function shellBinary(name) {
6592
- const explicit = process.env.RIG_PI_BINARY?.trim();
6593
- if (explicit)
6594
- return explicit;
6595
- return Bun.which(name) || name;
6596
- }
6597
- function buildPiRigSessionCommand(input) {
6598
- const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
6599
- const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
6600
- const initialCommand = `/rig attach ${input.runId}`;
6601
- return [
6602
- shellBinary("pi"),
6603
- "--no-extensions",
6604
- "--extension",
6605
- extensionSource,
6606
- initialCommand
6607
- ];
6608
- }
6609
- async function launchPiRigSession(context, input) {
6610
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
6611
- return { launched: false, exitCode: null, command: [] };
6612
- }
6613
- if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
6614
- return { launched: false, exitCode: null, command: [] };
6615
- }
6616
- const server = await ensureServerForCli(context.projectRoot);
6617
- const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
6618
- const env = {
6619
- ...process.env,
6620
- ...buildPiRigSessionEnv({
6621
- projectRoot: context.projectRoot,
6622
- runId: input.runId,
6623
- taskId: input.taskId,
6624
- serverUrl: server.baseUrl,
6625
- authToken: server.authToken
6626
- })
6627
- };
6628
- process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
6629
- `);
6630
- process.stdout.write(`Pi command: ${formatCommand(command)}
6631
- `);
6632
- const launchedAt = Date.now();
6633
- const child = spawn2(command[0], command.slice(1), {
6634
- cwd: context.projectRoot,
6635
- env,
6636
- stdio: "inherit"
6637
- });
6638
- const launchError = await new Promise((resolve19) => {
6639
- child.once("error", (error) => {
6640
- resolve19({ error: error.message });
6641
- });
6642
- child.once("close", (code) => resolve19({ code }));
6643
- });
6644
- if ("error" in launchError) {
6645
- process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
6646
- `);
6647
- return { launched: false, exitCode: null, command, error: launchError.error };
6648
- }
6649
- const exitCode = launchError.code;
6650
- const elapsedMs = Date.now() - launchedAt;
6651
- if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
6652
- const error = `Pi exited during startup with code ${exitCode}.`;
6653
- process.stderr.write(`${error} Falling back to Rig attach view.
6654
- `);
6655
- return { launched: false, exitCode, command, error };
6656
- }
6657
- return { launched: true, exitCode, command };
6658
- }
6659
-
6660
6807
  // packages/cli/src/commands/run.ts
6661
6808
  function normalizeRemoteRunDetails(payload) {
6662
6809
  const run = payload.run;
@@ -6881,20 +7028,13 @@ async function executeRun(context, args) {
6881
7028
  throw new CliError2("run attach requires a run id.", 2);
6882
7029
  }
6883
7030
  let steered = false;
6884
- const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
6885
- if (shouldTryPiAttach && messageOption.value?.trim()) {
7031
+ if (messageOption.value?.trim()) {
6886
7032
  await steerRunViaServer(context, runId, messageOption.value.trim());
6887
7033
  steered = true;
6888
7034
  }
6889
- if (shouldTryPiAttach) {
6890
- const piSession = await launchPiRigSession(context, { runId });
6891
- if (piSession.launched) {
6892
- return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
6893
- }
6894
- }
6895
7035
  const attached = await attachRunOperatorView(context, {
6896
7036
  runId,
6897
- message: shouldTryPiAttach ? null : messageOption.value ?? null,
7037
+ message: null,
6898
7038
  once: once.value,
6899
7039
  follow: follow.value,
6900
7040
  pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
@@ -7621,20 +7761,7 @@ async function executeTask(context, args, options) {
7621
7761
  let attachDetails = null;
7622
7762
  if (!detachResult.value && context.outputMode === "text") {
7623
7763
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7624
- if (runtimeAdapter === "pi") {
7625
- const piSession = await launchPiRigSession(context, {
7626
- runId: submitted.runId,
7627
- taskId: selectedTaskId,
7628
- title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
7629
- runtimeAdapter
7630
- });
7631
- attachDetails = { mode: "pi", ...piSession };
7632
- if (!piSession.launched) {
7633
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7634
- }
7635
- } else {
7636
- attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7637
- }
7764
+ attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
7638
7765
  } else if (context.outputMode === "text") {
7639
7766
  console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
7640
7767
  }
@@ -7721,7 +7848,7 @@ async function executeTask(context, args, options) {
7721
7848
  // packages/cli/src/commands/task-run-driver.ts
7722
7849
  import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
7723
7850
  import { resolve as resolve20 } from "path";
7724
- import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
7851
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
7725
7852
  import { createInterface as createLineInterface } from "readline";
7726
7853
  import { loadConfig as loadConfig2 } from "@rig/core/load-config";
7727
7854
  import {
@@ -7778,6 +7905,7 @@ function buildPiRigBridgeEnv(input) {
7778
7905
  RIG_SERVER_RUN_ID: input.runId,
7779
7906
  RIG_TASK_ID: input.taskId,
7780
7907
  RIG_RUNTIME_ADAPTER: "pi",
7908
+ RIG_STEERING_POLL_MS: "0",
7781
7909
  ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
7782
7910
  ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
7783
7911
  ...githubBridgeEnv(githubToken)
@@ -7949,7 +8077,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
7949
8077
  }
7950
8078
  function createCommandRunner(binary) {
7951
8079
  return async (args, options) => {
7952
- const child = spawn3(binary, [...args], {
8080
+ const child = spawn2(binary, [...args], {
7953
8081
  cwd: options?.cwd,
7954
8082
  stdio: ["ignore", "pipe", "pipe"]
7955
8083
  });
@@ -8373,6 +8501,69 @@ function appendAssistantTimelineFromRecord(input) {
8373
8501
  }
8374
8502
  return nextAssistantText;
8375
8503
  }
8504
+ function appendPiRpcProtocolLogFromRecord(input) {
8505
+ const type = typeof input.record.type === "string" ? input.record.type : "";
8506
+ if (type === "response") {
8507
+ const command = typeof input.record.command === "string" ? input.record.command : "rpc";
8508
+ const success = input.record.success !== false;
8509
+ if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
8510
+ return true;
8511
+ }
8512
+ appendRunLog(input.projectRoot, input.runId, {
8513
+ id: input.nextRunLogId(),
8514
+ title: success ? "Pi RPC response" : "Pi RPC error",
8515
+ detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
8516
+ tone: success ? "tool" : "error",
8517
+ status: input.status,
8518
+ payload: input.record,
8519
+ createdAt: new Date().toISOString()
8520
+ });
8521
+ emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
8522
+ return true;
8523
+ }
8524
+ if (type !== "extension_ui_request")
8525
+ return false;
8526
+ const method = typeof input.record.method === "string" ? input.record.method : "ui";
8527
+ let title = "Pi UI event";
8528
+ let detail = method;
8529
+ let tone = "info";
8530
+ if (method === "notify") {
8531
+ title = "Pi notification";
8532
+ detail = String(input.record.message ?? "");
8533
+ tone = input.record.notifyType === "error" ? "error" : "info";
8534
+ } else if (method === "setStatus") {
8535
+ title = "Pi UI status";
8536
+ detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
8537
+ tone = "tool";
8538
+ } else if (method === "setWidget") {
8539
+ title = "Pi UI widget";
8540
+ const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
8541
+ detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
8542
+ tone = "tool";
8543
+ } else if (method === "setTitle") {
8544
+ title = "Pi UI title";
8545
+ detail = String(input.record.title ?? "");
8546
+ tone = "tool";
8547
+ } else if (method === "set_editor_text") {
8548
+ title = "Pi editor update";
8549
+ detail = String(input.record.text ?? "").slice(0, 500);
8550
+ tone = "tool";
8551
+ } else {
8552
+ title = "Pi UI request";
8553
+ detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
8554
+ }
8555
+ appendRunLog(input.projectRoot, input.runId, {
8556
+ id: input.nextRunLogId(),
8557
+ title,
8558
+ detail,
8559
+ tone,
8560
+ status: input.status,
8561
+ payload: input.record,
8562
+ createdAt: new Date().toISOString()
8563
+ });
8564
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
8565
+ return true;
8566
+ }
8376
8567
  function appendPiToolTimelineFromRecord(input) {
8377
8568
  const type = typeof input.record.type === "string" ? input.record.type : "";
8378
8569
  if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
@@ -8391,7 +8582,7 @@ function appendPiToolTimelineFromRecord(input) {
8391
8582
  }
8392
8583
  function isNonRenderablePiProtocolRecord(record) {
8393
8584
  const type = typeof record.type === "string" ? record.type : "";
8394
- return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
8585
+ return type === "agent_start" || type === "agent_end" || type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
8395
8586
  }
8396
8587
  function appendToolTimelineFromLog(input) {
8397
8588
  const title = typeof input.log.title === "string" ? input.log.title : "";
@@ -8554,11 +8745,8 @@ async function executeRigOwnedTaskRun(context, input) {
8554
8745
  ...input.model ? ["--model", input.model] : [],
8555
8746
  "--prompt"
8556
8747
  ] : input.runtimeAdapter === "pi" ? [
8557
- "--print",
8558
- "--verbose",
8559
8748
  "--mode",
8560
- "json",
8561
- "--no-session",
8749
+ "rpc",
8562
8750
  ...input.model ? ["--model", input.model] : []
8563
8751
  ] : [
8564
8752
  "--print",
@@ -8655,7 +8843,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8655
8843
  projectRoot: context.projectRoot,
8656
8844
  runId: input.runId,
8657
8845
  stage,
8658
- detail: stage === "Launch Pi" ? "Pi runtime bridge starting with pi-rig environment." : stage === "Plan" ? `${planningClassification.planningRequired ? "recorded" : "skipped"} (${planningClassification.reason}; size=${planningClassification.size}; risk=${planningClassification.risk})` : stage === "Implement" ? "Pi implementation pass is running." : null,
8846
+ 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,
8659
8847
  status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
8660
8848
  });
8661
8849
  }
@@ -8748,6 +8936,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8748
8936
  detail: detail ?? "Verifier review is running."
8749
8937
  });
8750
8938
  };
8939
+ const nextRunLogId = createRunLogIdFactory(input.runId);
8751
8940
  const handleWrapperEvent = (rawPayload) => {
8752
8941
  let event = null;
8753
8942
  try {
@@ -8882,9 +9071,23 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8882
9071
  }
8883
9072
  return true;
8884
9073
  }
9074
+ 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") {
9075
+ 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";
9076
+ 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")}`;
9077
+ appendRunLog(context.projectRoot, input.runId, {
9078
+ id: nextRunLogId(),
9079
+ title,
9080
+ detail,
9081
+ tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
9082
+ status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
9083
+ payload,
9084
+ createdAt: new Date().toISOString()
9085
+ });
9086
+ emitServerRunEvent({ type: "log", runId: input.runId, title });
9087
+ return true;
9088
+ }
8885
9089
  return false;
8886
9090
  };
8887
- const nextRunLogId = createRunLogIdFactory(input.runId);
8888
9091
  const handleAgentStdoutLine = (line) => {
8889
9092
  const trimmed = line.trim();
8890
9093
  if (!trimmed)
@@ -8918,6 +9121,15 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8918
9121
  try {
8919
9122
  const record = JSON.parse(trimmed);
8920
9123
  const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
9124
+ if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
9125
+ projectRoot: context.projectRoot,
9126
+ runId: input.runId,
9127
+ record,
9128
+ status: liveLogStatus,
9129
+ nextRunLogId
9130
+ })) {
9131
+ return;
9132
+ }
8921
9133
  if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
8922
9134
  emitServerRunEvent({ type: "timeline", runId: input.runId });
8923
9135
  return;
@@ -9063,7 +9275,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
9063
9275
  }
9064
9276
  for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
9065
9277
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
9066
- const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9278
+ const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
9067
9279
  cwd: context.projectRoot,
9068
9280
  env: childEnv,
9069
9281
  stdio: ["pipe", "pipe", "pipe"]
@@ -9410,7 +9622,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
9410
9622
  });
9411
9623
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
9412
9624
  const feedbackCommand = buildHostAgentCommand(message2);
9413
- const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
9625
+ const child = spawn2(feedbackCommand[0], feedbackCommand.slice(1), {
9414
9626
  cwd: latestRuntimeWorkspace ?? context.projectRoot,
9415
9627
  env: childEnv,
9416
9628
  stdio: ["pipe", "pipe", "pipe"]