@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.14
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 +710 -226
- package/dist/src/commands/_doctor-checks.js +7 -20
- package/dist/src/commands/_operator-surface.js +157 -0
- package/dist/src/commands/_operator-view.js +160 -51
- package/dist/src/commands/_preflight.js +30 -26
- package/dist/src/commands/_server-client.js +46 -22
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +21 -13
- package/dist/src/commands/agent.js +1 -0
- package/dist/src/commands/doctor.js +7 -20
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +183 -44
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +172 -76
- package/dist/src/commands/server.js +7 -20
- package/dist/src/commands/setup.js +7 -20
- package/dist/src/commands/task-run-driver.js +446 -53
- package/dist/src/commands/task.js +231 -98
- package/dist/src/commands.js +702 -218
- package/dist/src/index.js +710 -226
- package/package.json +5 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/task.ts
|
|
3
3
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4
|
-
import { spawnSync
|
|
5
|
-
import { createInterface as
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
6
6
|
import { resolve as resolve4 } from "path";
|
|
7
7
|
|
|
8
8
|
// packages/cli/src/runner.ts
|
|
@@ -188,11 +188,10 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// packages/cli/src/commands/_server-client.ts
|
|
191
|
-
import { spawnSync } from "child_process";
|
|
192
191
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
193
192
|
import { resolve as resolve2 } from "path";
|
|
194
193
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
195
|
-
var
|
|
194
|
+
var scopedGitHubBearerTokens = new Map;
|
|
196
195
|
function cleanToken(value) {
|
|
197
196
|
const trimmed = value?.trim();
|
|
198
197
|
return trimmed ? trimmed : null;
|
|
@@ -209,25 +208,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
209
208
|
}
|
|
210
209
|
}
|
|
211
210
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
const scopedKey = resolve2(projectRoot);
|
|
212
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
213
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
214
214
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
215
|
-
if (privateSession)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
220
|
-
if (envToken) {
|
|
221
|
-
cachedGitHubBearerToken = envToken;
|
|
222
|
-
return cachedGitHubBearerToken;
|
|
223
|
-
}
|
|
224
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
225
|
-
encoding: "utf8",
|
|
226
|
-
timeout: 5000,
|
|
227
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
228
|
-
});
|
|
229
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
230
|
-
return cachedGitHubBearerToken;
|
|
215
|
+
if (privateSession)
|
|
216
|
+
return privateSession;
|
|
217
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
231
218
|
}
|
|
232
219
|
async function ensureServerForCli(projectRoot) {
|
|
233
220
|
try {
|
|
@@ -347,6 +334,15 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
347
334
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
348
335
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
349
336
|
}
|
|
337
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
338
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
339
|
+
if (options.limit !== undefined)
|
|
340
|
+
url.searchParams.set("limit", String(options.limit));
|
|
341
|
+
if (options.cursor)
|
|
342
|
+
url.searchParams.set("cursor", options.cursor);
|
|
343
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
344
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
345
|
+
}
|
|
350
346
|
async function stopRunViaServer(context, runId) {
|
|
351
347
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
352
348
|
method: "POST",
|
|
@@ -522,6 +518,9 @@ function permissionAllowsPr(payload) {
|
|
|
522
518
|
}
|
|
523
519
|
return null;
|
|
524
520
|
}
|
|
521
|
+
function isNotFoundError(error) {
|
|
522
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
523
|
+
}
|
|
525
524
|
function projectCheckoutReady(payload) {
|
|
526
525
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
527
526
|
return null;
|
|
@@ -554,19 +553,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
554
553
|
const checks = [];
|
|
555
554
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
556
555
|
const taskId = options.taskId?.trim() || null;
|
|
556
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
557
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
558
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
559
|
+
let legacyServerCompatibility = false;
|
|
557
560
|
try {
|
|
558
561
|
await request("/api/server/status");
|
|
559
562
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
560
563
|
} catch (error) {
|
|
561
|
-
|
|
564
|
+
if (isNotFoundError(error)) {
|
|
565
|
+
try {
|
|
566
|
+
await request("/health");
|
|
567
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
568
|
+
checks.push(requiresCurrentRunApi && !allowLocalLegacyTaskRunCompatibility ? preflightCheck("server", "Rig server reachable", "fail", "legacy /health endpoint only; current task-run APIs are required", "Upgrade/select the Rig server before launching a task run.") : preflightCheck("server", "Rig server reachable", "pass", allowLocalLegacyTaskRunCompatibility ? "local legacy /health endpoint; submit endpoint will be authoritative" : "legacy /health endpoint"));
|
|
569
|
+
} catch (healthError) {
|
|
570
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
574
|
+
}
|
|
562
575
|
}
|
|
563
576
|
const repo = readRepoConnection(context.projectRoot);
|
|
564
|
-
checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
|
|
577
|
+
checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
|
|
565
578
|
try {
|
|
566
579
|
const auth = await request("/api/github/auth/status");
|
|
567
|
-
checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
580
|
+
checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
568
581
|
} catch (error) {
|
|
569
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
582
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
570
583
|
}
|
|
571
584
|
try {
|
|
572
585
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -594,9 +607,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
594
607
|
try {
|
|
595
608
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
596
609
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
597
|
-
checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
|
|
610
|
+
checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
|
|
598
611
|
} catch (error) {
|
|
599
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
612
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
600
613
|
}
|
|
601
614
|
try {
|
|
602
615
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -705,8 +718,135 @@ function withMutedConsole(mute, fn) {
|
|
|
705
718
|
}
|
|
706
719
|
}
|
|
707
720
|
|
|
708
|
-
// packages/cli/src/commands/
|
|
709
|
-
import { createInterface } from "readline
|
|
721
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
722
|
+
import { createInterface } from "readline";
|
|
723
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
724
|
+
var CANONICAL_STAGES = [
|
|
725
|
+
"Connect",
|
|
726
|
+
"GitHub/task sync",
|
|
727
|
+
"Prepare workspace",
|
|
728
|
+
"Launch Pi",
|
|
729
|
+
"Plan",
|
|
730
|
+
"Implement",
|
|
731
|
+
"Validate",
|
|
732
|
+
"Commit",
|
|
733
|
+
"Open PR",
|
|
734
|
+
"Review/CI",
|
|
735
|
+
"Merge",
|
|
736
|
+
"Complete"
|
|
737
|
+
];
|
|
738
|
+
function logDetail(log) {
|
|
739
|
+
return typeof log.detail === "string" ? log.detail.trim() : "";
|
|
740
|
+
}
|
|
741
|
+
function entryId(entry, fallback) {
|
|
742
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
743
|
+
}
|
|
744
|
+
function renderOperatorSnapshot(snapshot) {
|
|
745
|
+
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
746
|
+
const runId = String(run.runId ?? run.id ?? "run");
|
|
747
|
+
const status = String(run.status ?? "unknown");
|
|
748
|
+
const logs = snapshot.logs ?? [];
|
|
749
|
+
const latestByStage = new Map;
|
|
750
|
+
for (const log of logs) {
|
|
751
|
+
const title = String(log.title ?? "").toLowerCase();
|
|
752
|
+
const stageName = String(log.stage ?? "").toLowerCase();
|
|
753
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
754
|
+
if (stage)
|
|
755
|
+
latestByStage.set(stage, log);
|
|
756
|
+
}
|
|
757
|
+
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
758
|
+
const match = latestByStage.get(stage);
|
|
759
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
760
|
+
});
|
|
761
|
+
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
762
|
+
`);
|
|
763
|
+
}
|
|
764
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
765
|
+
let lastSnapshot = "";
|
|
766
|
+
const assistantTextById = new Map;
|
|
767
|
+
const seenTimeline = new Set;
|
|
768
|
+
const seenLogs = new Set;
|
|
769
|
+
const writeLine = (line) => output.write(`${line}
|
|
770
|
+
`);
|
|
771
|
+
return {
|
|
772
|
+
renderSnapshot(snapshot) {
|
|
773
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
774
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
775
|
+
writeLine(rendered);
|
|
776
|
+
lastSnapshot = rendered;
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
renderTimeline(entries) {
|
|
780
|
+
for (const [index, entry] of entries.entries()) {
|
|
781
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
782
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
783
|
+
const text = entry.text;
|
|
784
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
785
|
+
if (text.startsWith(previousText)) {
|
|
786
|
+
const delta = text.slice(previousText.length);
|
|
787
|
+
if (delta)
|
|
788
|
+
output.write(delta);
|
|
789
|
+
} else if (text.trim() && text !== previousText) {
|
|
790
|
+
writeLine(`
|
|
791
|
+
[Pi assistant]`);
|
|
792
|
+
output.write(text);
|
|
793
|
+
}
|
|
794
|
+
assistantTextById.set(id, text);
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
if (seenTimeline.has(id))
|
|
798
|
+
continue;
|
|
799
|
+
seenTimeline.add(id);
|
|
800
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
801
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (entry.type === "timeline_warning") {
|
|
805
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
renderLogs(entries) {
|
|
810
|
+
for (const [index, entry] of entries.entries()) {
|
|
811
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
812
|
+
if (seenLogs.has(id))
|
|
813
|
+
continue;
|
|
814
|
+
seenLogs.add(id);
|
|
815
|
+
const title = String(entry.title ?? "");
|
|
816
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
817
|
+
continue;
|
|
818
|
+
const detail = logDetail(entry);
|
|
819
|
+
if (!detail)
|
|
820
|
+
continue;
|
|
821
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function createOperatorSurface(options = {}) {
|
|
827
|
+
const input = options.input ?? process.stdin;
|
|
828
|
+
const output = options.output ?? process.stdout;
|
|
829
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
830
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
831
|
+
const writeLine = (line) => output.write(`${line}
|
|
832
|
+
`);
|
|
833
|
+
return {
|
|
834
|
+
mode: "pi-compatible-text",
|
|
835
|
+
...renderer,
|
|
836
|
+
info: writeLine,
|
|
837
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
838
|
+
`),
|
|
839
|
+
attachCommandInput(handler) {
|
|
840
|
+
if (options.interactive === false || !input.isTTY)
|
|
841
|
+
return null;
|
|
842
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
843
|
+
rl.on("line", (line) => {
|
|
844
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
845
|
+
});
|
|
846
|
+
return { close: () => rl.close() };
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
710
850
|
function taskId(task) {
|
|
711
851
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
712
852
|
}
|
|
@@ -719,6 +859,19 @@ function taskStatus(task) {
|
|
|
719
859
|
function renderTaskPickerRows(tasks) {
|
|
720
860
|
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
721
861
|
}
|
|
862
|
+
async function promptForTaskSelection(question) {
|
|
863
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
864
|
+
try {
|
|
865
|
+
return await rl.question(question);
|
|
866
|
+
} finally {
|
|
867
|
+
rl.close();
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// packages/cli/src/commands/_task-picker.ts
|
|
872
|
+
function taskId2(task) {
|
|
873
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
874
|
+
}
|
|
722
875
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
723
876
|
if (tasks.length === 0)
|
|
724
877
|
return null;
|
|
@@ -728,17 +881,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
728
881
|
if (!isTty) {
|
|
729
882
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
730
883
|
}
|
|
731
|
-
const prompt = io.prompt ??
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
} finally {
|
|
736
|
-
rl.close();
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
console.log("Select Rig task:");
|
|
884
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
885
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
886
|
+
`) };
|
|
887
|
+
renderer.writeLine("Select Rig task:");
|
|
740
888
|
for (const row of renderTaskPickerRows(tasks))
|
|
741
|
-
|
|
889
|
+
renderer.writeLine(` ${row}`);
|
|
742
890
|
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
743
891
|
if (!answer)
|
|
744
892
|
return null;
|
|
@@ -746,38 +894,11 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
746
894
|
const index = Number.parseInt(answer, 10) - 1;
|
|
747
895
|
return tasks[index] ?? null;
|
|
748
896
|
}
|
|
749
|
-
return tasks.find((task) =>
|
|
897
|
+
return tasks.find((task) => taskId2(task) === answer) ?? null;
|
|
750
898
|
}
|
|
751
899
|
|
|
752
900
|
// packages/cli/src/commands/_operator-view.ts
|
|
753
|
-
import { createInterface as createInterface2 } from "readline";
|
|
754
901
|
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
755
|
-
var CANONICAL_STAGES = [
|
|
756
|
-
"Connect",
|
|
757
|
-
"GitHub/task sync",
|
|
758
|
-
"Prepare workspace",
|
|
759
|
-
"Launch Pi",
|
|
760
|
-
"Plan",
|
|
761
|
-
"Implement",
|
|
762
|
-
"Validate",
|
|
763
|
-
"Commit",
|
|
764
|
-
"Open PR",
|
|
765
|
-
"Review/CI",
|
|
766
|
-
"Merge",
|
|
767
|
-
"Complete"
|
|
768
|
-
];
|
|
769
|
-
function renderOperatorSnapshot(snapshot) {
|
|
770
|
-
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
771
|
-
const runId = String(run.runId ?? run.id ?? "run");
|
|
772
|
-
const status = String(run.status ?? "unknown");
|
|
773
|
-
const logs = snapshot.logs ?? [];
|
|
774
|
-
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
775
|
-
const match = logs.find((log) => String(log.title ?? "").toLowerCase() === stage.toLowerCase() || String(log.stage ?? "").toLowerCase() === stage.toLowerCase());
|
|
776
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
777
|
-
});
|
|
778
|
-
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
779
|
-
`);
|
|
780
|
-
}
|
|
781
902
|
function runStatusFromPayload(payload) {
|
|
782
903
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
783
904
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -799,11 +920,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
799
920
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
800
921
|
return { action: "continue", message: "Steering message queued." };
|
|
801
922
|
}
|
|
802
|
-
async function readOperatorSnapshot(context, runId) {
|
|
923
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
803
924
|
const run = await getRunDetailsViaServer(context, runId);
|
|
804
925
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
805
|
-
const
|
|
806
|
-
|
|
926
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
927
|
+
entries: [{
|
|
928
|
+
id: `timeline-unavailable:${runId}`,
|
|
929
|
+
type: "timeline_warning",
|
|
930
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
931
|
+
createdAt: new Date().toISOString()
|
|
932
|
+
}],
|
|
933
|
+
nextCursor: options.timelineCursor ?? null
|
|
934
|
+
}));
|
|
935
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
936
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
937
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
938
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
807
939
|
}
|
|
808
940
|
async function attachRunOperatorView(context, input) {
|
|
809
941
|
let steered = false;
|
|
@@ -811,40 +943,41 @@ async function attachRunOperatorView(context, input) {
|
|
|
811
943
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
812
944
|
steered = true;
|
|
813
945
|
}
|
|
946
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
814
947
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
815
948
|
if (context.outputMode === "text") {
|
|
816
|
-
|
|
949
|
+
surface.renderSnapshot(snapshot);
|
|
950
|
+
surface.renderTimeline(snapshot.timeline);
|
|
951
|
+
surface.renderLogs(snapshot.logs);
|
|
817
952
|
if (steered)
|
|
818
|
-
|
|
953
|
+
surface.info("Steering message queued.");
|
|
819
954
|
}
|
|
820
955
|
let detached = false;
|
|
821
|
-
let
|
|
956
|
+
let commandInput = null;
|
|
822
957
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
823
958
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
959
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
960
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
961
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
962
|
+
if (result.message)
|
|
963
|
+
surface.info(result.message);
|
|
964
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
965
|
+
detached = true;
|
|
966
|
+
commandInput?.close();
|
|
967
|
+
}
|
|
835
968
|
});
|
|
836
969
|
}
|
|
837
|
-
let lastRendered = snapshot.rendered;
|
|
838
970
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
971
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
839
972
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
840
973
|
await Bun.sleep(pollMs);
|
|
841
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
974
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
975
|
+
timelineCursor = snapshot.timelineCursor;
|
|
976
|
+
surface.renderSnapshot(snapshot);
|
|
977
|
+
surface.renderTimeline(snapshot.timeline);
|
|
978
|
+
surface.renderLogs(snapshot.logs);
|
|
846
979
|
}
|
|
847
|
-
|
|
980
|
+
commandInput?.close();
|
|
848
981
|
}
|
|
849
982
|
return { ...snapshot, steered, detached };
|
|
850
983
|
}
|
|
@@ -924,7 +1057,7 @@ function normalizePrMode(value) {
|
|
|
924
1057
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
925
1058
|
}
|
|
926
1059
|
function detectLocalDirtyState(projectRoot) {
|
|
927
|
-
const result =
|
|
1060
|
+
const result = spawnSync("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
928
1061
|
if (result.status !== 0)
|
|
929
1062
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
930
1063
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -958,7 +1091,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
958
1091
|
if (explicit)
|
|
959
1092
|
return { mode: explicit, state };
|
|
960
1093
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
961
|
-
const rl =
|
|
1094
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
962
1095
|
try {
|
|
963
1096
|
const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
|
|
964
1097
|
return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
|
|
@@ -1043,12 +1176,12 @@ async function executeTask(context, args, options) {
|
|
|
1043
1176
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
1044
1177
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
1045
1178
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
1046
|
-
const
|
|
1047
|
-
if (!
|
|
1179
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
1180
|
+
if (!taskId3)
|
|
1048
1181
|
throw new CliError2("task show requires a task id.", 2);
|
|
1049
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
1182
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
1050
1183
|
if (!task)
|
|
1051
|
-
throw new CliError2(`Task not found: ${
|
|
1184
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
1052
1185
|
const summary = summarizeTask(task, { raw: true });
|
|
1053
1186
|
if (context.outputMode === "text")
|
|
1054
1187
|
console.log(JSON.stringify(summary, null, 2));
|