@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
|
@@ -618,6 +618,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
618
618
|
RIG_SERVER_RUN_ID: input.runId,
|
|
619
619
|
RIG_TASK_ID: input.taskId,
|
|
620
620
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
621
|
+
RIG_STEERING_POLL_MS: "0",
|
|
621
622
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
622
623
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
623
624
|
...githubBridgeEnv(githubToken)
|
|
@@ -1220,6 +1221,69 @@ function appendAssistantTimelineFromRecord(input) {
|
|
|
1220
1221
|
}
|
|
1221
1222
|
return nextAssistantText;
|
|
1222
1223
|
}
|
|
1224
|
+
function appendPiRpcProtocolLogFromRecord(input) {
|
|
1225
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
1226
|
+
if (type === "response") {
|
|
1227
|
+
const command = typeof input.record.command === "string" ? input.record.command : "rpc";
|
|
1228
|
+
const success = input.record.success !== false;
|
|
1229
|
+
if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
1233
|
+
id: input.nextRunLogId(),
|
|
1234
|
+
title: success ? "Pi RPC response" : "Pi RPC error",
|
|
1235
|
+
detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
|
|
1236
|
+
tone: success ? "tool" : "error",
|
|
1237
|
+
status: input.status,
|
|
1238
|
+
payload: input.record,
|
|
1239
|
+
createdAt: new Date().toISOString()
|
|
1240
|
+
});
|
|
1241
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
if (type !== "extension_ui_request")
|
|
1245
|
+
return false;
|
|
1246
|
+
const method = typeof input.record.method === "string" ? input.record.method : "ui";
|
|
1247
|
+
let title = "Pi UI event";
|
|
1248
|
+
let detail = method;
|
|
1249
|
+
let tone = "info";
|
|
1250
|
+
if (method === "notify") {
|
|
1251
|
+
title = "Pi notification";
|
|
1252
|
+
detail = String(input.record.message ?? "");
|
|
1253
|
+
tone = input.record.notifyType === "error" ? "error" : "info";
|
|
1254
|
+
} else if (method === "setStatus") {
|
|
1255
|
+
title = "Pi UI status";
|
|
1256
|
+
detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
|
|
1257
|
+
tone = "tool";
|
|
1258
|
+
} else if (method === "setWidget") {
|
|
1259
|
+
title = "Pi UI widget";
|
|
1260
|
+
const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
|
|
1261
|
+
detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
|
|
1262
|
+
tone = "tool";
|
|
1263
|
+
} else if (method === "setTitle") {
|
|
1264
|
+
title = "Pi UI title";
|
|
1265
|
+
detail = String(input.record.title ?? "");
|
|
1266
|
+
tone = "tool";
|
|
1267
|
+
} else if (method === "set_editor_text") {
|
|
1268
|
+
title = "Pi editor update";
|
|
1269
|
+
detail = String(input.record.text ?? "").slice(0, 500);
|
|
1270
|
+
tone = "tool";
|
|
1271
|
+
} else {
|
|
1272
|
+
title = "Pi UI request";
|
|
1273
|
+
detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
|
|
1274
|
+
}
|
|
1275
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
1276
|
+
id: input.nextRunLogId(),
|
|
1277
|
+
title,
|
|
1278
|
+
detail,
|
|
1279
|
+
tone,
|
|
1280
|
+
status: input.status,
|
|
1281
|
+
payload: input.record,
|
|
1282
|
+
createdAt: new Date().toISOString()
|
|
1283
|
+
});
|
|
1284
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title });
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1223
1287
|
function appendPiToolTimelineFromRecord(input) {
|
|
1224
1288
|
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
1225
1289
|
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
@@ -1238,7 +1302,7 @@ function appendPiToolTimelineFromRecord(input) {
|
|
|
1238
1302
|
}
|
|
1239
1303
|
function isNonRenderablePiProtocolRecord(record) {
|
|
1240
1304
|
const type = typeof record.type === "string" ? record.type : "";
|
|
1241
|
-
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");
|
|
1305
|
+
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");
|
|
1242
1306
|
}
|
|
1243
1307
|
function appendToolTimelineFromLog(input) {
|
|
1244
1308
|
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
@@ -1401,11 +1465,8 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1401
1465
|
...input.model ? ["--model", input.model] : [],
|
|
1402
1466
|
"--prompt"
|
|
1403
1467
|
] : input.runtimeAdapter === "pi" ? [
|
|
1404
|
-
"--print",
|
|
1405
|
-
"--verbose",
|
|
1406
1468
|
"--mode",
|
|
1407
|
-
"
|
|
1408
|
-
"--no-session",
|
|
1469
|
+
"rpc",
|
|
1409
1470
|
...input.model ? ["--model", input.model] : []
|
|
1410
1471
|
] : [
|
|
1411
1472
|
"--print",
|
|
@@ -1502,7 +1563,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1502
1563
|
projectRoot: context.projectRoot,
|
|
1503
1564
|
runId: input.runId,
|
|
1504
1565
|
stage,
|
|
1505
|
-
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,
|
|
1566
|
+
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,
|
|
1506
1567
|
status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
|
|
1507
1568
|
});
|
|
1508
1569
|
}
|
|
@@ -1595,6 +1656,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1595
1656
|
detail: detail ?? "Verifier review is running."
|
|
1596
1657
|
});
|
|
1597
1658
|
};
|
|
1659
|
+
const nextRunLogId = createRunLogIdFactory(input.runId);
|
|
1598
1660
|
const handleWrapperEvent = (rawPayload) => {
|
|
1599
1661
|
let event = null;
|
|
1600
1662
|
try {
|
|
@@ -1729,9 +1791,23 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1729
1791
|
}
|
|
1730
1792
|
return true;
|
|
1731
1793
|
}
|
|
1794
|
+
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") {
|
|
1795
|
+
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";
|
|
1796
|
+
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")}`;
|
|
1797
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
1798
|
+
id: nextRunLogId(),
|
|
1799
|
+
title,
|
|
1800
|
+
detail,
|
|
1801
|
+
tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
|
|
1802
|
+
status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
|
|
1803
|
+
payload,
|
|
1804
|
+
createdAt: new Date().toISOString()
|
|
1805
|
+
});
|
|
1806
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title });
|
|
1807
|
+
return true;
|
|
1808
|
+
}
|
|
1732
1809
|
return false;
|
|
1733
1810
|
};
|
|
1734
|
-
const nextRunLogId = createRunLogIdFactory(input.runId);
|
|
1735
1811
|
const handleAgentStdoutLine = (line) => {
|
|
1736
1812
|
const trimmed = line.trim();
|
|
1737
1813
|
if (!trimmed)
|
|
@@ -1765,6 +1841,15 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1765
1841
|
try {
|
|
1766
1842
|
const record = JSON.parse(trimmed);
|
|
1767
1843
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
1844
|
+
if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
|
|
1845
|
+
projectRoot: context.projectRoot,
|
|
1846
|
+
runId: input.runId,
|
|
1847
|
+
record,
|
|
1848
|
+
status: liveLogStatus,
|
|
1849
|
+
nextRunLogId
|
|
1850
|
+
})) {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1768
1853
|
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
1769
1854
|
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1770
1855
|
return;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
2
4
|
// packages/cli/src/commands/task.ts
|
|
3
5
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4
6
|
import { spawnSync } from "child_process";
|
|
@@ -13,9 +15,6 @@ import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
|
13
15
|
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
14
16
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
17
|
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
16
|
-
function formatCommand(parts) {
|
|
17
|
-
return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
18
|
-
}
|
|
19
18
|
function takeFlag(args, flag) {
|
|
20
19
|
const rest = [];
|
|
21
20
|
let value = false;
|
|
@@ -412,12 +411,6 @@ async function defaultCommandRunner(command, options = {}) {
|
|
|
412
411
|
function resolvePiRigExtensionPath(homeDir) {
|
|
413
412
|
return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
|
|
414
413
|
}
|
|
415
|
-
function resolvePiRigPackageSource(projectRoot, exists = existsSync3) {
|
|
416
|
-
const localPackage = resolve3(projectRoot, "packages", "pi-rig");
|
|
417
|
-
if (exists(resolve3(localPackage, "package.json")))
|
|
418
|
-
return localPackage;
|
|
419
|
-
return `npm:${PI_RIG_PACKAGE_NAME}`;
|
|
420
|
-
}
|
|
421
414
|
function resolvePiHomeDir(inputHomeDir) {
|
|
422
415
|
return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
|
|
423
416
|
}
|
|
@@ -976,6 +969,50 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
976
969
|
|
|
977
970
|
// packages/cli/src/commands/_operator-view.ts
|
|
978
971
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
972
|
+
var CANONICAL_STAGES2 = [
|
|
973
|
+
"Connect",
|
|
974
|
+
"GitHub/task sync",
|
|
975
|
+
"Prepare workspace",
|
|
976
|
+
"Launch Pi",
|
|
977
|
+
"Plan",
|
|
978
|
+
"Implement",
|
|
979
|
+
"Validate",
|
|
980
|
+
"Commit",
|
|
981
|
+
"Open PR",
|
|
982
|
+
"Review/CI",
|
|
983
|
+
"Merge",
|
|
984
|
+
"Complete"
|
|
985
|
+
];
|
|
986
|
+
var GREEN = "\x1B[32m";
|
|
987
|
+
var BLUE = "\x1B[34m";
|
|
988
|
+
var MAGENTA = "\x1B[35m";
|
|
989
|
+
var YELLOW = "\x1B[33m";
|
|
990
|
+
var RED = "\x1B[31m";
|
|
991
|
+
var DIM = "\x1B[2m";
|
|
992
|
+
var BOLD = "\x1B[1m";
|
|
993
|
+
var RESET = "\x1B[0m";
|
|
994
|
+
async function loadPiTuiRuntime() {
|
|
995
|
+
try {
|
|
996
|
+
return await import("@earendil-works/pi-tui");
|
|
997
|
+
} catch {
|
|
998
|
+
const base = new URL("../../../pi/packages/tui/src/", import.meta.url);
|
|
999
|
+
const [tui, input, terminal, keys, utils] = await Promise.all([
|
|
1000
|
+
import(new URL("tui.ts", base).href),
|
|
1001
|
+
import(new URL("components/input.ts", base).href),
|
|
1002
|
+
import(new URL("terminal.ts", base).href),
|
|
1003
|
+
import(new URL("keys.ts", base).href),
|
|
1004
|
+
import(new URL("utils.ts", base).href)
|
|
1005
|
+
]);
|
|
1006
|
+
return {
|
|
1007
|
+
Container: tui.Container,
|
|
1008
|
+
TUI: tui.TUI,
|
|
1009
|
+
Input: input.Input,
|
|
1010
|
+
ProcessTerminal: terminal.ProcessTerminal,
|
|
1011
|
+
matchesKey: keys.matchesKey,
|
|
1012
|
+
truncateToWidth: utils.truncateToWidth
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
979
1016
|
function runStatusFromPayload(payload) {
|
|
980
1017
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
981
1018
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -1014,12 +1051,201 @@ async function readOperatorSnapshot(context, runId, options = {}) {
|
|
|
1014
1051
|
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
1015
1052
|
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
1016
1053
|
}
|
|
1054
|
+
function unwrapRun(runPayload) {
|
|
1055
|
+
return runPayload.run && typeof runPayload.run === "object" && !Array.isArray(runPayload.run) ? runPayload.run : runPayload;
|
|
1056
|
+
}
|
|
1057
|
+
function logDetail2(log) {
|
|
1058
|
+
return typeof log.detail === "string" ? log.detail.trim() : "";
|
|
1059
|
+
}
|
|
1060
|
+
function logTitle(log) {
|
|
1061
|
+
return typeof log.title === "string" ? log.title.trim() : "";
|
|
1062
|
+
}
|
|
1063
|
+
function renderAssistantTextFromTimeline(entries) {
|
|
1064
|
+
const assistant = entries.filter((entry) => entry.type === "assistant_message" && typeof entry.text === "string").at(-1);
|
|
1065
|
+
const text = typeof assistant?.text === "string" ? assistant.text.trimEnd() : "";
|
|
1066
|
+
if (!text)
|
|
1067
|
+
return [];
|
|
1068
|
+
return [`${BLUE}${BOLD}Remote Pi assistant${RESET}`, ...text.split(/\r?\n/).slice(-18)];
|
|
1069
|
+
}
|
|
1070
|
+
function renderToolLines(entries) {
|
|
1071
|
+
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());
|
|
1072
|
+
}
|
|
1073
|
+
function renderStageLines(logs) {
|
|
1074
|
+
const latestByStage = new Map;
|
|
1075
|
+
for (const log of logs) {
|
|
1076
|
+
const title = logTitle(log).toLowerCase();
|
|
1077
|
+
const stageName = String(log.stage ?? "").toLowerCase();
|
|
1078
|
+
const stage = CANONICAL_STAGES2.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
1079
|
+
if (stage)
|
|
1080
|
+
latestByStage.set(stage, log);
|
|
1081
|
+
}
|
|
1082
|
+
return CANONICAL_STAGES2.map((stage) => {
|
|
1083
|
+
const log = latestByStage.get(stage);
|
|
1084
|
+
const status = String(log?.status ?? "pending");
|
|
1085
|
+
const detail = log ? logDetail2(log) : "";
|
|
1086
|
+
const color = status === "completed" ? GREEN : status === "failed" || status === "rejected" ? RED : status === "pending" ? DIM : YELLOW;
|
|
1087
|
+
const mark = status === "completed" ? "\u2713" : status === "pending" ? "\xB7" : status === "failed" ? "\u2717" : "\u25B6";
|
|
1088
|
+
return `${color}${mark} ${stage}${RESET}${detail ? ` ${DIM}\u2014 ${detail.slice(0, 140)}${RESET}` : ""}`;
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
function renderEventLines(logs) {
|
|
1092
|
+
return logs.filter((log) => !CANONICAL_STAGES2.some((stage) => stage.toLowerCase() === logTitle(log).toLowerCase())).slice(-12).flatMap((log) => {
|
|
1093
|
+
const title = logTitle(log) || "Rig event";
|
|
1094
|
+
const detail = logDetail2(log);
|
|
1095
|
+
if (!detail)
|
|
1096
|
+
return [];
|
|
1097
|
+
const tone = String(log.tone ?? "");
|
|
1098
|
+
const color = tone === "error" ? RED : tone === "tool" ? MAGENTA : DIM;
|
|
1099
|
+
return [`${color}[${title}]${RESET} ${detail.slice(0, 220)}`];
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
class RigRunComponent {
|
|
1104
|
+
truncateToWidth;
|
|
1105
|
+
snapshot = { run: {}, logs: [], timeline: [] };
|
|
1106
|
+
localEvents = [];
|
|
1107
|
+
constructor(truncateToWidth) {
|
|
1108
|
+
this.truncateToWidth = truncateToWidth;
|
|
1109
|
+
}
|
|
1110
|
+
update(snapshot) {
|
|
1111
|
+
this.snapshot = snapshot;
|
|
1112
|
+
}
|
|
1113
|
+
addLocalEvent(message2) {
|
|
1114
|
+
this.localEvents.push(`${new Date().toLocaleTimeString()} ${message2}`);
|
|
1115
|
+
this.localEvents = this.localEvents.slice(-8);
|
|
1116
|
+
}
|
|
1117
|
+
invalidate() {}
|
|
1118
|
+
render(width) {
|
|
1119
|
+
const run = unwrapRun(this.snapshot.run);
|
|
1120
|
+
const runId = String(run.runId ?? run.id ?? "run");
|
|
1121
|
+
const status = String(run.status ?? "unknown");
|
|
1122
|
+
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";
|
|
1123
|
+
const lines = [
|
|
1124
|
+
`${BOLD}Rig Pi frontend${RESET} ${DIM}(local Pi TUI \u2192 Rig server \u2192 worker Pi backend)${RESET}`,
|
|
1125
|
+
`${BOLD}${runId}${RESET} \xB7 ${status} \xB7 ${DIM}${worker}${RESET}`,
|
|
1126
|
+
"",
|
|
1127
|
+
`${BOLD}Rig flow${RESET}`,
|
|
1128
|
+
...renderStageLines(this.snapshot.logs ?? []),
|
|
1129
|
+
"",
|
|
1130
|
+
...renderAssistantTextFromTimeline(this.snapshot.timeline ?? []),
|
|
1131
|
+
...renderToolLines(this.snapshot.timeline ?? []),
|
|
1132
|
+
"",
|
|
1133
|
+
`${BOLD}Rig / Pi events${RESET}`,
|
|
1134
|
+
...renderEventLines(this.snapshot.logs ?? []),
|
|
1135
|
+
...this.localEvents.map((event) => `${GREEN}[frontend]${RESET} ${event}`),
|
|
1136
|
+
""
|
|
1137
|
+
];
|
|
1138
|
+
return lines.slice(-42).map((line) => this.truncateToWidth(line, Math.max(10, width)));
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
class RigInputComponent {
|
|
1143
|
+
matchesKey;
|
|
1144
|
+
truncateToWidth;
|
|
1145
|
+
input;
|
|
1146
|
+
status = "Type text, /skill:..., or remote Pi slash commands. Local: /stop /detach.";
|
|
1147
|
+
constructor(InputCtor, matchesKey, truncateToWidth, onSubmit, onEscape) {
|
|
1148
|
+
this.matchesKey = matchesKey;
|
|
1149
|
+
this.truncateToWidth = truncateToWidth;
|
|
1150
|
+
this.input = new InputCtor;
|
|
1151
|
+
this.input.onSubmit = (value) => {
|
|
1152
|
+
const text = value.trim();
|
|
1153
|
+
this.input.setValue("");
|
|
1154
|
+
if (text)
|
|
1155
|
+
Promise.resolve(onSubmit(text));
|
|
1156
|
+
};
|
|
1157
|
+
this.input.onEscape = onEscape;
|
|
1158
|
+
}
|
|
1159
|
+
handleInput(data) {
|
|
1160
|
+
if (this.matchesKey(data, "ctrl+d")) {
|
|
1161
|
+
this.input.onEscape?.();
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
this.input.handleInput?.(data);
|
|
1165
|
+
}
|
|
1166
|
+
setStatus(status) {
|
|
1167
|
+
this.status = status;
|
|
1168
|
+
}
|
|
1169
|
+
invalidate() {
|
|
1170
|
+
this.input.invalidate();
|
|
1171
|
+
}
|
|
1172
|
+
render(width) {
|
|
1173
|
+
return [
|
|
1174
|
+
`${DIM}${this.truncateToWidth(this.status, Math.max(10, width))}${RESET}`,
|
|
1175
|
+
`${GREEN}${BOLD}You \u2192 worker Pi:${RESET}`,
|
|
1176
|
+
...this.input.render(width)
|
|
1177
|
+
];
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
async function attachRunPiTuiFrontend(context, input) {
|
|
1181
|
+
const piTui = await loadPiTuiRuntime();
|
|
1182
|
+
const terminal = new piTui.ProcessTerminal;
|
|
1183
|
+
const tui = new piTui.TUI(terminal);
|
|
1184
|
+
const root = new piTui.Container;
|
|
1185
|
+
const runView = new RigRunComponent(piTui.truncateToWidth);
|
|
1186
|
+
let detached = false;
|
|
1187
|
+
let steered = input.steered === true;
|
|
1188
|
+
let latest = await readOperatorSnapshot(context, input.runId);
|
|
1189
|
+
let timelineCursor = latest.timelineCursor;
|
|
1190
|
+
runView.update(latest);
|
|
1191
|
+
if (steered)
|
|
1192
|
+
runView.addLocalEvent("initial message queued to worker Pi.");
|
|
1193
|
+
const stop = () => {
|
|
1194
|
+
detached = true;
|
|
1195
|
+
tui.stop();
|
|
1196
|
+
};
|
|
1197
|
+
const inputView = new RigInputComponent(piTui.Input, piTui.matchesKey, piTui.truncateToWidth, async (line) => {
|
|
1198
|
+
if (line === "/detach" || line === "/quit" || line === "/q") {
|
|
1199
|
+
runView.addLocalEvent("detached from run.");
|
|
1200
|
+
stop();
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (line === "/stop") {
|
|
1204
|
+
await stopRunViaServer(context, input.runId);
|
|
1205
|
+
runView.addLocalEvent("stop requested.");
|
|
1206
|
+
stop();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
await steerRunViaServer(context, input.runId, line);
|
|
1210
|
+
steered = true;
|
|
1211
|
+
runView.addLocalEvent(`queued to worker Pi: ${line.slice(0, 160)}`);
|
|
1212
|
+
tui.requestRender();
|
|
1213
|
+
}, stop);
|
|
1214
|
+
root.addChild(runView);
|
|
1215
|
+
root.addChild(inputView);
|
|
1216
|
+
tui.addChild(root);
|
|
1217
|
+
tui.setFocus(inputView.input);
|
|
1218
|
+
tui.start();
|
|
1219
|
+
tui.requestRender(true);
|
|
1220
|
+
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 1000));
|
|
1221
|
+
try {
|
|
1222
|
+
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(latest.run))) {
|
|
1223
|
+
await Bun.sleep(pollMs);
|
|
1224
|
+
latest = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1225
|
+
timelineCursor = latest.timelineCursor;
|
|
1226
|
+
runView.update(latest);
|
|
1227
|
+
inputView.setStatus(`Remote worker ${runStatusFromPayload(latest.run)}. Input is forwarded to worker Pi; /stop /detach are local controls.`);
|
|
1228
|
+
tui.requestRender();
|
|
1229
|
+
}
|
|
1230
|
+
} finally {
|
|
1231
|
+
if (!detached)
|
|
1232
|
+
tui.stop();
|
|
1233
|
+
}
|
|
1234
|
+
return { ...latest, timelineCursor, steered, detached, rendered: renderOperatorSnapshot(latest) };
|
|
1235
|
+
}
|
|
1017
1236
|
async function attachRunOperatorView(context, input) {
|
|
1018
1237
|
let steered = false;
|
|
1019
1238
|
if (input.message?.trim()) {
|
|
1020
1239
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
1021
1240
|
steered = true;
|
|
1022
1241
|
}
|
|
1242
|
+
if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
1243
|
+
return attachRunPiTuiFrontend(context, {
|
|
1244
|
+
runId: input.runId,
|
|
1245
|
+
pollMs: input.pollMs,
|
|
1246
|
+
steered
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1023
1249
|
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
1024
1250
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
1025
1251
|
if (context.outputMode === "text") {
|
|
@@ -1137,92 +1363,6 @@ function formatSubmittedRun(input) {
|
|
|
1137
1363
|
`);
|
|
1138
1364
|
}
|
|
1139
1365
|
|
|
1140
|
-
// packages/cli/src/commands/_pi-session.ts
|
|
1141
|
-
import { spawn } from "child_process";
|
|
1142
|
-
function buildPiRigSessionEnv(input) {
|
|
1143
|
-
return {
|
|
1144
|
-
RIG_PROJECT_ROOT: input.projectRoot,
|
|
1145
|
-
PROJECT_RIG_ROOT: input.projectRoot,
|
|
1146
|
-
RIG_RUN_ID: input.runId,
|
|
1147
|
-
RIG_SERVER_RUN_ID: input.runId,
|
|
1148
|
-
RIG_RUNTIME_ADAPTER: "pi",
|
|
1149
|
-
RIG_SERVER_URL: input.serverUrl,
|
|
1150
|
-
RIG_SERVER_BASE_URL: input.serverUrl,
|
|
1151
|
-
RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
|
|
1152
|
-
RIG_PI_OPERATOR_SESSION: "1",
|
|
1153
|
-
...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
|
|
1154
|
-
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
function shellBinary(name) {
|
|
1158
|
-
const explicit = process.env.RIG_PI_BINARY?.trim();
|
|
1159
|
-
if (explicit)
|
|
1160
|
-
return explicit;
|
|
1161
|
-
return Bun.which(name) || name;
|
|
1162
|
-
}
|
|
1163
|
-
function buildPiRigSessionCommand(input) {
|
|
1164
|
-
const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
|
|
1165
|
-
const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
|
|
1166
|
-
const initialCommand = `/rig attach ${input.runId}`;
|
|
1167
|
-
return [
|
|
1168
|
-
shellBinary("pi"),
|
|
1169
|
-
"--no-extensions",
|
|
1170
|
-
"--extension",
|
|
1171
|
-
extensionSource,
|
|
1172
|
-
initialCommand
|
|
1173
|
-
];
|
|
1174
|
-
}
|
|
1175
|
-
async function launchPiRigSession(context, input) {
|
|
1176
|
-
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1177
|
-
return { launched: false, exitCode: null, command: [] };
|
|
1178
|
-
}
|
|
1179
|
-
if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
|
|
1180
|
-
return { launched: false, exitCode: null, command: [] };
|
|
1181
|
-
}
|
|
1182
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
1183
|
-
const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
|
|
1184
|
-
const env = {
|
|
1185
|
-
...process.env,
|
|
1186
|
-
...buildPiRigSessionEnv({
|
|
1187
|
-
projectRoot: context.projectRoot,
|
|
1188
|
-
runId: input.runId,
|
|
1189
|
-
taskId: input.taskId,
|
|
1190
|
-
serverUrl: server.baseUrl,
|
|
1191
|
-
authToken: server.authToken
|
|
1192
|
-
})
|
|
1193
|
-
};
|
|
1194
|
-
process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
|
|
1195
|
-
`);
|
|
1196
|
-
process.stdout.write(`Pi command: ${formatCommand(command)}
|
|
1197
|
-
`);
|
|
1198
|
-
const launchedAt = Date.now();
|
|
1199
|
-
const child = spawn(command[0], command.slice(1), {
|
|
1200
|
-
cwd: context.projectRoot,
|
|
1201
|
-
env,
|
|
1202
|
-
stdio: "inherit"
|
|
1203
|
-
});
|
|
1204
|
-
const launchError = await new Promise((resolve4) => {
|
|
1205
|
-
child.once("error", (error) => {
|
|
1206
|
-
resolve4({ error: error.message });
|
|
1207
|
-
});
|
|
1208
|
-
child.once("close", (code) => resolve4({ code }));
|
|
1209
|
-
});
|
|
1210
|
-
if ("error" in launchError) {
|
|
1211
|
-
process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
|
|
1212
|
-
`);
|
|
1213
|
-
return { launched: false, exitCode: null, command, error: launchError.error };
|
|
1214
|
-
}
|
|
1215
|
-
const exitCode = launchError.code;
|
|
1216
|
-
const elapsedMs = Date.now() - launchedAt;
|
|
1217
|
-
if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
|
|
1218
|
-
const error = `Pi exited during startup with code ${exitCode}.`;
|
|
1219
|
-
process.stderr.write(`${error} Falling back to Rig attach view.
|
|
1220
|
-
`);
|
|
1221
|
-
return { launched: false, exitCode, command, error };
|
|
1222
|
-
}
|
|
1223
|
-
return { launched: true, exitCode, command };
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
1366
|
// packages/cli/src/commands/task.ts
|
|
1227
1367
|
import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
|
|
1228
1368
|
import { loadConfig } from "@rig/core/load-config";
|
|
@@ -1614,20 +1754,7 @@ async function executeTask(context, args, options) {
|
|
|
1614
1754
|
let attachDetails = null;
|
|
1615
1755
|
if (!detachResult.value && context.outputMode === "text") {
|
|
1616
1756
|
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
1617
|
-
|
|
1618
|
-
const piSession = await launchPiRigSession(context, {
|
|
1619
|
-
runId: submitted.runId,
|
|
1620
|
-
taskId: selectedTaskId,
|
|
1621
|
-
title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
|
|
1622
|
-
runtimeAdapter
|
|
1623
|
-
});
|
|
1624
|
-
attachDetails = { mode: "pi", ...piSession };
|
|
1625
|
-
if (!piSession.launched) {
|
|
1626
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
1627
|
-
}
|
|
1628
|
-
} else {
|
|
1629
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
1630
|
-
}
|
|
1757
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
1631
1758
|
} else if (context.outputMode === "text") {
|
|
1632
1759
|
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
1633
1760
|
}
|