@h-rig/cli 0.0.6-alpha.21 → 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 +332 -120
- package/dist/src/commands/_operator-view.js +235 -0
- package/dist/src/commands/run.js +237 -111
- package/dist/src/commands/task-run-driver.js +92 -7
- package/dist/src/commands/task.js +236 -109
- package/dist/src/commands.js +332 -120
- package/dist/src/index.js +332 -120
- package/package.json +6 -5
- package/dist/src/commands/_pi-session.js +0 -253
package/dist/src/commands.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
"
|
|
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 =
|
|
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 =
|
|
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"]
|