@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 +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/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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
"
|
|
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 =
|
|
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 =
|
|
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"]
|