@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.20
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 +1288 -315
- package/dist/src/commands/_authority-runs.js +1 -0
- package/dist/src/commands/_cli-format.js +106 -0
- package/dist/src/commands/_doctor-checks.js +10 -22
- package/dist/src/commands/_operator-surface.js +204 -0
- package/dist/src/commands/_operator-view.js +207 -51
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-session.js +253 -0
- package/dist/src/commands/_preflight.js +33 -28
- package/dist/src/commands/_server-client.js +80 -27
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +2 -0
- package/dist/src/commands/doctor.js +10 -22
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +295 -66
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +456 -95
- package/dist/src/commands/server.js +9 -22
- package/dist/src/commands/setup.js +10 -22
- package/dist/src/commands/task-run-driver.js +539 -64
- package/dist/src/commands/task.js +502 -130
- package/dist/src/commands.js +1276 -306
- package/dist/src/index.js +1288 -315
- package/dist/src/launcher.js +5 -3
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -1,9 +1,9 @@
|
|
|
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 createInterface3 } from "readline/promises";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
6
5
|
import { resolve as resolve4 } from "path";
|
|
6
|
+
import { cancel as cancel2, confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// packages/cli/src/runner.ts
|
|
9
9
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -13,6 +13,9 @@ import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
|
13
13
|
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
14
14
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
15
|
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
|
+
}
|
|
16
19
|
function takeFlag(args, flag) {
|
|
17
20
|
const rest = [];
|
|
18
21
|
let value = false;
|
|
@@ -188,11 +191,10 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
// packages/cli/src/commands/_server-client.ts
|
|
191
|
-
import { spawnSync } from "child_process";
|
|
192
194
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
193
195
|
import { resolve as resolve2 } from "path";
|
|
194
196
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
195
|
-
var
|
|
197
|
+
var scopedGitHubBearerTokens = new Map;
|
|
196
198
|
function cleanToken(value) {
|
|
197
199
|
const trimmed = value?.trim();
|
|
198
200
|
return trimmed ? trimmed : null;
|
|
@@ -209,25 +211,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
const scopedKey = resolve2(projectRoot);
|
|
215
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
216
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
214
217
|
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;
|
|
218
|
+
if (privateSession)
|
|
219
|
+
return privateSession;
|
|
220
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
231
221
|
}
|
|
232
222
|
async function ensureServerForCli(projectRoot) {
|
|
233
223
|
try {
|
|
@@ -347,6 +337,15 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
347
337
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
348
338
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
349
339
|
}
|
|
340
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
341
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
342
|
+
if (options.limit !== undefined)
|
|
343
|
+
url.searchParams.set("limit", String(options.limit));
|
|
344
|
+
if (options.cursor)
|
|
345
|
+
url.searchParams.set("cursor", options.cursor);
|
|
346
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
347
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
348
|
+
}
|
|
350
349
|
async function stopRunViaServer(context, runId) {
|
|
351
350
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
352
351
|
method: "POST",
|
|
@@ -399,7 +398,8 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
399
398
|
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
400
399
|
import { homedir as homedir2 } from "os";
|
|
401
400
|
import { resolve as resolve3 } from "path";
|
|
402
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
401
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
402
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
403
403
|
async function defaultCommandRunner(command, options = {}) {
|
|
404
404
|
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
405
405
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -412,13 +412,19 @@ async function defaultCommandRunner(command, options = {}) {
|
|
|
412
412
|
function resolvePiRigExtensionPath(homeDir) {
|
|
413
413
|
return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
|
|
414
414
|
}
|
|
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
|
+
}
|
|
415
421
|
function resolvePiHomeDir(inputHomeDir) {
|
|
416
422
|
return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
|
|
417
423
|
}
|
|
418
424
|
function piListContainsPiRig(output) {
|
|
419
425
|
return output.split(/\r?\n/).some((line) => {
|
|
420
426
|
const normalized = line.trim();
|
|
421
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
427
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
422
428
|
});
|
|
423
429
|
}
|
|
424
430
|
async function safeRun(runner, command, options) {
|
|
@@ -522,6 +528,9 @@ function permissionAllowsPr(payload) {
|
|
|
522
528
|
}
|
|
523
529
|
return null;
|
|
524
530
|
}
|
|
531
|
+
function isNotFoundError(error) {
|
|
532
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
533
|
+
}
|
|
525
534
|
function projectCheckoutReady(payload) {
|
|
526
535
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
527
536
|
return null;
|
|
@@ -554,19 +563,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
554
563
|
const checks = [];
|
|
555
564
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
556
565
|
const taskId = options.taskId?.trim() || null;
|
|
566
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
567
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
568
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
569
|
+
let legacyServerCompatibility = false;
|
|
557
570
|
try {
|
|
558
571
|
await request("/api/server/status");
|
|
559
572
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
560
573
|
} catch (error) {
|
|
561
|
-
|
|
574
|
+
if (isNotFoundError(error)) {
|
|
575
|
+
try {
|
|
576
|
+
await request("/health");
|
|
577
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
578
|
+
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"));
|
|
579
|
+
} catch (healthError) {
|
|
580
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
584
|
+
}
|
|
562
585
|
}
|
|
563
586
|
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>`."));
|
|
587
|
+
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
588
|
try {
|
|
566
589
|
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>`."));
|
|
590
|
+
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
591
|
} catch (error) {
|
|
569
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
592
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
570
593
|
}
|
|
571
594
|
try {
|
|
572
595
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -594,9 +617,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
594
617
|
try {
|
|
595
618
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
596
619
|
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."));
|
|
620
|
+
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
621
|
} catch (error) {
|
|
599
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
622
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
600
623
|
}
|
|
601
624
|
try {
|
|
602
625
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -706,7 +729,184 @@ function withMutedConsole(mute, fn) {
|
|
|
706
729
|
}
|
|
707
730
|
|
|
708
731
|
// packages/cli/src/commands/_task-picker.ts
|
|
709
|
-
import {
|
|
732
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
733
|
+
|
|
734
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
735
|
+
import { createInterface } from "readline";
|
|
736
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
737
|
+
var CANONICAL_STAGES = [
|
|
738
|
+
"Connect",
|
|
739
|
+
"GitHub/task sync",
|
|
740
|
+
"Prepare workspace",
|
|
741
|
+
"Launch Pi",
|
|
742
|
+
"Plan",
|
|
743
|
+
"Implement",
|
|
744
|
+
"Validate",
|
|
745
|
+
"Commit",
|
|
746
|
+
"Open PR",
|
|
747
|
+
"Review/CI",
|
|
748
|
+
"Merge",
|
|
749
|
+
"Complete"
|
|
750
|
+
];
|
|
751
|
+
function logDetail(log) {
|
|
752
|
+
return typeof log.detail === "string" ? log.detail.trim() : "";
|
|
753
|
+
}
|
|
754
|
+
function parseProviderProtocolLog(title, detail) {
|
|
755
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
756
|
+
return null;
|
|
757
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
758
|
+
return null;
|
|
759
|
+
try {
|
|
760
|
+
const record = JSON.parse(detail);
|
|
761
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
762
|
+
return null;
|
|
763
|
+
const type = record.type;
|
|
764
|
+
return typeof type === "string" && [
|
|
765
|
+
"assistant",
|
|
766
|
+
"message_start",
|
|
767
|
+
"message_update",
|
|
768
|
+
"message_end",
|
|
769
|
+
"stream_event",
|
|
770
|
+
"tool_result",
|
|
771
|
+
"tool_execution_start",
|
|
772
|
+
"tool_execution_update",
|
|
773
|
+
"tool_execution_end",
|
|
774
|
+
"turn_start",
|
|
775
|
+
"turn_end"
|
|
776
|
+
].includes(type) ? record : null;
|
|
777
|
+
} catch {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function renderProviderProtocolLog(record) {
|
|
782
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
783
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
784
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
785
|
+
const status = type === "tool_execution_start" ? "started" : type === "tool_execution_end" ? record.isError === true || record.result && typeof record.result === "object" && !Array.isArray(record.result) && record.result.isError === true ? "failed" : "completed" : "running";
|
|
786
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
function entryId(entry, fallback) {
|
|
791
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
792
|
+
}
|
|
793
|
+
function renderOperatorSnapshot(snapshot) {
|
|
794
|
+
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
795
|
+
const runId = String(run.runId ?? run.id ?? "run");
|
|
796
|
+
const status = String(run.status ?? "unknown");
|
|
797
|
+
const logs = snapshot.logs ?? [];
|
|
798
|
+
const latestByStage = new Map;
|
|
799
|
+
for (const log of logs) {
|
|
800
|
+
const title = String(log.title ?? "").toLowerCase();
|
|
801
|
+
const stageName = String(log.stage ?? "").toLowerCase();
|
|
802
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
803
|
+
if (stage)
|
|
804
|
+
latestByStage.set(stage, log);
|
|
805
|
+
}
|
|
806
|
+
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
807
|
+
const match = latestByStage.get(stage);
|
|
808
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
809
|
+
});
|
|
810
|
+
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
811
|
+
`);
|
|
812
|
+
}
|
|
813
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
814
|
+
let lastSnapshot = "";
|
|
815
|
+
const assistantTextById = new Map;
|
|
816
|
+
const seenTimeline = new Set;
|
|
817
|
+
const seenLogs = new Set;
|
|
818
|
+
const writeLine = (line) => output.write(`${line}
|
|
819
|
+
`);
|
|
820
|
+
return {
|
|
821
|
+
renderSnapshot(snapshot) {
|
|
822
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
823
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
824
|
+
writeLine(rendered);
|
|
825
|
+
lastSnapshot = rendered;
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
renderTimeline(entries) {
|
|
829
|
+
for (const [index, entry] of entries.entries()) {
|
|
830
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
831
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
832
|
+
const text = entry.text;
|
|
833
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
834
|
+
if (!previousText && text.trim()) {
|
|
835
|
+
writeLine("[Pi assistant]");
|
|
836
|
+
}
|
|
837
|
+
if (text.startsWith(previousText)) {
|
|
838
|
+
const delta = text.slice(previousText.length);
|
|
839
|
+
if (delta)
|
|
840
|
+
output.write(delta);
|
|
841
|
+
} else if (text.trim() && text !== previousText) {
|
|
842
|
+
if (previousText)
|
|
843
|
+
writeLine(`
|
|
844
|
+
[Pi assistant]`);
|
|
845
|
+
output.write(text);
|
|
846
|
+
}
|
|
847
|
+
assistantTextById.set(id, text);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
if (seenTimeline.has(id))
|
|
851
|
+
continue;
|
|
852
|
+
seenTimeline.add(id);
|
|
853
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
854
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (entry.type === "timeline_warning") {
|
|
858
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
renderLogs(entries) {
|
|
863
|
+
for (const [index, entry] of entries.entries()) {
|
|
864
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
865
|
+
if (seenLogs.has(id))
|
|
866
|
+
continue;
|
|
867
|
+
seenLogs.add(id);
|
|
868
|
+
const title = String(entry.title ?? "");
|
|
869
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
870
|
+
continue;
|
|
871
|
+
const detail = logDetail(entry);
|
|
872
|
+
if (!detail)
|
|
873
|
+
continue;
|
|
874
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
875
|
+
if (protocolRecord) {
|
|
876
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
877
|
+
if (protocolLine)
|
|
878
|
+
writeLine(protocolLine);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function createOperatorSurface(options = {}) {
|
|
887
|
+
const input = options.input ?? process.stdin;
|
|
888
|
+
const output = options.output ?? process.stdout;
|
|
889
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
890
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
891
|
+
const writeLine = (line) => output.write(`${line}
|
|
892
|
+
`);
|
|
893
|
+
return {
|
|
894
|
+
mode: "pi-compatible-text",
|
|
895
|
+
...renderer,
|
|
896
|
+
info: writeLine,
|
|
897
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
898
|
+
`),
|
|
899
|
+
attachCommandInput(handler) {
|
|
900
|
+
if (options.interactive === false || !input.isTTY)
|
|
901
|
+
return null;
|
|
902
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
903
|
+
rl.on("line", (line) => {
|
|
904
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
905
|
+
});
|
|
906
|
+
return { close: () => rl.close() };
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
710
910
|
function taskId(task) {
|
|
711
911
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
712
912
|
}
|
|
@@ -719,6 +919,19 @@ function taskStatus(task) {
|
|
|
719
919
|
function renderTaskPickerRows(tasks) {
|
|
720
920
|
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
721
921
|
}
|
|
922
|
+
async function promptForTaskSelection(question) {
|
|
923
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
924
|
+
try {
|
|
925
|
+
return await rl.question(question);
|
|
926
|
+
} finally {
|
|
927
|
+
rl.close();
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// packages/cli/src/commands/_task-picker.ts
|
|
932
|
+
function taskId2(task) {
|
|
933
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
934
|
+
}
|
|
722
935
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
723
936
|
if (tasks.length === 0)
|
|
724
937
|
return null;
|
|
@@ -728,56 +941,41 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
728
941
|
if (!isTty) {
|
|
729
942
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
730
943
|
}
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
944
|
+
if (io.prompt || io.renderer) {
|
|
945
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
946
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
947
|
+
`) };
|
|
948
|
+
renderer.writeLine("Select Rig task:");
|
|
949
|
+
for (const row of renderTaskPickerRows(tasks))
|
|
950
|
+
renderer.writeLine(` ${row}`);
|
|
951
|
+
const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
952
|
+
if (!answer2)
|
|
953
|
+
return null;
|
|
954
|
+
if (/^\d+$/.test(answer2)) {
|
|
955
|
+
const index2 = Number.parseInt(answer2, 10) - 1;
|
|
956
|
+
return tasks[index2] ?? null;
|
|
737
957
|
}
|
|
958
|
+
return tasks.find((task) => taskId2(task) === answer2) ?? null;
|
|
959
|
+
}
|
|
960
|
+
const options = tasks.map((task, index2) => ({
|
|
961
|
+
value: `${index2}`,
|
|
962
|
+
label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
|
|
963
|
+
hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
|
|
964
|
+
}));
|
|
965
|
+
const answer = await select({
|
|
966
|
+
message: "Select Rig task",
|
|
967
|
+
options
|
|
738
968
|
});
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
console.log(` ${row}`);
|
|
742
|
-
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
743
|
-
if (!answer)
|
|
969
|
+
if (isCancel(answer)) {
|
|
970
|
+
cancel("No task selected.");
|
|
744
971
|
return null;
|
|
745
|
-
if (/^\d+$/.test(answer)) {
|
|
746
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
747
|
-
return tasks[index] ?? null;
|
|
748
972
|
}
|
|
749
|
-
|
|
973
|
+
const index = Number.parseInt(String(answer), 10);
|
|
974
|
+
return Number.isFinite(index) ? tasks[index] ?? null : null;
|
|
750
975
|
}
|
|
751
976
|
|
|
752
977
|
// packages/cli/src/commands/_operator-view.ts
|
|
753
|
-
import { createInterface as createInterface2 } from "readline";
|
|
754
978
|
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
979
|
function runStatusFromPayload(payload) {
|
|
782
980
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
783
981
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -799,11 +997,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
799
997
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
800
998
|
return { action: "continue", message: "Steering message queued." };
|
|
801
999
|
}
|
|
802
|
-
async function readOperatorSnapshot(context, runId) {
|
|
1000
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
803
1001
|
const run = await getRunDetailsViaServer(context, runId);
|
|
804
1002
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
805
|
-
const
|
|
806
|
-
|
|
1003
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
1004
|
+
entries: [{
|
|
1005
|
+
id: `timeline-unavailable:${runId}`,
|
|
1006
|
+
type: "timeline_warning",
|
|
1007
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
1008
|
+
createdAt: new Date().toISOString()
|
|
1009
|
+
}],
|
|
1010
|
+
nextCursor: options.timelineCursor ?? null
|
|
1011
|
+
}));
|
|
1012
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
1013
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1014
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
1015
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
807
1016
|
}
|
|
808
1017
|
async function attachRunOperatorView(context, input) {
|
|
809
1018
|
let steered = false;
|
|
@@ -811,44 +1020,209 @@ async function attachRunOperatorView(context, input) {
|
|
|
811
1020
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
812
1021
|
steered = true;
|
|
813
1022
|
}
|
|
1023
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
814
1024
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
815
1025
|
if (context.outputMode === "text") {
|
|
816
|
-
|
|
1026
|
+
surface.renderSnapshot(snapshot);
|
|
1027
|
+
surface.renderTimeline(snapshot.timeline);
|
|
1028
|
+
surface.renderLogs(snapshot.logs);
|
|
817
1029
|
if (steered)
|
|
818
|
-
|
|
1030
|
+
surface.info("Steering message queued.");
|
|
819
1031
|
}
|
|
820
1032
|
let detached = false;
|
|
821
|
-
let
|
|
1033
|
+
let commandInput = null;
|
|
822
1034
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
823
1035
|
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)}`));
|
|
1036
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
1037
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
1038
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
1039
|
+
if (result.message)
|
|
1040
|
+
surface.info(result.message);
|
|
1041
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
1042
|
+
detached = true;
|
|
1043
|
+
commandInput?.close();
|
|
1044
|
+
}
|
|
835
1045
|
});
|
|
836
1046
|
}
|
|
837
|
-
let lastRendered = snapshot.rendered;
|
|
838
1047
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
1048
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
839
1049
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
840
1050
|
await Bun.sleep(pollMs);
|
|
841
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1051
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1052
|
+
timelineCursor = snapshot.timelineCursor;
|
|
1053
|
+
surface.renderSnapshot(snapshot);
|
|
1054
|
+
surface.renderTimeline(snapshot.timeline);
|
|
1055
|
+
surface.renderLogs(snapshot.logs);
|
|
846
1056
|
}
|
|
847
|
-
|
|
1057
|
+
commandInput?.close();
|
|
848
1058
|
}
|
|
849
1059
|
return { ...snapshot, steered, detached };
|
|
850
1060
|
}
|
|
851
1061
|
|
|
1062
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
1063
|
+
import pc from "picocolors";
|
|
1064
|
+
function stringField(record, key, fallback = "") {
|
|
1065
|
+
const value = record[key];
|
|
1066
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
1067
|
+
}
|
|
1068
|
+
function arrayField(record, key) {
|
|
1069
|
+
const value = record[key];
|
|
1070
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
|
|
1071
|
+
}
|
|
1072
|
+
function rawObject(record) {
|
|
1073
|
+
const raw = record.raw;
|
|
1074
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
1075
|
+
}
|
|
1076
|
+
function truncate(value, width) {
|
|
1077
|
+
if (value.length <= width)
|
|
1078
|
+
return value;
|
|
1079
|
+
if (width <= 1)
|
|
1080
|
+
return "\u2026";
|
|
1081
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
1082
|
+
}
|
|
1083
|
+
function pad(value, width) {
|
|
1084
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
1085
|
+
}
|
|
1086
|
+
function statusColor(status) {
|
|
1087
|
+
const normalized = status.toLowerCase();
|
|
1088
|
+
if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
|
|
1089
|
+
return pc.green;
|
|
1090
|
+
if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
|
|
1091
|
+
return pc.red;
|
|
1092
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
|
|
1093
|
+
return pc.cyan;
|
|
1094
|
+
if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
|
|
1095
|
+
return pc.yellow;
|
|
1096
|
+
return pc.dim;
|
|
1097
|
+
}
|
|
1098
|
+
function formatTaskList(tasks, options = {}) {
|
|
1099
|
+
if (tasks.length === 0)
|
|
1100
|
+
return pc.dim("No matching tasks.");
|
|
1101
|
+
if (options.raw)
|
|
1102
|
+
return tasks.map((task) => JSON.stringify(task)).join(`
|
|
1103
|
+
`);
|
|
1104
|
+
const rows = tasks.map((task) => {
|
|
1105
|
+
const raw = rawObject(task);
|
|
1106
|
+
const id = stringField(task, "id", "<unknown>");
|
|
1107
|
+
const status = stringField(task, "status", "unknown");
|
|
1108
|
+
const title = stringField(task, "title", "Untitled task");
|
|
1109
|
+
const source = stringField(task, "source", stringField(raw, "source", ""));
|
|
1110
|
+
const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
|
|
1111
|
+
return { id, status, title, source, labels };
|
|
1112
|
+
});
|
|
1113
|
+
const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
|
|
1114
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
1115
|
+
const header = `${pc.bold(pad("TASK", idWidth))} ${pc.bold(pad("STATUS", statusWidth))} ${pc.bold("TITLE")}`;
|
|
1116
|
+
const body = rows.map((row) => {
|
|
1117
|
+
const labels = row.labels.length > 0 ? pc.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
|
|
1118
|
+
const source = row.source ? pc.dim(` ${row.source}`) : "";
|
|
1119
|
+
return [
|
|
1120
|
+
pc.bold(pad(truncate(row.id, idWidth), idWidth)),
|
|
1121
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
1122
|
+
`${row.title}${labels}${source}`
|
|
1123
|
+
].join(" ");
|
|
1124
|
+
});
|
|
1125
|
+
return [pc.bold("Rig tasks"), header, ...body].join(`
|
|
1126
|
+
`);
|
|
1127
|
+
}
|
|
1128
|
+
function formatSubmittedRun(input) {
|
|
1129
|
+
const lines = [`${pc.green("Run submitted")}: ${pc.bold(input.runId)}`];
|
|
1130
|
+
if (input.task) {
|
|
1131
|
+
const id = stringField(input.task, "id", "<unknown>");
|
|
1132
|
+
const status = stringField(input.task, "status", "unknown");
|
|
1133
|
+
const title = stringField(input.task, "title", "Untitled task");
|
|
1134
|
+
lines.push(`${pc.dim("task")} ${pc.bold(id)} ${statusColor(status)(status)} ${title}`);
|
|
1135
|
+
}
|
|
1136
|
+
return lines.join(`
|
|
1137
|
+
`);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
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
|
+
|
|
852
1226
|
// packages/cli/src/commands/task.ts
|
|
853
1227
|
import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
|
|
854
1228
|
import { loadConfig } from "@rig/core/load-config";
|
|
@@ -924,7 +1298,7 @@ function normalizePrMode(value) {
|
|
|
924
1298
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
925
1299
|
}
|
|
926
1300
|
function detectLocalDirtyState(projectRoot) {
|
|
927
|
-
const result =
|
|
1301
|
+
const result = spawnSync("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
928
1302
|
if (result.status !== 0)
|
|
929
1303
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
930
1304
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -958,13 +1332,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
958
1332
|
if (explicit)
|
|
959
1333
|
return { mode: explicit, state };
|
|
960
1334
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1335
|
+
const answer = await confirm({
|
|
1336
|
+
message: "Include current uncommitted changes in run baseline?",
|
|
1337
|
+
initialValue: false
|
|
1338
|
+
});
|
|
1339
|
+
if (isCancel2(answer)) {
|
|
1340
|
+
cancel2("Run cancelled.");
|
|
1341
|
+
throw new CliError2("Run cancelled by user.", 1);
|
|
967
1342
|
}
|
|
1343
|
+
return { mode: answer ? "dirty-snapshot" : "head", state };
|
|
968
1344
|
}
|
|
969
1345
|
return { mode: "head", state };
|
|
970
1346
|
}
|
|
@@ -999,10 +1375,7 @@ function summarizeTask(task, options = {}) {
|
|
|
999
1375
|
};
|
|
1000
1376
|
}
|
|
1001
1377
|
function printTaskSummary(task) {
|
|
1002
|
-
|
|
1003
|
-
const title = readTaskString(task, "title") ?? "Untitled task";
|
|
1004
|
-
const status = readTaskString(task, "status") ?? "unknown";
|
|
1005
|
-
console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
|
|
1378
|
+
console.log(formatTaskList([task]));
|
|
1006
1379
|
}
|
|
1007
1380
|
async function validatorRegistryForTaskCommands(projectRoot) {
|
|
1008
1381
|
return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
|
|
@@ -1020,16 +1393,8 @@ async function executeTask(context, args, options) {
|
|
|
1020
1393
|
requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
1021
1394
|
const tasks = await listWorkspaceTasksViaServer(context, filters);
|
|
1022
1395
|
if (context.outputMode === "text") {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
} else {
|
|
1026
|
-
for (const task of tasks) {
|
|
1027
|
-
if (rawResult.value)
|
|
1028
|
-
console.log(JSON.stringify(summarizeTask(task, { raw: true })));
|
|
1029
|
-
else
|
|
1030
|
-
printTaskSummary(task);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1396
|
+
const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
|
|
1397
|
+
console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
|
|
1033
1398
|
}
|
|
1034
1399
|
return {
|
|
1035
1400
|
ok: true,
|
|
@@ -1043,12 +1408,12 @@ async function executeTask(context, args, options) {
|
|
|
1043
1408
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
1044
1409
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
1045
1410
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
1046
|
-
const
|
|
1047
|
-
if (!
|
|
1411
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
1412
|
+
if (!taskId3)
|
|
1048
1413
|
throw new CliError2("task show requires a task id.", 2);
|
|
1049
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
1414
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
1050
1415
|
if (!task)
|
|
1051
|
-
throw new CliError2(`Task not found: ${
|
|
1416
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
1052
1417
|
const summary = summarizeTask(task, { raw: true });
|
|
1053
1418
|
if (context.outputMode === "text")
|
|
1054
1419
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -1248,16 +1613,23 @@ async function executeTask(context, args, options) {
|
|
|
1248
1613
|
});
|
|
1249
1614
|
let attachDetails = null;
|
|
1250
1615
|
if (!detachResult.value && context.outputMode === "text") {
|
|
1251
|
-
console.log(
|
|
1252
|
-
if (
|
|
1253
|
-
|
|
1616
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
1617
|
+
if (runtimeAdapter === "pi") {
|
|
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 });
|
|
1254
1630
|
}
|
|
1255
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
1256
1631
|
} else if (context.outputMode === "text") {
|
|
1257
|
-
console.log(
|
|
1258
|
-
if (selectedTask) {
|
|
1259
|
-
printTaskSummary(selectedTask);
|
|
1260
|
-
}
|
|
1632
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
1261
1633
|
}
|
|
1262
1634
|
return {
|
|
1263
1635
|
ok: true,
|