@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.21
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
package/dist/src/commands.js
CHANGED
|
@@ -2464,17 +2464,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2464
2464
|
}
|
|
2465
2465
|
|
|
2466
2466
|
// packages/cli/src/commands/_server-client.ts
|
|
2467
|
-
import { spawnSync } from "child_process";
|
|
2468
2467
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2469
2468
|
import { resolve as resolve8 } from "path";
|
|
2470
2469
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2471
|
-
var
|
|
2470
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2472
2471
|
function cleanToken(value) {
|
|
2473
2472
|
const trimmed = value?.trim();
|
|
2474
2473
|
return trimmed ? trimmed : null;
|
|
2475
2474
|
}
|
|
2476
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2477
|
-
|
|
2475
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2476
|
+
const scopedKey = resolve8(projectRoot ?? process.cwd());
|
|
2477
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2478
2478
|
}
|
|
2479
2479
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2480
2480
|
const path = resolve8(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2488,25 +2488,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2488
2488
|
}
|
|
2489
2489
|
}
|
|
2490
2490
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2491
|
-
|
|
2492
|
-
|
|
2491
|
+
const scopedKey = resolve8(projectRoot);
|
|
2492
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2493
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2493
2494
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2494
|
-
if (privateSession)
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
}
|
|
2498
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2499
|
-
if (envToken) {
|
|
2500
|
-
cachedGitHubBearerToken = envToken;
|
|
2501
|
-
return cachedGitHubBearerToken;
|
|
2502
|
-
}
|
|
2503
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2504
|
-
encoding: "utf8",
|
|
2505
|
-
timeout: 5000,
|
|
2506
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2507
|
-
});
|
|
2508
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2509
|
-
return cachedGitHubBearerToken;
|
|
2495
|
+
if (privateSession)
|
|
2496
|
+
return privateSession;
|
|
2497
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2510
2498
|
}
|
|
2511
2499
|
async function ensureServerForCli(projectRoot) {
|
|
2512
2500
|
try {
|
|
@@ -2644,16 +2632,36 @@ async function registerProjectViaServer(context, input) {
|
|
|
2644
2632
|
function sleep(ms) {
|
|
2645
2633
|
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
2646
2634
|
}
|
|
2635
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
2636
|
+
if (!(error instanceof Error))
|
|
2637
|
+
return false;
|
|
2638
|
+
const message = error.message.toLowerCase();
|
|
2639
|
+
return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
|
|
2640
|
+
}
|
|
2647
2641
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
2648
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2649
|
-
method: "POST",
|
|
2650
|
-
headers: { "content-type": "application/json" },
|
|
2651
|
-
body: JSON.stringify({ projectRoot })
|
|
2652
|
-
});
|
|
2653
2642
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
2654
2643
|
const pollMs = options.pollMs ?? 1000;
|
|
2655
2644
|
const deadline = Date.now() + timeoutMs;
|
|
2656
2645
|
let lastError;
|
|
2646
|
+
let switched = null;
|
|
2647
|
+
while (Date.now() < deadline) {
|
|
2648
|
+
try {
|
|
2649
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2650
|
+
method: "POST",
|
|
2651
|
+
headers: { "content-type": "application/json" },
|
|
2652
|
+
body: JSON.stringify({ projectRoot })
|
|
2653
|
+
});
|
|
2654
|
+
break;
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
lastError = error;
|
|
2657
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
2658
|
+
throw error;
|
|
2659
|
+
await sleep(pollMs);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
if (!switched) {
|
|
2663
|
+
throw new CliError2(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
|
|
2664
|
+
}
|
|
2657
2665
|
while (Date.now() < deadline) {
|
|
2658
2666
|
try {
|
|
2659
2667
|
const status = await requestServerJson(context, "/api/server/status");
|
|
@@ -2671,6 +2679,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
2671
2679
|
}
|
|
2672
2680
|
throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
|
|
2673
2681
|
}
|
|
2682
|
+
async function listRunsViaServer(context, options = {}) {
|
|
2683
|
+
const url = new URL("http://rig.local/api/runs");
|
|
2684
|
+
if (options.limit !== undefined)
|
|
2685
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2686
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2687
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
2688
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
2689
|
+
}
|
|
2674
2690
|
async function getRunDetailsViaServer(context, runId) {
|
|
2675
2691
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
|
|
2676
2692
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
@@ -2684,6 +2700,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2684
2700
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2685
2701
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2686
2702
|
}
|
|
2703
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2704
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2705
|
+
if (options.limit !== undefined)
|
|
2706
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2707
|
+
if (options.cursor)
|
|
2708
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2709
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2710
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2711
|
+
}
|
|
2712
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2713
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2714
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2715
|
+
}
|
|
2716
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2717
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2718
|
+
url.searchParams.set("owner", owner);
|
|
2719
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2720
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2721
|
+
}
|
|
2722
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2723
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2724
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2725
|
+
}
|
|
2726
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2727
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2728
|
+
method: "POST",
|
|
2729
|
+
headers: { "content-type": "application/json" },
|
|
2730
|
+
body: JSON.stringify(input)
|
|
2731
|
+
});
|
|
2732
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2733
|
+
}
|
|
2687
2734
|
async function stopRunViaServer(context, runId) {
|
|
2688
2735
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2689
2736
|
method: "POST",
|
|
@@ -2736,7 +2783,8 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
2736
2783
|
import { existsSync as existsSync5, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
|
|
2737
2784
|
import { homedir as homedir3 } from "os";
|
|
2738
2785
|
import { resolve as resolve9 } from "path";
|
|
2739
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2786
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
2787
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2740
2788
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
2741
2789
|
export { default } from '@rig/pi-rig';
|
|
2742
2790
|
`;
|
|
@@ -2764,7 +2812,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
2764
2812
|
function piListContainsPiRig(output) {
|
|
2765
2813
|
return output.split(/\r?\n/).some((line) => {
|
|
2766
2814
|
const normalized = line.trim();
|
|
2767
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
2815
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
2768
2816
|
});
|
|
2769
2817
|
}
|
|
2770
2818
|
async function safeRun(runner, command, options) {
|
|
@@ -2880,7 +2928,7 @@ async function ensureRemotePiRigInstalled(input) {
|
|
|
2880
2928
|
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
2881
2929
|
method: "POST",
|
|
2882
2930
|
headers: { "content-type": "application/json" },
|
|
2883
|
-
body: JSON.stringify({ package:
|
|
2931
|
+
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
2884
2932
|
});
|
|
2885
2933
|
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2886
2934
|
const piOk = record.piOk === true || record.ok === true;
|
|
@@ -2963,6 +3011,9 @@ function permissionAllowsPr(payload) {
|
|
|
2963
3011
|
}
|
|
2964
3012
|
return null;
|
|
2965
3013
|
}
|
|
3014
|
+
function isNotFoundError(error) {
|
|
3015
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3016
|
+
}
|
|
2966
3017
|
function projectCheckoutReady(payload) {
|
|
2967
3018
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
2968
3019
|
return null;
|
|
@@ -2995,19 +3046,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
2995
3046
|
const checks = [];
|
|
2996
3047
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
2997
3048
|
const taskId = options.taskId?.trim() || null;
|
|
3049
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3050
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3051
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3052
|
+
let legacyServerCompatibility = false;
|
|
2998
3053
|
try {
|
|
2999
3054
|
await request("/api/server/status");
|
|
3000
3055
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3001
3056
|
} catch (error) {
|
|
3002
|
-
|
|
3057
|
+
if (isNotFoundError(error)) {
|
|
3058
|
+
try {
|
|
3059
|
+
await request("/health");
|
|
3060
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3061
|
+
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"));
|
|
3062
|
+
} catch (healthError) {
|
|
3063
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3064
|
+
}
|
|
3065
|
+
} else {
|
|
3066
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3067
|
+
}
|
|
3003
3068
|
}
|
|
3004
3069
|
const repo = readRepoConnection(context.projectRoot);
|
|
3005
|
-
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>`."));
|
|
3070
|
+
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>`."));
|
|
3006
3071
|
try {
|
|
3007
3072
|
const auth = await request("/api/github/auth/status");
|
|
3008
|
-
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>`."));
|
|
3073
|
+
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>`."));
|
|
3009
3074
|
} catch (error) {
|
|
3010
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3075
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3011
3076
|
}
|
|
3012
3077
|
try {
|
|
3013
3078
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3035,9 +3100,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3035
3100
|
try {
|
|
3036
3101
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3037
3102
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3038
|
-
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."));
|
|
3103
|
+
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."));
|
|
3039
3104
|
} catch (error) {
|
|
3040
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3105
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3041
3106
|
}
|
|
3042
3107
|
try {
|
|
3043
3108
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -3240,6 +3305,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
|
|
|
3240
3305
|
const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
|
|
3241
3306
|
const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
|
|
3242
3307
|
const next = {
|
|
3308
|
+
...existing ?? {},
|
|
3243
3309
|
runId: input.runId,
|
|
3244
3310
|
projectRoot,
|
|
3245
3311
|
workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
|
|
@@ -3948,9 +4014,10 @@ async function executeInbox(context, args) {
|
|
|
3948
4014
|
|
|
3949
4015
|
// packages/cli/src/commands/init.ts
|
|
3950
4016
|
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
3951
|
-
import { spawnSync
|
|
4017
|
+
import { spawnSync } from "child_process";
|
|
3952
4018
|
import { resolve as resolve16 } from "path";
|
|
3953
4019
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4020
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
3954
4021
|
|
|
3955
4022
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
3956
4023
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4264,6 +4331,7 @@ function countDoctorFailures(checks) {
|
|
|
4264
4331
|
|
|
4265
4332
|
// packages/cli/src/commands/init.ts
|
|
4266
4333
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
4334
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
4267
4335
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
4268
4336
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
4269
4337
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -4274,7 +4342,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4274
4342
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4275
4343
|
}
|
|
4276
4344
|
function detectOriginRepoSlug(projectRoot) {
|
|
4277
|
-
const result =
|
|
4345
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4278
4346
|
if (result.status !== 0)
|
|
4279
4347
|
return null;
|
|
4280
4348
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4330,11 +4398,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4330
4398
|
return source;
|
|
4331
4399
|
const projectId = JSON.stringify(options.githubProject);
|
|
4332
4400
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4401
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4402
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4403
|
+
`)},` : "";
|
|
4333
4404
|
return source.replace(` projects: { enabled: false },`, [
|
|
4334
4405
|
` projects: {`,
|
|
4335
4406
|
` enabled: true,`,
|
|
4336
4407
|
` projectId: ${projectId},`,
|
|
4337
|
-
` statusFieldId: ${statusFieldId}
|
|
4408
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4338
4409
|
` },`
|
|
4339
4410
|
].join(`
|
|
4340
4411
|
`));
|
|
@@ -4355,21 +4426,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4355
4426
|
}
|
|
4356
4427
|
}
|
|
4357
4428
|
function detectGhLogin() {
|
|
4358
|
-
const result =
|
|
4429
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4359
4430
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4360
4431
|
}
|
|
4361
4432
|
function readGhAuthToken() {
|
|
4362
|
-
const result =
|
|
4433
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4363
4434
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4364
4435
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4365
4436
|
}
|
|
4366
4437
|
return result.stdout.trim();
|
|
4367
4438
|
}
|
|
4439
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
4440
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
4441
|
+
encoding: "utf8",
|
|
4442
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4443
|
+
});
|
|
4444
|
+
if (result.status !== 0)
|
|
4445
|
+
return null;
|
|
4446
|
+
try {
|
|
4447
|
+
return readGhAuthToken();
|
|
4448
|
+
} catch {
|
|
4449
|
+
return null;
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4368
4452
|
async function loadClackPrompts() {
|
|
4369
4453
|
return await import("@clack/prompts");
|
|
4370
4454
|
}
|
|
4455
|
+
function clackTextOptions(options) {
|
|
4456
|
+
return {
|
|
4457
|
+
message: options.message,
|
|
4458
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4459
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4371
4462
|
async function promptRequiredText(prompts, options) {
|
|
4372
|
-
const value = await prompts.text(options);
|
|
4463
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4373
4464
|
if (prompts.isCancel(value))
|
|
4374
4465
|
throw new CliError2("Init cancelled.", 1);
|
|
4375
4466
|
const text2 = String(value ?? "").trim();
|
|
@@ -4378,7 +4469,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4378
4469
|
return text2;
|
|
4379
4470
|
}
|
|
4380
4471
|
async function promptOptionalText(prompts, options) {
|
|
4381
|
-
const value = await prompts.text(options);
|
|
4472
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4382
4473
|
if (prompts.isCancel(value))
|
|
4383
4474
|
throw new CliError2("Init cancelled.", 1);
|
|
4384
4475
|
return String(value ?? "").trim();
|
|
@@ -4389,6 +4480,164 @@ async function promptSelect(prompts, options) {
|
|
|
4389
4480
|
throw new CliError2("Init cancelled.", 1);
|
|
4390
4481
|
return String(value);
|
|
4391
4482
|
}
|
|
4483
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4484
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4485
|
+
}
|
|
4486
|
+
function recordArray(value, key) {
|
|
4487
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4488
|
+
return [];
|
|
4489
|
+
const raw = value[key];
|
|
4490
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4491
|
+
}
|
|
4492
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4493
|
+
if (token?.trim()) {
|
|
4494
|
+
try {
|
|
4495
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4496
|
+
} catch (directError) {
|
|
4497
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4498
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4499
|
+
return serverPayload;
|
|
4500
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4504
|
+
}
|
|
4505
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4506
|
+
if (token?.trim()) {
|
|
4507
|
+
try {
|
|
4508
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4509
|
+
} catch (directError) {
|
|
4510
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4511
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4512
|
+
return serverPayload;
|
|
4513
|
+
}
|
|
4514
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4518
|
+
}
|
|
4519
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4520
|
+
running: "Running/In progress",
|
|
4521
|
+
prOpen: "PR open/review",
|
|
4522
|
+
ciFixing: "CI/review fixing",
|
|
4523
|
+
merging: "Merging",
|
|
4524
|
+
done: "Done",
|
|
4525
|
+
needsAttention: "Needs attention"
|
|
4526
|
+
};
|
|
4527
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4528
|
+
running: "In Progress",
|
|
4529
|
+
prOpen: "In Review",
|
|
4530
|
+
ciFixing: "In Review",
|
|
4531
|
+
merging: "Merging",
|
|
4532
|
+
done: "Done",
|
|
4533
|
+
needsAttention: "Needs Attention"
|
|
4534
|
+
};
|
|
4535
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4536
|
+
const statuses = {};
|
|
4537
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4538
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4539
|
+
const value = await promptOptionalText(prompts, {
|
|
4540
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4541
|
+
placeholder: defaultLabel
|
|
4542
|
+
});
|
|
4543
|
+
statuses[key] = value || defaultLabel;
|
|
4544
|
+
}
|
|
4545
|
+
return statuses;
|
|
4546
|
+
}
|
|
4547
|
+
function projectScopeError(value) {
|
|
4548
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
4549
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
|
|
4550
|
+
}
|
|
4551
|
+
function optionName(option) {
|
|
4552
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
4553
|
+
}
|
|
4554
|
+
function autoProjectStatusValue(options, key, label) {
|
|
4555
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
4556
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
4557
|
+
if (!match)
|
|
4558
|
+
return null;
|
|
4559
|
+
return String(match.id ?? match.name);
|
|
4560
|
+
}
|
|
4561
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
4562
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4563
|
+
message: "GitHub Projects status sync",
|
|
4564
|
+
initialValue: "select",
|
|
4565
|
+
options: [
|
|
4566
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4567
|
+
{ value: "off", label: "Off" },
|
|
4568
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4569
|
+
]
|
|
4570
|
+
});
|
|
4571
|
+
if (projectChoice === "off")
|
|
4572
|
+
return { githubProject: "off" };
|
|
4573
|
+
if (projectChoice === "manual") {
|
|
4574
|
+
return {
|
|
4575
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4576
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4577
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4581
|
+
if (!owner)
|
|
4582
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4583
|
+
let activeToken = githubToken?.trim() || null;
|
|
4584
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4585
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
4586
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
4587
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
4588
|
+
const refreshedToken = refreshProjectToken();
|
|
4589
|
+
if (refreshedToken) {
|
|
4590
|
+
activeToken = refreshedToken;
|
|
4591
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4592
|
+
projects = recordArray(projectsPayload, "projects");
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
if (projects.length === 0) {
|
|
4596
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
4597
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
4598
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
4599
|
+
}
|
|
4600
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4601
|
+
message: "GitHub ProjectV2 project",
|
|
4602
|
+
options: [
|
|
4603
|
+
...projects.map((project) => ({
|
|
4604
|
+
value: String(project.id),
|
|
4605
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4606
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4607
|
+
})),
|
|
4608
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4609
|
+
]
|
|
4610
|
+
});
|
|
4611
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4612
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4613
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4614
|
+
const rawField = fieldPayloadRecord.field;
|
|
4615
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4616
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4617
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4618
|
+
if (options.length === 0) {
|
|
4619
|
+
return {
|
|
4620
|
+
githubProject: projectId,
|
|
4621
|
+
githubProjectStatusField: fieldId,
|
|
4622
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
4623
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4624
|
+
};
|
|
4625
|
+
}
|
|
4626
|
+
const statuses = {};
|
|
4627
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4628
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
4629
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
4630
|
+
message: `Project status option for ${label}`,
|
|
4631
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
return {
|
|
4635
|
+
githubProject: projectId,
|
|
4636
|
+
githubProjectStatusField: fieldId,
|
|
4637
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
4638
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4639
|
+
};
|
|
4640
|
+
}
|
|
4392
4641
|
function sleep2(ms) {
|
|
4393
4642
|
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
4394
4643
|
}
|
|
@@ -4402,12 +4651,29 @@ function apiSessionTokenFrom(payload) {
|
|
|
4402
4651
|
const token = payload.apiSessionToken;
|
|
4403
4652
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
4404
4653
|
}
|
|
4654
|
+
function cleanPayloadString(value) {
|
|
4655
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4656
|
+
}
|
|
4657
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
4658
|
+
if (!payload)
|
|
4659
|
+
return {};
|
|
4660
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
4661
|
+
return {
|
|
4662
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
4663
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
4664
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
4665
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
4666
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
4667
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4405
4670
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
4406
4671
|
writeFileSync5(resolve16(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
4407
4672
|
authenticated: true,
|
|
4408
4673
|
source: input.source,
|
|
4409
4674
|
storedOnServer: true,
|
|
4410
4675
|
selectedRepo: input.selectedRepo,
|
|
4676
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
4411
4677
|
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
4412
4678
|
updatedAt: new Date().toISOString()
|
|
4413
4679
|
}, null, 2)}
|
|
@@ -4500,12 +4766,13 @@ async function runControlPlaneInit(context, options) {
|
|
|
4500
4766
|
if (token) {
|
|
4501
4767
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4502
4768
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4503
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4769
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4504
4770
|
if (serverKind === "remote") {
|
|
4505
4771
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4506
4772
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
4507
4773
|
selectedRepo: repo.slug,
|
|
4508
|
-
apiSessionToken
|
|
4774
|
+
apiSessionToken,
|
|
4775
|
+
authPayload: githubAuth
|
|
4509
4776
|
});
|
|
4510
4777
|
}
|
|
4511
4778
|
} else if (authMethod === "device") {
|
|
@@ -4524,9 +4791,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
4524
4791
|
if (completed) {
|
|
4525
4792
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4526
4793
|
if (apiSessionToken) {
|
|
4527
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
4794
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4528
4795
|
if (serverKind === "remote") {
|
|
4529
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
|
|
4796
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4530
4797
|
}
|
|
4531
4798
|
}
|
|
4532
4799
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
@@ -4544,19 +4811,25 @@ async function runControlPlaneInit(context, options) {
|
|
|
4544
4811
|
Object.assign(checkout, preparedCheckout);
|
|
4545
4812
|
}
|
|
4546
4813
|
}
|
|
4814
|
+
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
4815
|
+
if (serverKind === "remote" && checkoutPath && token) {
|
|
4816
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
4817
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4818
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4819
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
4820
|
+
}
|
|
4547
4821
|
const registered = await registerProjectViaServer(context, {
|
|
4548
4822
|
repoSlug: repo.slug,
|
|
4549
4823
|
checkout
|
|
4550
4824
|
});
|
|
4551
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
4552
4825
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4553
|
-
if (serverRootSwitch && token) {
|
|
4554
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
|
|
4555
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4556
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4557
|
-
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
|
|
4558
|
-
}
|
|
4559
4826
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
4827
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
4828
|
+
ok: false,
|
|
4829
|
+
ready: false,
|
|
4830
|
+
labelsReady: false,
|
|
4831
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4832
|
+
}));
|
|
4560
4833
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4561
4834
|
remote: true,
|
|
4562
4835
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4587,6 +4860,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4587
4860
|
githubAuth,
|
|
4588
4861
|
deviceAuth,
|
|
4589
4862
|
githubAuthWarning: remoteGhTokenWarning,
|
|
4863
|
+
labelSetup,
|
|
4590
4864
|
pi,
|
|
4591
4865
|
doctor
|
|
4592
4866
|
};
|
|
@@ -4693,12 +4967,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4693
4967
|
});
|
|
4694
4968
|
const serverChoice = await promptSelect(prompts, {
|
|
4695
4969
|
message: "Rig server",
|
|
4970
|
+
initialValue: "remote",
|
|
4696
4971
|
options: [
|
|
4697
|
-
{ value: "
|
|
4698
|
-
{ value: "
|
|
4972
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
4973
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
4699
4974
|
]
|
|
4700
4975
|
});
|
|
4701
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
4976
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
4702
4977
|
let remoteCheckout;
|
|
4703
4978
|
if (serverChoice === "remote") {
|
|
4704
4979
|
const checkout = await promptSelect(prompts, {
|
|
@@ -4730,38 +5005,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4730
5005
|
{ value: "skip", label: "Skip for now" }
|
|
4731
5006
|
]
|
|
4732
5007
|
});
|
|
5008
|
+
let remoteGhTokenConfirmed = false;
|
|
4733
5009
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
4734
5010
|
if (!prompts.confirm)
|
|
4735
5011
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
4736
5012
|
const confirmed = await prompts.confirm({
|
|
4737
5013
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
4738
|
-
initialValue:
|
|
5014
|
+
initialValue: true
|
|
4739
5015
|
});
|
|
4740
5016
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
4741
5017
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4742
5018
|
}
|
|
5019
|
+
remoteGhTokenConfirmed = true;
|
|
4743
5020
|
}
|
|
4744
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4745
|
-
const
|
|
4746
|
-
|
|
4747
|
-
options: [
|
|
4748
|
-
{ value: "off", label: "Off" },
|
|
4749
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4750
|
-
]
|
|
4751
|
-
});
|
|
4752
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4753
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
5021
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
5022
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
5023
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
4754
5024
|
const result = await runControlPlaneInit(context, {
|
|
4755
5025
|
server: serverChoice,
|
|
4756
5026
|
remoteUrl,
|
|
4757
5027
|
repoSlug,
|
|
4758
|
-
githubToken,
|
|
5028
|
+
githubToken: effectiveGithubToken,
|
|
4759
5029
|
githubAuthMethod: authMethod,
|
|
4760
|
-
githubProject,
|
|
4761
|
-
githubProjectStatusField,
|
|
5030
|
+
githubProject: projectConfig.githubProject,
|
|
5031
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
5032
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
4762
5033
|
remoteCheckout,
|
|
4763
5034
|
repair,
|
|
4764
|
-
privateStateOnly
|
|
5035
|
+
privateStateOnly,
|
|
5036
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
4765
5037
|
});
|
|
4766
5038
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
4767
5039
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
@@ -4857,7 +5129,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
4857
5129
|
}
|
|
4858
5130
|
|
|
4859
5131
|
// packages/cli/src/commands/github.ts
|
|
4860
|
-
import { spawnSync as
|
|
5132
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
4861
5133
|
function printPayload(context, payload, fallback) {
|
|
4862
5134
|
if (context.outputMode === "json")
|
|
4863
5135
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -4865,7 +5137,7 @@ function printPayload(context, payload, fallback) {
|
|
|
4865
5137
|
console.log(fallback);
|
|
4866
5138
|
}
|
|
4867
5139
|
function readGhToken() {
|
|
4868
|
-
const result =
|
|
5140
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4869
5141
|
if (result.status !== 0) {
|
|
4870
5142
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
4871
5143
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -5897,14 +6169,10 @@ async function executeRemote(context, args) {
|
|
|
5897
6169
|
}
|
|
5898
6170
|
|
|
5899
6171
|
// packages/cli/src/commands/run.ts
|
|
5900
|
-
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
5901
|
-
import { resolve as resolve19 } from "path";
|
|
5902
6172
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
5903
6173
|
import {
|
|
5904
6174
|
listAuthorityRuns as listAuthorityRuns3,
|
|
5905
|
-
readAuthorityRun as readAuthorityRun4
|
|
5906
|
-
readJsonlFile as readJsonlFile4,
|
|
5907
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6175
|
+
readAuthorityRun as readAuthorityRun4
|
|
5908
6176
|
} from "@rig/runtime/control-plane/authority-files";
|
|
5909
6177
|
import {
|
|
5910
6178
|
cleanupRunState,
|
|
@@ -5912,6 +6180,7 @@ import {
|
|
|
5912
6180
|
listOpenEpics,
|
|
5913
6181
|
resolveDefaultEpic,
|
|
5914
6182
|
runResume,
|
|
6183
|
+
runRestart,
|
|
5915
6184
|
runStatus,
|
|
5916
6185
|
runStop,
|
|
5917
6186
|
startRun,
|
|
@@ -5919,9 +6188,9 @@ import {
|
|
|
5919
6188
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
5920
6189
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
5921
6190
|
|
|
5922
|
-
// packages/cli/src/commands/_operator-
|
|
6191
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
5923
6192
|
import { createInterface } from "readline";
|
|
5924
|
-
|
|
6193
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
5925
6194
|
var CANONICAL_STAGES = [
|
|
5926
6195
|
"Connect",
|
|
5927
6196
|
"GitHub/task sync",
|
|
@@ -5936,18 +6205,188 @@ var CANONICAL_STAGES = [
|
|
|
5936
6205
|
"Merge",
|
|
5937
6206
|
"Complete"
|
|
5938
6207
|
];
|
|
6208
|
+
function logDetail(log3) {
|
|
6209
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6210
|
+
}
|
|
6211
|
+
function parseProviderProtocolLog(title, detail) {
|
|
6212
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
6213
|
+
return null;
|
|
6214
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
6215
|
+
return null;
|
|
6216
|
+
try {
|
|
6217
|
+
const record = JSON.parse(detail);
|
|
6218
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
6219
|
+
return null;
|
|
6220
|
+
const type = record.type;
|
|
6221
|
+
return typeof type === "string" && [
|
|
6222
|
+
"assistant",
|
|
6223
|
+
"message_start",
|
|
6224
|
+
"message_update",
|
|
6225
|
+
"message_end",
|
|
6226
|
+
"stream_event",
|
|
6227
|
+
"tool_result",
|
|
6228
|
+
"tool_execution_start",
|
|
6229
|
+
"tool_execution_update",
|
|
6230
|
+
"tool_execution_end",
|
|
6231
|
+
"turn_start",
|
|
6232
|
+
"turn_end"
|
|
6233
|
+
].includes(type) ? record : null;
|
|
6234
|
+
} catch {
|
|
6235
|
+
return null;
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
function renderProviderProtocolLog(record) {
|
|
6239
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
6240
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
6241
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
6242
|
+
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";
|
|
6243
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
6244
|
+
}
|
|
6245
|
+
return null;
|
|
6246
|
+
}
|
|
6247
|
+
function entryId(entry, fallback) {
|
|
6248
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6249
|
+
}
|
|
5939
6250
|
function renderOperatorSnapshot(snapshot) {
|
|
5940
6251
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
5941
6252
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
5942
6253
|
const status = String(run.status ?? "unknown");
|
|
5943
6254
|
const logs = snapshot.logs ?? [];
|
|
6255
|
+
const latestByStage = new Map;
|
|
6256
|
+
for (const log3 of logs) {
|
|
6257
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6258
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6259
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6260
|
+
if (stage)
|
|
6261
|
+
latestByStage.set(stage, log3);
|
|
6262
|
+
}
|
|
5944
6263
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
5945
|
-
const match =
|
|
5946
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6264
|
+
const match = latestByStage.get(stage);
|
|
6265
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
5947
6266
|
});
|
|
5948
6267
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
5949
6268
|
`);
|
|
5950
6269
|
}
|
|
6270
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6271
|
+
let lastSnapshot = "";
|
|
6272
|
+
const assistantTextById = new Map;
|
|
6273
|
+
const seenTimeline = new Set;
|
|
6274
|
+
const seenLogs = new Set;
|
|
6275
|
+
const writeLine = (line) => output.write(`${line}
|
|
6276
|
+
`);
|
|
6277
|
+
return {
|
|
6278
|
+
renderSnapshot(snapshot) {
|
|
6279
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6280
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6281
|
+
writeLine(rendered);
|
|
6282
|
+
lastSnapshot = rendered;
|
|
6283
|
+
}
|
|
6284
|
+
},
|
|
6285
|
+
renderTimeline(entries) {
|
|
6286
|
+
for (const [index, entry] of entries.entries()) {
|
|
6287
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6288
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6289
|
+
const text2 = entry.text;
|
|
6290
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6291
|
+
if (!previousText && text2.trim()) {
|
|
6292
|
+
writeLine("[Pi assistant]");
|
|
6293
|
+
}
|
|
6294
|
+
if (text2.startsWith(previousText)) {
|
|
6295
|
+
const delta = text2.slice(previousText.length);
|
|
6296
|
+
if (delta)
|
|
6297
|
+
output.write(delta);
|
|
6298
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6299
|
+
if (previousText)
|
|
6300
|
+
writeLine(`
|
|
6301
|
+
[Pi assistant]`);
|
|
6302
|
+
output.write(text2);
|
|
6303
|
+
}
|
|
6304
|
+
assistantTextById.set(id, text2);
|
|
6305
|
+
continue;
|
|
6306
|
+
}
|
|
6307
|
+
if (seenTimeline.has(id))
|
|
6308
|
+
continue;
|
|
6309
|
+
seenTimeline.add(id);
|
|
6310
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6311
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6312
|
+
continue;
|
|
6313
|
+
}
|
|
6314
|
+
if (entry.type === "timeline_warning") {
|
|
6315
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
},
|
|
6319
|
+
renderLogs(entries) {
|
|
6320
|
+
for (const [index, entry] of entries.entries()) {
|
|
6321
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6322
|
+
if (seenLogs.has(id))
|
|
6323
|
+
continue;
|
|
6324
|
+
seenLogs.add(id);
|
|
6325
|
+
const title = String(entry.title ?? "");
|
|
6326
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6327
|
+
continue;
|
|
6328
|
+
const detail = logDetail(entry);
|
|
6329
|
+
if (!detail)
|
|
6330
|
+
continue;
|
|
6331
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
6332
|
+
if (protocolRecord) {
|
|
6333
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
6334
|
+
if (protocolLine)
|
|
6335
|
+
writeLine(protocolLine);
|
|
6336
|
+
continue;
|
|
6337
|
+
}
|
|
6338
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
function createOperatorSurface(options = {}) {
|
|
6344
|
+
const input = options.input ?? process.stdin;
|
|
6345
|
+
const output = options.output ?? process.stdout;
|
|
6346
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6347
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6348
|
+
const writeLine = (line) => output.write(`${line}
|
|
6349
|
+
`);
|
|
6350
|
+
return {
|
|
6351
|
+
mode: "pi-compatible-text",
|
|
6352
|
+
...renderer,
|
|
6353
|
+
info: writeLine,
|
|
6354
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6355
|
+
`),
|
|
6356
|
+
attachCommandInput(handler) {
|
|
6357
|
+
if (options.interactive === false || !input.isTTY)
|
|
6358
|
+
return null;
|
|
6359
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6360
|
+
rl.on("line", (line) => {
|
|
6361
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6362
|
+
});
|
|
6363
|
+
return { close: () => rl.close() };
|
|
6364
|
+
}
|
|
6365
|
+
};
|
|
6366
|
+
}
|
|
6367
|
+
function taskId(task) {
|
|
6368
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6369
|
+
}
|
|
6370
|
+
function taskTitle(task) {
|
|
6371
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6372
|
+
}
|
|
6373
|
+
function taskStatus(task) {
|
|
6374
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6375
|
+
}
|
|
6376
|
+
function renderTaskPickerRows(tasks) {
|
|
6377
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6378
|
+
}
|
|
6379
|
+
async function promptForTaskSelection(question) {
|
|
6380
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6381
|
+
try {
|
|
6382
|
+
return await rl.question(question);
|
|
6383
|
+
} finally {
|
|
6384
|
+
rl.close();
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
|
|
6388
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6389
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
5951
6390
|
function runStatusFromPayload(payload) {
|
|
5952
6391
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
5953
6392
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -5969,11 +6408,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
5969
6408
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
5970
6409
|
return { action: "continue", message: "Steering message queued." };
|
|
5971
6410
|
}
|
|
5972
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6411
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
5973
6412
|
const run = await getRunDetailsViaServer(context, runId);
|
|
5974
6413
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
5975
|
-
const
|
|
5976
|
-
|
|
6414
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6415
|
+
entries: [{
|
|
6416
|
+
id: `timeline-unavailable:${runId}`,
|
|
6417
|
+
type: "timeline_warning",
|
|
6418
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6419
|
+
createdAt: new Date().toISOString()
|
|
6420
|
+
}],
|
|
6421
|
+
nextCursor: options.timelineCursor ?? null
|
|
6422
|
+
}));
|
|
6423
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6424
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6425
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6426
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
5977
6427
|
}
|
|
5978
6428
|
async function attachRunOperatorView(context, input) {
|
|
5979
6429
|
let steered = false;
|
|
@@ -5981,45 +6431,266 @@ async function attachRunOperatorView(context, input) {
|
|
|
5981
6431
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
5982
6432
|
steered = true;
|
|
5983
6433
|
}
|
|
6434
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
5984
6435
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
5985
6436
|
if (context.outputMode === "text") {
|
|
5986
|
-
|
|
6437
|
+
surface.renderSnapshot(snapshot);
|
|
6438
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6439
|
+
surface.renderLogs(snapshot.logs);
|
|
5987
6440
|
if (steered)
|
|
5988
|
-
|
|
6441
|
+
surface.info("Steering message queued.");
|
|
5989
6442
|
}
|
|
5990
6443
|
let detached = false;
|
|
5991
|
-
let
|
|
6444
|
+
let commandInput = null;
|
|
5992
6445
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
5993
6446
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
}
|
|
6004
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6447
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6448
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6449
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6450
|
+
if (result.message)
|
|
6451
|
+
surface.info(result.message);
|
|
6452
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6453
|
+
detached = true;
|
|
6454
|
+
commandInput?.close();
|
|
6455
|
+
}
|
|
6005
6456
|
});
|
|
6006
6457
|
}
|
|
6007
|
-
let lastRendered = snapshot.rendered;
|
|
6008
6458
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6459
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6009
6460
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6010
6461
|
await Bun.sleep(pollMs);
|
|
6011
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6462
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6463
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6464
|
+
surface.renderSnapshot(snapshot);
|
|
6465
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6466
|
+
surface.renderLogs(snapshot.logs);
|
|
6016
6467
|
}
|
|
6017
|
-
|
|
6468
|
+
commandInput?.close();
|
|
6018
6469
|
}
|
|
6019
6470
|
return { ...snapshot, steered, detached };
|
|
6020
6471
|
}
|
|
6021
6472
|
|
|
6473
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
6474
|
+
import pc3 from "picocolors";
|
|
6475
|
+
function stringField(record, key, fallback = "") {
|
|
6476
|
+
const value = record[key];
|
|
6477
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
6478
|
+
}
|
|
6479
|
+
function arrayField(record, key) {
|
|
6480
|
+
const value = record[key];
|
|
6481
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
|
|
6482
|
+
}
|
|
6483
|
+
function rawObject(record) {
|
|
6484
|
+
const raw = record.raw;
|
|
6485
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
6486
|
+
}
|
|
6487
|
+
function truncate(value, width) {
|
|
6488
|
+
if (value.length <= width)
|
|
6489
|
+
return value;
|
|
6490
|
+
if (width <= 1)
|
|
6491
|
+
return "\u2026";
|
|
6492
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
6493
|
+
}
|
|
6494
|
+
function pad(value, width) {
|
|
6495
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
6496
|
+
}
|
|
6497
|
+
function statusColor(status) {
|
|
6498
|
+
const normalized = status.toLowerCase();
|
|
6499
|
+
if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
|
|
6500
|
+
return pc3.green;
|
|
6501
|
+
if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
|
|
6502
|
+
return pc3.red;
|
|
6503
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
|
|
6504
|
+
return pc3.cyan;
|
|
6505
|
+
if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
|
|
6506
|
+
return pc3.yellow;
|
|
6507
|
+
return pc3.dim;
|
|
6508
|
+
}
|
|
6509
|
+
function formatTaskList(tasks, options = {}) {
|
|
6510
|
+
if (tasks.length === 0)
|
|
6511
|
+
return pc3.dim("No matching tasks.");
|
|
6512
|
+
if (options.raw)
|
|
6513
|
+
return tasks.map((task) => JSON.stringify(task)).join(`
|
|
6514
|
+
`);
|
|
6515
|
+
const rows = tasks.map((task) => {
|
|
6516
|
+
const raw = rawObject(task);
|
|
6517
|
+
const id = stringField(task, "id", "<unknown>");
|
|
6518
|
+
const status = stringField(task, "status", "unknown");
|
|
6519
|
+
const title = stringField(task, "title", "Untitled task");
|
|
6520
|
+
const source = stringField(task, "source", stringField(raw, "source", ""));
|
|
6521
|
+
const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
|
|
6522
|
+
return { id, status, title, source, labels };
|
|
6523
|
+
});
|
|
6524
|
+
const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
|
|
6525
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6526
|
+
const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6527
|
+
const body = rows.map((row) => {
|
|
6528
|
+
const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
|
|
6529
|
+
const source = row.source ? pc3.dim(` ${row.source}`) : "";
|
|
6530
|
+
return [
|
|
6531
|
+
pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
|
|
6532
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6533
|
+
`${row.title}${labels}${source}`
|
|
6534
|
+
].join(" ");
|
|
6535
|
+
});
|
|
6536
|
+
return [pc3.bold("Rig tasks"), header, ...body].join(`
|
|
6537
|
+
`);
|
|
6538
|
+
}
|
|
6539
|
+
function formatRunList(runs, options = {}) {
|
|
6540
|
+
if (runs.length === 0) {
|
|
6541
|
+
return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
|
|
6542
|
+
}
|
|
6543
|
+
const rows = runs.map((run) => {
|
|
6544
|
+
const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
|
|
6545
|
+
const status = stringField(run, "status", "unknown");
|
|
6546
|
+
const taskId2 = stringField(run, "taskId", "");
|
|
6547
|
+
const title = stringField(run, "title", taskId2 || "(untitled)");
|
|
6548
|
+
const runtime = stringField(run, "runtimeAdapter", "");
|
|
6549
|
+
return { runId, status, title, runtime };
|
|
6550
|
+
});
|
|
6551
|
+
const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
|
|
6552
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6553
|
+
const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6554
|
+
const body = rows.map((row) => [
|
|
6555
|
+
pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
|
|
6556
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6557
|
+
`${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
|
|
6558
|
+
].join(" "));
|
|
6559
|
+
return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
|
|
6560
|
+
`);
|
|
6561
|
+
}
|
|
6562
|
+
function formatSubmittedRun(input) {
|
|
6563
|
+
const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
|
|
6564
|
+
if (input.task) {
|
|
6565
|
+
const id = stringField(input.task, "id", "<unknown>");
|
|
6566
|
+
const status = stringField(input.task, "status", "unknown");
|
|
6567
|
+
const title = stringField(input.task, "title", "Untitled task");
|
|
6568
|
+
lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
|
|
6569
|
+
}
|
|
6570
|
+
return lines.join(`
|
|
6571
|
+
`);
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
// packages/cli/src/commands/_pi-session.ts
|
|
6575
|
+
import { spawn as spawn2 } from "child_process";
|
|
6576
|
+
function buildPiRigSessionEnv(input) {
|
|
6577
|
+
return {
|
|
6578
|
+
RIG_PROJECT_ROOT: input.projectRoot,
|
|
6579
|
+
PROJECT_RIG_ROOT: input.projectRoot,
|
|
6580
|
+
RIG_RUN_ID: input.runId,
|
|
6581
|
+
RIG_SERVER_RUN_ID: input.runId,
|
|
6582
|
+
RIG_RUNTIME_ADAPTER: "pi",
|
|
6583
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
6584
|
+
RIG_SERVER_BASE_URL: input.serverUrl,
|
|
6585
|
+
RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
|
|
6586
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
6587
|
+
...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
|
|
6588
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
|
|
6589
|
+
};
|
|
6590
|
+
}
|
|
6591
|
+
function shellBinary(name) {
|
|
6592
|
+
const explicit = process.env.RIG_PI_BINARY?.trim();
|
|
6593
|
+
if (explicit)
|
|
6594
|
+
return explicit;
|
|
6595
|
+
return Bun.which(name) || name;
|
|
6596
|
+
}
|
|
6597
|
+
function buildPiRigSessionCommand(input) {
|
|
6598
|
+
const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
|
|
6599
|
+
const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
|
|
6600
|
+
const initialCommand = `/rig attach ${input.runId}`;
|
|
6601
|
+
return [
|
|
6602
|
+
shellBinary("pi"),
|
|
6603
|
+
"--no-extensions",
|
|
6604
|
+
"--extension",
|
|
6605
|
+
extensionSource,
|
|
6606
|
+
initialCommand
|
|
6607
|
+
];
|
|
6608
|
+
}
|
|
6609
|
+
async function launchPiRigSession(context, input) {
|
|
6610
|
+
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
6611
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6612
|
+
}
|
|
6613
|
+
if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
|
|
6614
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6615
|
+
}
|
|
6616
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
6617
|
+
const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
|
|
6618
|
+
const env = {
|
|
6619
|
+
...process.env,
|
|
6620
|
+
...buildPiRigSessionEnv({
|
|
6621
|
+
projectRoot: context.projectRoot,
|
|
6622
|
+
runId: input.runId,
|
|
6623
|
+
taskId: input.taskId,
|
|
6624
|
+
serverUrl: server.baseUrl,
|
|
6625
|
+
authToken: server.authToken
|
|
6626
|
+
})
|
|
6627
|
+
};
|
|
6628
|
+
process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
|
|
6629
|
+
`);
|
|
6630
|
+
process.stdout.write(`Pi command: ${formatCommand(command)}
|
|
6631
|
+
`);
|
|
6632
|
+
const launchedAt = Date.now();
|
|
6633
|
+
const child = spawn2(command[0], command.slice(1), {
|
|
6634
|
+
cwd: context.projectRoot,
|
|
6635
|
+
env,
|
|
6636
|
+
stdio: "inherit"
|
|
6637
|
+
});
|
|
6638
|
+
const launchError = await new Promise((resolve19) => {
|
|
6639
|
+
child.once("error", (error) => {
|
|
6640
|
+
resolve19({ error: error.message });
|
|
6641
|
+
});
|
|
6642
|
+
child.once("close", (code) => resolve19({ code }));
|
|
6643
|
+
});
|
|
6644
|
+
if ("error" in launchError) {
|
|
6645
|
+
process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
|
|
6646
|
+
`);
|
|
6647
|
+
return { launched: false, exitCode: null, command, error: launchError.error };
|
|
6648
|
+
}
|
|
6649
|
+
const exitCode = launchError.code;
|
|
6650
|
+
const elapsedMs = Date.now() - launchedAt;
|
|
6651
|
+
if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
|
|
6652
|
+
const error = `Pi exited during startup with code ${exitCode}.`;
|
|
6653
|
+
process.stderr.write(`${error} Falling back to Rig attach view.
|
|
6654
|
+
`);
|
|
6655
|
+
return { launched: false, exitCode, command, error };
|
|
6656
|
+
}
|
|
6657
|
+
return { launched: true, exitCode, command };
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6022
6660
|
// packages/cli/src/commands/run.ts
|
|
6661
|
+
function normalizeRemoteRunDetails(payload) {
|
|
6662
|
+
const run = payload.run;
|
|
6663
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
6664
|
+
return null;
|
|
6665
|
+
return {
|
|
6666
|
+
...run,
|
|
6667
|
+
...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
|
|
6668
|
+
...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
|
|
6669
|
+
...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
|
|
6670
|
+
};
|
|
6671
|
+
}
|
|
6672
|
+
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
6673
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
6674
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
6675
|
+
}
|
|
6676
|
+
async function listRunsForSelectedConnection(context, options = {}) {
|
|
6677
|
+
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
6678
|
+
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
6679
|
+
}
|
|
6680
|
+
return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
|
|
6681
|
+
}
|
|
6682
|
+
function runStringField(run, key, fallback = "") {
|
|
6683
|
+
const value = run[key];
|
|
6684
|
+
return typeof value === "string" && value.trim() ? value : fallback;
|
|
6685
|
+
}
|
|
6686
|
+
function runDisplayTitle(run) {
|
|
6687
|
+
return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
|
|
6688
|
+
}
|
|
6689
|
+
function buildServerRunStatus(runs) {
|
|
6690
|
+
const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6691
|
+
const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6692
|
+
return { activeRuns, recentRuns, runs };
|
|
6693
|
+
}
|
|
6023
6694
|
function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
|
|
6024
6695
|
if (noEpicPrompt) {
|
|
6025
6696
|
return false;
|
|
@@ -6085,17 +6756,11 @@ async function executeRun(context, args) {
|
|
|
6085
6756
|
switch (command) {
|
|
6086
6757
|
case "list": {
|
|
6087
6758
|
requireNoExtraArgs(rest, "bun run rig run list");
|
|
6088
|
-
const runs =
|
|
6759
|
+
const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
|
|
6089
6760
|
if (context.outputMode === "text") {
|
|
6090
|
-
|
|
6091
|
-
console.log("No runs recorded in .rig/runs.");
|
|
6092
|
-
} else {
|
|
6093
|
-
for (const run of runs) {
|
|
6094
|
-
console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
6761
|
+
console.log(formatRunList(runs, { source }));
|
|
6097
6762
|
}
|
|
6098
|
-
return { ok: true, group: "run", command, details: { runs } };
|
|
6763
|
+
return { ok: true, group: "run", command, details: { runs, source } };
|
|
6099
6764
|
}
|
|
6100
6765
|
case "delete": {
|
|
6101
6766
|
let pending = rest;
|
|
@@ -6158,7 +6823,7 @@ async function executeRun(context, args) {
|
|
|
6158
6823
|
if (!run.value) {
|
|
6159
6824
|
throw new CliError2("run show requires --run <id>.");
|
|
6160
6825
|
}
|
|
6161
|
-
const record = readAuthorityRun4(context.projectRoot, run.value);
|
|
6826
|
+
const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
|
|
6162
6827
|
if (!record) {
|
|
6163
6828
|
throw new CliError2(`Run not found: ${run.value}`, 2);
|
|
6164
6829
|
}
|
|
@@ -6177,34 +6842,24 @@ async function executeRun(context, args) {
|
|
|
6177
6842
|
if (!run.value) {
|
|
6178
6843
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6179
6844
|
}
|
|
6180
|
-
const
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
return events2;
|
|
6189
|
-
};
|
|
6190
|
-
const events = printEvents();
|
|
6845
|
+
const renderer = createPiRunStreamRenderer();
|
|
6846
|
+
let cursor = null;
|
|
6847
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
6848
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6849
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
6850
|
+
if (context.outputMode === "text") {
|
|
6851
|
+
renderer.renderTimeline(events);
|
|
6852
|
+
}
|
|
6191
6853
|
if (follow.value && context.outputMode === "text") {
|
|
6192
|
-
let lastLength = existsSync11(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6193
6854
|
while (true) {
|
|
6194
6855
|
await Bun.sleep(1000);
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
continue;
|
|
6200
|
-
const delta = next.slice(lastLength);
|
|
6201
|
-
lastLength = next.length;
|
|
6202
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6203
|
-
console.log(line);
|
|
6204
|
-
}
|
|
6856
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
6857
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6858
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
6859
|
+
renderer.renderTimeline(nextEvents);
|
|
6205
6860
|
}
|
|
6206
6861
|
}
|
|
6207
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
6862
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6208
6863
|
}
|
|
6209
6864
|
case "attach": {
|
|
6210
6865
|
let pending = rest;
|
|
@@ -6225,14 +6880,26 @@ async function executeRun(context, args) {
|
|
|
6225
6880
|
if (!runId) {
|
|
6226
6881
|
throw new CliError2("run attach requires a run id.", 2);
|
|
6227
6882
|
}
|
|
6883
|
+
let steered = false;
|
|
6884
|
+
const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
|
|
6885
|
+
if (shouldTryPiAttach && messageOption.value?.trim()) {
|
|
6886
|
+
await steerRunViaServer(context, runId, messageOption.value.trim());
|
|
6887
|
+
steered = true;
|
|
6888
|
+
}
|
|
6889
|
+
if (shouldTryPiAttach) {
|
|
6890
|
+
const piSession = await launchPiRigSession(context, { runId });
|
|
6891
|
+
if (piSession.launched) {
|
|
6892
|
+
return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6228
6895
|
const attached = await attachRunOperatorView(context, {
|
|
6229
6896
|
runId,
|
|
6230
|
-
message: messageOption.value ?? null,
|
|
6897
|
+
message: shouldTryPiAttach ? null : messageOption.value ?? null,
|
|
6231
6898
|
once: once.value,
|
|
6232
6899
|
follow: follow.value,
|
|
6233
6900
|
pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
|
|
6234
6901
|
});
|
|
6235
|
-
return { ok: true, group: "run", command, details: attached };
|
|
6902
|
+
return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
|
|
6236
6903
|
}
|
|
6237
6904
|
case "status": {
|
|
6238
6905
|
requireNoExtraArgs(rest, "bun run rig run status");
|
|
@@ -6242,17 +6909,19 @@ async function executeRun(context, args) {
|
|
|
6242
6909
|
}
|
|
6243
6910
|
return { ok: true, group: "run", command };
|
|
6244
6911
|
}
|
|
6245
|
-
const summary = runStatus(context.projectRoot, runtimeContext);
|
|
6912
|
+
const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
|
|
6913
|
+
const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
6914
|
+
const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
6246
6915
|
if (context.outputMode === "text") {
|
|
6247
|
-
console.log(`Active runs: ${
|
|
6248
|
-
for (const run of
|
|
6249
|
-
console.log(`- ${run
|
|
6916
|
+
console.log(`Active runs: ${activeRuns.length}`);
|
|
6917
|
+
for (const run of activeRuns) {
|
|
6918
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6250
6919
|
}
|
|
6251
|
-
if (
|
|
6920
|
+
if (recentRuns.length > 0) {
|
|
6252
6921
|
console.log("");
|
|
6253
6922
|
console.log("Recent runs:");
|
|
6254
|
-
for (const run of
|
|
6255
|
-
console.log(`- ${run
|
|
6923
|
+
for (const run of recentRuns) {
|
|
6924
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6256
6925
|
}
|
|
6257
6926
|
}
|
|
6258
6927
|
}
|
|
@@ -6339,6 +7008,20 @@ async function executeRun(context, args) {
|
|
|
6339
7008
|
}
|
|
6340
7009
|
return { ok: true, group: "run", command, details: resumed };
|
|
6341
7010
|
}
|
|
7011
|
+
case "restart": {
|
|
7012
|
+
requireNoExtraArgs(rest, "bun run rig run restart");
|
|
7013
|
+
if (context.dryRun) {
|
|
7014
|
+
if (context.outputMode === "text") {
|
|
7015
|
+
console.log("[dry-run] rig run restart");
|
|
7016
|
+
}
|
|
7017
|
+
return { ok: true, group: "run", command };
|
|
7018
|
+
}
|
|
7019
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
7020
|
+
if (context.outputMode === "text") {
|
|
7021
|
+
console.log(`Restarted run: ${restarted.runId}`);
|
|
7022
|
+
}
|
|
7023
|
+
return { ok: true, group: "run", command, details: restarted };
|
|
7024
|
+
}
|
|
6342
7025
|
case "stop": {
|
|
6343
7026
|
const runOption = takeOption(rest, "--run");
|
|
6344
7027
|
const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
|
|
@@ -6396,7 +7079,7 @@ async function executeServer(context, args, options) {
|
|
|
6396
7079
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
6397
7080
|
pending = authTokenResult.rest;
|
|
6398
7081
|
requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
6399
|
-
const commandParts = ["
|
|
7082
|
+
const commandParts = ["rig-server", "start"];
|
|
6400
7083
|
if (hostResult.value) {
|
|
6401
7084
|
commandParts.push("--host", hostResult.value);
|
|
6402
7085
|
}
|
|
@@ -6419,7 +7102,7 @@ async function executeServer(context, args, options) {
|
|
|
6419
7102
|
const eventResult = takeOption(pending, "--event");
|
|
6420
7103
|
pending = eventResult.rest;
|
|
6421
7104
|
requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
|
|
6422
|
-
const commandParts = ["
|
|
7105
|
+
const commandParts = ["rig-server", "notify-test"];
|
|
6423
7106
|
if (eventResult.value) {
|
|
6424
7107
|
commandParts.push("--event", eventResult.value);
|
|
6425
7108
|
}
|
|
@@ -6480,10 +7163,10 @@ async function executeServer(context, args, options) {
|
|
|
6480
7163
|
}
|
|
6481
7164
|
|
|
6482
7165
|
// packages/cli/src/commands/task.ts
|
|
6483
|
-
import { readFileSync as
|
|
6484
|
-
import { spawnSync as
|
|
6485
|
-
import {
|
|
6486
|
-
import {
|
|
7166
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7167
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7168
|
+
import { resolve as resolve19 } from "path";
|
|
7169
|
+
import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
|
|
6487
7170
|
import {
|
|
6488
7171
|
taskArtifactDir,
|
|
6489
7172
|
taskArtifacts,
|
|
@@ -6501,19 +7184,10 @@ import {
|
|
|
6501
7184
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6502
7185
|
|
|
6503
7186
|
// packages/cli/src/commands/_task-picker.ts
|
|
6504
|
-
import {
|
|
6505
|
-
function
|
|
7187
|
+
import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
|
|
7188
|
+
function taskId2(task) {
|
|
6506
7189
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6507
7190
|
}
|
|
6508
|
-
function taskTitle(task) {
|
|
6509
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6510
|
-
}
|
|
6511
|
-
function taskStatus(task) {
|
|
6512
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6513
|
-
}
|
|
6514
|
-
function renderTaskPickerRows(tasks) {
|
|
6515
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6516
|
-
}
|
|
6517
7191
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6518
7192
|
if (tasks.length === 0)
|
|
6519
7193
|
return null;
|
|
@@ -6523,25 +7197,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6523
7197
|
if (!isTty) {
|
|
6524
7198
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6525
7199
|
}
|
|
6526
|
-
|
|
6527
|
-
const
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
7200
|
+
if (io.prompt || io.renderer) {
|
|
7201
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
7202
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
7203
|
+
`) };
|
|
7204
|
+
renderer.writeLine("Select Rig task:");
|
|
7205
|
+
for (const row of renderTaskPickerRows(tasks))
|
|
7206
|
+
renderer.writeLine(` ${row}`);
|
|
7207
|
+
const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
7208
|
+
if (!answer2)
|
|
7209
|
+
return null;
|
|
7210
|
+
if (/^\d+$/.test(answer2)) {
|
|
7211
|
+
const index2 = Number.parseInt(answer2, 10) - 1;
|
|
7212
|
+
return tasks[index2] ?? null;
|
|
6532
7213
|
}
|
|
7214
|
+
return tasks.find((task) => taskId2(task) === answer2) ?? null;
|
|
7215
|
+
}
|
|
7216
|
+
const options = tasks.map((task, index2) => ({
|
|
7217
|
+
value: `${index2}`,
|
|
7218
|
+
label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
|
|
7219
|
+
hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
|
|
7220
|
+
}));
|
|
7221
|
+
const answer = await select2({
|
|
7222
|
+
message: "Select Rig task",
|
|
7223
|
+
options
|
|
6533
7224
|
});
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
console.log(` ${row}`);
|
|
6537
|
-
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6538
|
-
if (!answer)
|
|
7225
|
+
if (isCancel2(answer)) {
|
|
7226
|
+
cancel2("No task selected.");
|
|
6539
7227
|
return null;
|
|
6540
|
-
if (/^\d+$/.test(answer)) {
|
|
6541
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
6542
|
-
return tasks[index] ?? null;
|
|
6543
7228
|
}
|
|
6544
|
-
|
|
7229
|
+
const index = Number.parseInt(String(answer), 10);
|
|
7230
|
+
return Number.isFinite(index) ? tasks[index] ?? null : null;
|
|
6545
7231
|
}
|
|
6546
7232
|
|
|
6547
7233
|
// packages/cli/src/commands/task.ts
|
|
@@ -6619,7 +7305,7 @@ function normalizePrMode(value) {
|
|
|
6619
7305
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6620
7306
|
}
|
|
6621
7307
|
function detectLocalDirtyState(projectRoot) {
|
|
6622
|
-
const result =
|
|
7308
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6623
7309
|
if (result.status !== 0)
|
|
6624
7310
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6625
7311
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6653,13 +7339,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6653
7339
|
if (explicit)
|
|
6654
7340
|
return { mode: explicit, state };
|
|
6655
7341
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6656
|
-
const
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
7342
|
+
const answer = await confirm2({
|
|
7343
|
+
message: "Include current uncommitted changes in run baseline?",
|
|
7344
|
+
initialValue: false
|
|
7345
|
+
});
|
|
7346
|
+
if (isCancel3(answer)) {
|
|
7347
|
+
cancel3("Run cancelled.");
|
|
7348
|
+
throw new CliError2("Run cancelled by user.", 1);
|
|
6662
7349
|
}
|
|
7350
|
+
return { mode: answer ? "dirty-snapshot" : "head", state };
|
|
6663
7351
|
}
|
|
6664
7352
|
return { mode: "head", state };
|
|
6665
7353
|
}
|
|
@@ -6694,10 +7382,7 @@ function summarizeTask(task, options = {}) {
|
|
|
6694
7382
|
};
|
|
6695
7383
|
}
|
|
6696
7384
|
function printTaskSummary(task) {
|
|
6697
|
-
|
|
6698
|
-
const title = readTaskString(task, "title") ?? "Untitled task";
|
|
6699
|
-
const status = readTaskString(task, "status") ?? "unknown";
|
|
6700
|
-
console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
|
|
7385
|
+
console.log(formatTaskList([task]));
|
|
6701
7386
|
}
|
|
6702
7387
|
async function validatorRegistryForTaskCommands(projectRoot) {
|
|
6703
7388
|
return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
|
|
@@ -6715,16 +7400,8 @@ async function executeTask(context, args, options) {
|
|
|
6715
7400
|
requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
6716
7401
|
const tasks = await listWorkspaceTasksViaServer(context, filters);
|
|
6717
7402
|
if (context.outputMode === "text") {
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
} else {
|
|
6721
|
-
for (const task of tasks) {
|
|
6722
|
-
if (rawResult.value)
|
|
6723
|
-
console.log(JSON.stringify(summarizeTask(task, { raw: true })));
|
|
6724
|
-
else
|
|
6725
|
-
printTaskSummary(task);
|
|
6726
|
-
}
|
|
6727
|
-
}
|
|
7403
|
+
const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
|
|
7404
|
+
console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
|
|
6728
7405
|
}
|
|
6729
7406
|
return {
|
|
6730
7407
|
ok: true,
|
|
@@ -6738,12 +7415,12 @@ async function executeTask(context, args, options) {
|
|
|
6738
7415
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
6739
7416
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
6740
7417
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
6741
|
-
const
|
|
6742
|
-
if (!
|
|
7418
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7419
|
+
if (!taskId3)
|
|
6743
7420
|
throw new CliError2("task show requires a task id.", 2);
|
|
6744
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7421
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
6745
7422
|
if (!task)
|
|
6746
|
-
throw new CliError2(`Task not found: ${
|
|
7423
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
6747
7424
|
const summary = summarizeTask(task, { raw: true });
|
|
6748
7425
|
if (context.outputMode === "text")
|
|
6749
7426
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -6814,7 +7491,7 @@ async function executeTask(context, args, options) {
|
|
|
6814
7491
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
6815
7492
|
let content;
|
|
6816
7493
|
if (fileFlag.value) {
|
|
6817
|
-
content =
|
|
7494
|
+
content = readFileSync9(resolve19(context.projectRoot, fileFlag.value), "utf-8");
|
|
6818
7495
|
} else {
|
|
6819
7496
|
content = await readStdin();
|
|
6820
7497
|
}
|
|
@@ -6943,16 +7620,23 @@ async function executeTask(context, args, options) {
|
|
|
6943
7620
|
});
|
|
6944
7621
|
let attachDetails = null;
|
|
6945
7622
|
if (!detachResult.value && context.outputMode === "text") {
|
|
6946
|
-
console.log(
|
|
6947
|
-
if (
|
|
6948
|
-
|
|
7623
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
7624
|
+
if (runtimeAdapter === "pi") {
|
|
7625
|
+
const piSession = await launchPiRigSession(context, {
|
|
7626
|
+
runId: submitted.runId,
|
|
7627
|
+
taskId: selectedTaskId,
|
|
7628
|
+
title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
|
|
7629
|
+
runtimeAdapter
|
|
7630
|
+
});
|
|
7631
|
+
attachDetails = { mode: "pi", ...piSession };
|
|
7632
|
+
if (!piSession.launched) {
|
|
7633
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7634
|
+
}
|
|
7635
|
+
} else {
|
|
7636
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
6949
7637
|
}
|
|
6950
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
6951
7638
|
} else if (context.outputMode === "text") {
|
|
6952
|
-
console.log(
|
|
6953
|
-
if (selectedTask) {
|
|
6954
|
-
printTaskSummary(selectedTask);
|
|
6955
|
-
}
|
|
7639
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
6956
7640
|
}
|
|
6957
7641
|
return {
|
|
6958
7642
|
ok: true,
|
|
@@ -7035,9 +7719,9 @@ async function executeTask(context, args, options) {
|
|
|
7035
7719
|
}
|
|
7036
7720
|
|
|
7037
7721
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7038
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7039
|
-
import { resolve as
|
|
7040
|
-
import { spawn as
|
|
7722
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7723
|
+
import { resolve as resolve20 } from "path";
|
|
7724
|
+
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
7041
7725
|
import { createInterface as createLineInterface } from "readline";
|
|
7042
7726
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7043
7727
|
import {
|
|
@@ -7062,15 +7746,31 @@ import {
|
|
|
7062
7746
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7063
7747
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7064
7748
|
import {
|
|
7065
|
-
buildTaskRunLifecycleComment
|
|
7066
|
-
updateConfiguredTaskSourceTask
|
|
7749
|
+
buildTaskRunLifecycleComment
|
|
7067
7750
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7068
7751
|
import {
|
|
7069
7752
|
closeIssueAfterMergedPr,
|
|
7070
7753
|
commitRunChanges,
|
|
7071
7754
|
runPrAutomation
|
|
7072
7755
|
} from "@rig/runtime/control-plane/native/pr-automation";
|
|
7756
|
+
function looksLikeGitHubToken(value) {
|
|
7757
|
+
const token = value?.trim();
|
|
7758
|
+
if (!token)
|
|
7759
|
+
return false;
|
|
7760
|
+
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
7761
|
+
}
|
|
7762
|
+
function githubBridgeEnv(token) {
|
|
7763
|
+
const clean = token?.trim();
|
|
7764
|
+
if (!clean)
|
|
7765
|
+
return {};
|
|
7766
|
+
return {
|
|
7767
|
+
RIG_GITHUB_TOKEN: clean,
|
|
7768
|
+
GITHUB_TOKEN: clean,
|
|
7769
|
+
GH_TOKEN: clean
|
|
7770
|
+
};
|
|
7771
|
+
}
|
|
7073
7772
|
function buildPiRigBridgeEnv(input) {
|
|
7773
|
+
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
7074
7774
|
return {
|
|
7075
7775
|
RIG_PROJECT_ROOT: input.projectRoot,
|
|
7076
7776
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
@@ -7080,11 +7780,11 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7080
7780
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
7081
7781
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
7082
7782
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
7083
|
-
...
|
|
7783
|
+
...githubBridgeEnv(githubToken)
|
|
7084
7784
|
};
|
|
7085
7785
|
}
|
|
7086
7786
|
function runGitSync(cwd, args, input) {
|
|
7087
|
-
const result =
|
|
7787
|
+
const result = spawnSync4("git", [...args], {
|
|
7088
7788
|
cwd,
|
|
7089
7789
|
input,
|
|
7090
7790
|
encoding: "utf8",
|
|
@@ -7102,12 +7802,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7102
7802
|
return 0;
|
|
7103
7803
|
let copied = 0;
|
|
7104
7804
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7105
|
-
const sourcePath =
|
|
7106
|
-
const targetPath =
|
|
7805
|
+
const sourcePath = resolve20(sourceRoot, relativePath);
|
|
7806
|
+
const targetPath = resolve20(targetRoot, relativePath);
|
|
7107
7807
|
try {
|
|
7108
7808
|
if (!statSync2(sourcePath).isFile())
|
|
7109
7809
|
continue;
|
|
7110
|
-
mkdirSync7(
|
|
7810
|
+
mkdirSync7(resolve20(targetPath, ".."), { recursive: true });
|
|
7111
7811
|
copyFileSync3(sourcePath, targetPath);
|
|
7112
7812
|
copied += 1;
|
|
7113
7813
|
} catch {}
|
|
@@ -7146,7 +7846,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7146
7846
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7147
7847
|
return {
|
|
7148
7848
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7149
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
7849
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve20(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7150
7850
|
};
|
|
7151
7851
|
}
|
|
7152
7852
|
function positiveInt(value, fallback) {
|
|
@@ -7154,7 +7854,7 @@ function positiveInt(value, fallback) {
|
|
|
7154
7854
|
}
|
|
7155
7855
|
function resolveTaskRunAutomationLimits(config, env = process.env) {
|
|
7156
7856
|
const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
|
|
7157
|
-
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations,
|
|
7857
|
+
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
|
|
7158
7858
|
return {
|
|
7159
7859
|
maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
|
|
7160
7860
|
maxPrFixIterations: configuredPrFixIterations
|
|
@@ -7249,7 +7949,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
|
|
|
7249
7949
|
}
|
|
7250
7950
|
function createCommandRunner(binary) {
|
|
7251
7951
|
return async (args, options) => {
|
|
7252
|
-
const child =
|
|
7952
|
+
const child = spawn3(binary, [...args], {
|
|
7253
7953
|
cwd: options?.cwd,
|
|
7254
7954
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7255
7955
|
});
|
|
@@ -7257,9 +7957,9 @@ function createCommandRunner(binary) {
|
|
|
7257
7957
|
const stderrChunks = [];
|
|
7258
7958
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7259
7959
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7260
|
-
return await new Promise((
|
|
7261
|
-
child.once("error", (error) =>
|
|
7262
|
-
child.once("close", (code) =>
|
|
7960
|
+
return await new Promise((resolve21) => {
|
|
7961
|
+
child.once("error", (error) => resolve21({ exitCode: 1, stderr: error.message }));
|
|
7962
|
+
child.once("close", (code) => resolve21({
|
|
7263
7963
|
exitCode: code ?? 1,
|
|
7264
7964
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7265
7965
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7279,12 +7979,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7279
7979
|
return input.configuredBranch;
|
|
7280
7980
|
}
|
|
7281
7981
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7282
|
-
const
|
|
7283
|
-
if (!
|
|
7982
|
+
const taskId3 = input.taskId?.trim();
|
|
7983
|
+
if (!taskId3) {
|
|
7284
7984
|
return { status: "skipped" };
|
|
7285
7985
|
}
|
|
7286
|
-
const
|
|
7287
|
-
const
|
|
7986
|
+
const configInput = input.config ?? null;
|
|
7987
|
+
const config = configInput ?? {};
|
|
7988
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7288
7989
|
if (prMode === "off" || prMode === "ask") {
|
|
7289
7990
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7290
7991
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7300,7 +8001,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7300
8001
|
gitCommand
|
|
7301
8002
|
});
|
|
7302
8003
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7303
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
8004
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7304
8005
|
const stage = input.appendStage ?? (() => {
|
|
7305
8006
|
return;
|
|
7306
8007
|
});
|
|
@@ -7318,7 +8019,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7318
8019
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7319
8020
|
const commit = await commitRunChanges({
|
|
7320
8021
|
cwd: workspace,
|
|
7321
|
-
message: `rig: complete task ${
|
|
8022
|
+
message: `rig: complete task ${taskId3}`,
|
|
7322
8023
|
command: gitCommand
|
|
7323
8024
|
});
|
|
7324
8025
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7326,13 +8027,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7326
8027
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7327
8028
|
const pr = await prAutomation({
|
|
7328
8029
|
projectRoot: workspace,
|
|
7329
|
-
taskId:
|
|
8030
|
+
taskId: taskId3,
|
|
7330
8031
|
runId: input.runId,
|
|
7331
8032
|
branch,
|
|
7332
8033
|
config,
|
|
7333
8034
|
sourceTask: input.sourceTask,
|
|
7334
8035
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
8036
|
+
artifactRoot: resolve20(input.projectRoot, "artifacts", taskId3),
|
|
7335
8037
|
command: ghCommand,
|
|
8038
|
+
gitCommand,
|
|
7336
8039
|
steerPi,
|
|
7337
8040
|
lifecycle: {
|
|
7338
8041
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7340,7 +8043,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7340
8043
|
try {
|
|
7341
8044
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7342
8045
|
await updateTaskSource(input.projectRoot, {
|
|
7343
|
-
taskId:
|
|
8046
|
+
taskId: taskId3,
|
|
7344
8047
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7345
8048
|
update: {
|
|
7346
8049
|
status: "under_review",
|
|
@@ -7370,7 +8073,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7370
8073
|
`), "reviewing", "error");
|
|
7371
8074
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7372
8075
|
await updateTaskSource(input.projectRoot, {
|
|
7373
|
-
taskId:
|
|
8076
|
+
taskId: taskId3,
|
|
7374
8077
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7375
8078
|
update: {
|
|
7376
8079
|
status: "ci_fixing",
|
|
@@ -7381,8 +8084,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7381
8084
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7382
8085
|
})
|
|
7383
8086
|
}
|
|
7384
|
-
}).catch(() => {
|
|
7385
|
-
return;
|
|
7386
8087
|
});
|
|
7387
8088
|
}
|
|
7388
8089
|
},
|
|
@@ -7390,7 +8091,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7390
8091
|
stage("Merge", prUrl, "running", "tool");
|
|
7391
8092
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7392
8093
|
await updateTaskSource(input.projectRoot, {
|
|
7393
|
-
taskId:
|
|
8094
|
+
taskId: taskId3,
|
|
7394
8095
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7395
8096
|
update: {
|
|
7396
8097
|
status: "merging",
|
|
@@ -7401,8 +8102,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7401
8102
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7402
8103
|
})
|
|
7403
8104
|
}
|
|
7404
|
-
}).catch(() => {
|
|
7405
|
-
return;
|
|
7406
8105
|
});
|
|
7407
8106
|
}
|
|
7408
8107
|
},
|
|
@@ -7419,7 +8118,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7419
8118
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7420
8119
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7421
8120
|
await updateTaskSource(input.projectRoot, {
|
|
7422
|
-
taskId:
|
|
8121
|
+
taskId: taskId3,
|
|
7423
8122
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7424
8123
|
update: {
|
|
7425
8124
|
status: "needs_attention",
|
|
@@ -7431,8 +8130,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7431
8130
|
errorText: detail
|
|
7432
8131
|
})
|
|
7433
8132
|
}
|
|
7434
|
-
}).catch(() => {
|
|
7435
|
-
|
|
8133
|
+
}).catch((error) => {
|
|
8134
|
+
try {
|
|
8135
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
8136
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
8137
|
+
title: "Task source needs-attention update failed",
|
|
8138
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
8139
|
+
tone: "error",
|
|
8140
|
+
status: "needs_attention",
|
|
8141
|
+
createdAt: new Date().toISOString()
|
|
8142
|
+
});
|
|
8143
|
+
} catch {}
|
|
7436
8144
|
});
|
|
7437
8145
|
}
|
|
7438
8146
|
return { status: "needs_attention", pr };
|
|
@@ -7440,7 +8148,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7440
8148
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7441
8149
|
await closeIssueAfterMergedPr({
|
|
7442
8150
|
projectRoot: input.projectRoot,
|
|
7443
|
-
taskId:
|
|
8151
|
+
taskId: taskId3,
|
|
7444
8152
|
runId: input.runId,
|
|
7445
8153
|
prUrl: pr.prUrl,
|
|
7446
8154
|
sourceTask: input.sourceTask,
|
|
@@ -7450,12 +8158,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7450
8158
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7451
8159
|
return { status: "completed", pr };
|
|
7452
8160
|
}
|
|
7453
|
-
function summarizeValidationFailure(projectRoot,
|
|
7454
|
-
if (!
|
|
8161
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
8162
|
+
if (!taskId3) {
|
|
7455
8163
|
return null;
|
|
7456
8164
|
}
|
|
7457
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7458
|
-
const summary = readJsonFile3(
|
|
8165
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
8166
|
+
const summary = readJsonFile3(resolve20(artifactDir, "validation-summary.json"), null);
|
|
7459
8167
|
if (!summary || summary.status !== "fail") {
|
|
7460
8168
|
continue;
|
|
7461
8169
|
}
|
|
@@ -7536,9 +8244,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7536
8244
|
if (!input.taskId || !input.workspaceDir) {
|
|
7537
8245
|
return { accepted: false, reason: null };
|
|
7538
8246
|
}
|
|
7539
|
-
const artifactDir =
|
|
7540
|
-
const reviewStatusPath =
|
|
7541
|
-
const taskResultPath =
|
|
8247
|
+
const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
|
|
8248
|
+
const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
|
|
8249
|
+
const taskResultPath = resolve20(artifactDir, "task-result.json");
|
|
7542
8250
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7543
8251
|
if (reviewStatus !== "APPROVED") {
|
|
7544
8252
|
return { accepted: false, reason: null };
|
|
@@ -7575,12 +8283,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7575
8283
|
if (!input.taskId || !input.workspaceDir) {
|
|
7576
8284
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7577
8285
|
}
|
|
7578
|
-
const artifactDir =
|
|
7579
|
-
const reviewStatePath =
|
|
7580
|
-
const reviewFeedbackPath =
|
|
7581
|
-
const reviewStatusPath =
|
|
7582
|
-
const failedApproachesPath =
|
|
7583
|
-
const validationSummaryPath =
|
|
8286
|
+
const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
|
|
8287
|
+
const reviewStatePath = resolve20(artifactDir, "review-state.json");
|
|
8288
|
+
const reviewFeedbackPath = resolve20(artifactDir, "review-feedback.md");
|
|
8289
|
+
const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
|
|
8290
|
+
const failedApproachesPath = resolve20(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
8291
|
+
const validationSummaryPath = resolve20(artifactDir, "validation-summary.json");
|
|
7584
8292
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7585
8293
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7586
8294
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7634,12 +8342,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7634
8342
|
}
|
|
7635
8343
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7636
8344
|
}
|
|
8345
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8346
|
+
let nextAssistantText = input.assistantText;
|
|
8347
|
+
if (input.record.type === "message_update") {
|
|
8348
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8349
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8350
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8351
|
+
}
|
|
8352
|
+
} else if (input.record.type === "stream_event") {
|
|
8353
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8354
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8355
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8356
|
+
nextAssistantText += delta.text;
|
|
8357
|
+
}
|
|
8358
|
+
} else if (input.record.type === "assistant") {
|
|
8359
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8360
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8361
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8362
|
+
if (fullText.length > nextAssistantText.length)
|
|
8363
|
+
nextAssistantText = fullText;
|
|
8364
|
+
}
|
|
8365
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8366
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8367
|
+
id: input.messageId,
|
|
8368
|
+
type: "assistant_message",
|
|
8369
|
+
text: nextAssistantText,
|
|
8370
|
+
state: "streaming",
|
|
8371
|
+
createdAt: new Date().toISOString()
|
|
8372
|
+
});
|
|
8373
|
+
}
|
|
8374
|
+
return nextAssistantText;
|
|
8375
|
+
}
|
|
8376
|
+
function appendPiToolTimelineFromRecord(input) {
|
|
8377
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
8378
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
8379
|
+
return false;
|
|
8380
|
+
const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
|
|
8381
|
+
const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
|
|
8382
|
+
const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
|
|
8383
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8384
|
+
id: `tool:${toolCallId}:${type}`,
|
|
8385
|
+
type,
|
|
8386
|
+
toolName,
|
|
8387
|
+
status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
|
|
8388
|
+
createdAt: new Date().toISOString()
|
|
8389
|
+
});
|
|
8390
|
+
return true;
|
|
8391
|
+
}
|
|
8392
|
+
function isNonRenderablePiProtocolRecord(record) {
|
|
8393
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
8394
|
+
return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
|
|
8395
|
+
}
|
|
8396
|
+
function appendToolTimelineFromLog(input) {
|
|
8397
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8398
|
+
if (title !== "Tool activity")
|
|
8399
|
+
return;
|
|
8400
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8401
|
+
const toolName = typeof payload.toolName === "string" && payload.toolName.trim() ? payload.toolName.trim() : typeof payload.tool_name === "string" && payload.tool_name.trim() ? payload.tool_name.trim() : null;
|
|
8402
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8403
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8404
|
+
id: `tool:${logId}`,
|
|
8405
|
+
type: "tool_execution_update",
|
|
8406
|
+
toolName,
|
|
8407
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8408
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8409
|
+
payload,
|
|
8410
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8411
|
+
});
|
|
8412
|
+
}
|
|
7637
8413
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7638
|
-
if (!
|
|
8414
|
+
if (!existsSync11(reviewStatusPath)) {
|
|
7639
8415
|
return null;
|
|
7640
8416
|
}
|
|
7641
8417
|
try {
|
|
7642
|
-
const status =
|
|
8418
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7643
8419
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7644
8420
|
} catch {
|
|
7645
8421
|
return null;
|
|
@@ -7657,14 +8433,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7657
8433
|
function runSourceTaskIdentity(sourceTask) {
|
|
7658
8434
|
return sourceTask;
|
|
7659
8435
|
}
|
|
7660
|
-
|
|
7661
|
-
if (!
|
|
8436
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8437
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8438
|
+
return null;
|
|
8439
|
+
const record = sourceTask;
|
|
8440
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8441
|
+
if (direct?.trim())
|
|
8442
|
+
return direct.trim();
|
|
8443
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8444
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8445
|
+
}
|
|
8446
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8447
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8448
|
+
id: input.taskId,
|
|
8449
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8450
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8451
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8452
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8453
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8454
|
+
});
|
|
8455
|
+
if (serverResult.ok === false) {
|
|
8456
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8457
|
+
}
|
|
8458
|
+
return {
|
|
8459
|
+
updated: serverResult.ok !== false,
|
|
8460
|
+
taskId: input.taskId,
|
|
8461
|
+
status: input.update.status,
|
|
8462
|
+
source: "server",
|
|
8463
|
+
sourceKind: "server",
|
|
8464
|
+
projectSync: serverResult.projectSync
|
|
8465
|
+
};
|
|
8466
|
+
};
|
|
8467
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8468
|
+
if (!taskId3)
|
|
7662
8469
|
return;
|
|
7663
8470
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7664
8471
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7665
8472
|
return;
|
|
7666
8473
|
const result = await updateTaskSource(projectRoot, {
|
|
7667
|
-
taskId:
|
|
8474
|
+
taskId: taskId3,
|
|
7668
8475
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7669
8476
|
update: {
|
|
7670
8477
|
status,
|
|
@@ -7683,14 +8490,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7683
8490
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7684
8491
|
}
|
|
7685
8492
|
}
|
|
7686
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8493
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7687
8494
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7688
8495
|
const raw = run?.sourceTask;
|
|
7689
8496
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7690
8497
|
return null;
|
|
7691
8498
|
}
|
|
7692
8499
|
const record = raw;
|
|
7693
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8500
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7694
8501
|
if (!id) {
|
|
7695
8502
|
return null;
|
|
7696
8503
|
}
|
|
@@ -7722,6 +8529,9 @@ function stringArrayField(record, key) {
|
|
|
7722
8529
|
}
|
|
7723
8530
|
async function executeRigOwnedTaskRun(context, input) {
|
|
7724
8531
|
const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
|
|
8532
|
+
const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
|
|
8533
|
+
const resumeMode = process.env.RIG_RUN_RESUME === "1";
|
|
8534
|
+
const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
|
|
7725
8535
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
7726
8536
|
let prompt = buildRunPrompt({
|
|
7727
8537
|
projectRoot: context.projectRoot,
|
|
@@ -7777,14 +8587,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7777
8587
|
taskId: runtimeTaskId,
|
|
7778
8588
|
createdAt: startedAt,
|
|
7779
8589
|
runtimeAdapter: input.runtimeAdapter,
|
|
7780
|
-
status: "created"
|
|
8590
|
+
status: resumeMode ? "preparing" : "created"
|
|
7781
8591
|
});
|
|
7782
8592
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
7783
8593
|
status: "preparing",
|
|
7784
8594
|
startedAt,
|
|
7785
8595
|
completedAt: null,
|
|
7786
8596
|
errorText: null,
|
|
7787
|
-
artifactRoot: null,
|
|
8597
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
7788
8598
|
runtimeAdapter: input.runtimeAdapter,
|
|
7789
8599
|
runtimeMode: input.runtimeMode,
|
|
7790
8600
|
interactionMode: input.interactionMode,
|
|
@@ -7800,9 +8610,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7800
8610
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
7801
8611
|
});
|
|
7802
8612
|
appendRunLog(context.projectRoot, input.runId, {
|
|
7803
|
-
id: `log:${input.runId}:start`,
|
|
7804
|
-
title: "Rig task run started",
|
|
7805
|
-
detail: input.taskId ?? input.title ?? runtimeTaskId,
|
|
8613
|
+
id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
|
|
8614
|
+
title: resumeMode ? "Rig task run resumed" : "Rig task run started",
|
|
8615
|
+
detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
|
|
7806
8616
|
tone: "info",
|
|
7807
8617
|
status: "preparing",
|
|
7808
8618
|
createdAt: startedAt
|
|
@@ -7823,15 +8633,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7823
8633
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
7824
8634
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
7825
8635
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
7826
|
-
const planningArtifactPath =
|
|
8636
|
+
const planningArtifactPath = resolve20("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
7827
8637
|
const persistedPlanning = {
|
|
7828
8638
|
...planningClassification,
|
|
7829
8639
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
7830
8640
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
7831
8641
|
classifiedAt: new Date().toISOString()
|
|
7832
8642
|
};
|
|
7833
|
-
mkdirSync7(
|
|
7834
|
-
writeFileSync6(
|
|
8643
|
+
mkdirSync7(resolve20(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8644
|
+
writeFileSync6(resolve20(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
7835
8645
|
`, "utf8");
|
|
7836
8646
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
7837
8647
|
prompt = `${prompt}
|
|
@@ -7880,11 +8690,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
7880
8690
|
let reviewAction;
|
|
7881
8691
|
let verificationStarted = false;
|
|
7882
8692
|
let reviewStarted = false;
|
|
7883
|
-
let latestRuntimeWorkspace = null;
|
|
7884
|
-
let latestSessionDir = null;
|
|
7885
|
-
let latestLogsDir = null;
|
|
8693
|
+
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
8694
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve20(existingRunRecord.sessionPath, "..") : null;
|
|
8695
|
+
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
7886
8696
|
let latestProviderCommand = null;
|
|
7887
|
-
let latestRuntimeBranch = null;
|
|
8697
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
7888
8698
|
let snapshotSidecarPromise = null;
|
|
7889
8699
|
let dirtyBaselineApplied = false;
|
|
7890
8700
|
const childEnv = {
|
|
@@ -7902,7 +8712,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
7902
8712
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
7903
8713
|
RIG_SERVER_RUN_ID: input.runId
|
|
7904
8714
|
},
|
|
7905
|
-
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
|
|
8715
|
+
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
|
|
8716
|
+
...resumeMode ? {
|
|
8717
|
+
RIG_RUN_RESUME: "1",
|
|
8718
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
8719
|
+
} : {}
|
|
7906
8720
|
};
|
|
7907
8721
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
7908
8722
|
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
@@ -7960,10 +8774,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
7960
8774
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
7961
8775
|
status: "running",
|
|
7962
8776
|
worktreePath: latestRuntimeWorkspace,
|
|
7963
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8777
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve20(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
7964
8778
|
logRoot: latestLogsDir,
|
|
7965
|
-
sessionPath: latestSessionDir ?
|
|
7966
|
-
sessionLogPath: latestLogsDir ?
|
|
8779
|
+
sessionPath: latestSessionDir ? resolve20(latestSessionDir, "session.json") : null,
|
|
8780
|
+
sessionLogPath: latestLogsDir ? resolve20(latestLogsDir, "agent-stdout.log") : null,
|
|
7967
8781
|
branch: runtimeId
|
|
7968
8782
|
});
|
|
7969
8783
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -7971,7 +8785,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
7971
8785
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
7972
8786
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
7973
8787
|
if (readyFile) {
|
|
7974
|
-
mkdirSync7(
|
|
8788
|
+
mkdirSync7(resolve20(readyFile, ".."), { recursive: true });
|
|
7975
8789
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
7976
8790
|
`, "utf8");
|
|
7977
8791
|
}
|
|
@@ -8104,6 +8918,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8104
8918
|
try {
|
|
8105
8919
|
const record = JSON.parse(trimmed);
|
|
8106
8920
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
8921
|
+
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
8922
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8923
|
+
return;
|
|
8924
|
+
}
|
|
8107
8925
|
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
8108
8926
|
runId: input.runId,
|
|
8109
8927
|
record,
|
|
@@ -8120,7 +8938,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8120
8938
|
if (providerLogs.length > 0) {
|
|
8121
8939
|
for (const providerLog of providerLogs) {
|
|
8122
8940
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
8941
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8123
8942
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
8943
|
+
if (providerLog.title === "Tool activity")
|
|
8944
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8124
8945
|
}
|
|
8125
8946
|
}
|
|
8126
8947
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8172,6 +8993,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8172
8993
|
return;
|
|
8173
8994
|
}
|
|
8174
8995
|
}
|
|
8996
|
+
if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
|
|
8997
|
+
return;
|
|
8998
|
+
}
|
|
8175
8999
|
if (record.type === "assistant") {
|
|
8176
9000
|
const message2 = record.message && typeof record.message === "object" ? record.message : record;
|
|
8177
9001
|
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
@@ -8208,9 +9032,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8208
9032
|
let reviewFailureDetail = null;
|
|
8209
9033
|
const stderrLines = [];
|
|
8210
9034
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
8211
|
-
|
|
9035
|
+
if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
|
|
9036
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9037
|
+
id: `log:${input.runId}:resume-closeout-phase`,
|
|
9038
|
+
title: "Resume continuing from closeout phase",
|
|
9039
|
+
detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
|
|
9040
|
+
tone: "info",
|
|
9041
|
+
status: resumePreviousStatus,
|
|
9042
|
+
createdAt: new Date().toISOString()
|
|
9043
|
+
});
|
|
9044
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
|
|
9045
|
+
exit = { code: 0, signal: null };
|
|
9046
|
+
} else if (resumeMode && latestRuntimeWorkspace) {
|
|
9047
|
+
const acceptedArtifactState = readTaskRunAcceptedArtifactState({
|
|
9048
|
+
taskId: input.taskId ?? runtimeTaskId,
|
|
9049
|
+
workspaceDir: latestRuntimeWorkspace
|
|
9050
|
+
});
|
|
9051
|
+
if (acceptedArtifactState.accepted) {
|
|
9052
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9053
|
+
id: `log:${input.runId}:resume-accepted-artifacts`,
|
|
9054
|
+
title: "Resume found accepted artifacts; continuing closeout",
|
|
9055
|
+
detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
|
|
9056
|
+
tone: "info",
|
|
9057
|
+
status: "validating",
|
|
9058
|
+
createdAt: new Date().toISOString()
|
|
9059
|
+
});
|
|
9060
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
|
|
9061
|
+
exit = { code: 0, signal: null };
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
|
|
8212
9065
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
8213
|
-
const child =
|
|
9066
|
+
const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
8214
9067
|
cwd: context.projectRoot,
|
|
8215
9068
|
env: childEnv,
|
|
8216
9069
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8249,7 +9102,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8249
9102
|
let acceptedArtifactObservedAt = null;
|
|
8250
9103
|
let acceptedArtifactPollTimer = null;
|
|
8251
9104
|
let acceptedArtifactKillTimer = null;
|
|
8252
|
-
const attemptExit = await new Promise((
|
|
9105
|
+
const attemptExit = await new Promise((resolve21) => {
|
|
8253
9106
|
let settled = false;
|
|
8254
9107
|
const settle = (result) => {
|
|
8255
9108
|
if (settled)
|
|
@@ -8257,7 +9110,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8257
9110
|
settled = true;
|
|
8258
9111
|
if (acceptedArtifactPollTimer)
|
|
8259
9112
|
clearInterval(acceptedArtifactPollTimer);
|
|
8260
|
-
|
|
9113
|
+
resolve21(result);
|
|
8261
9114
|
};
|
|
8262
9115
|
const pollAcceptedArtifacts = () => {
|
|
8263
9116
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8330,7 +9183,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8330
9183
|
});
|
|
8331
9184
|
for (const pendingLog of pendingLogs) {
|
|
8332
9185
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9186
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8333
9187
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9188
|
+
if (pendingLog.title === "Tool activity")
|
|
9189
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8334
9190
|
}
|
|
8335
9191
|
process.off("SIGTERM", forwardSigterm);
|
|
8336
9192
|
if (attemptExit.error) {
|
|
@@ -8456,8 +9312,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8456
9312
|
}
|
|
8457
9313
|
if (planningClassification.planningRequired) {
|
|
8458
9314
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8459
|
-
const expectedPlanPath =
|
|
8460
|
-
if (!
|
|
9315
|
+
const expectedPlanPath = resolve20(planWorkspace, planningArtifactPath);
|
|
9316
|
+
if (!existsSync11(expectedPlanPath)) {
|
|
8461
9317
|
const failedAt = new Date().toISOString();
|
|
8462
9318
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8463
9319
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8477,6 +9333,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8477
9333
|
throw new CliError2(failureDetail, 1);
|
|
8478
9334
|
}
|
|
8479
9335
|
}
|
|
9336
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
9337
|
+
appendPiStageLog({
|
|
9338
|
+
projectRoot: context.projectRoot,
|
|
9339
|
+
runId: input.runId,
|
|
9340
|
+
stage: "Validate",
|
|
9341
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
9342
|
+
status: "completed"
|
|
9343
|
+
});
|
|
9344
|
+
if (verificationAction && !reviewStarted) {
|
|
9345
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
9346
|
+
}
|
|
9347
|
+
if (!reviewAction) {
|
|
9348
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
9349
|
+
}
|
|
9350
|
+
if (reviewAction) {
|
|
9351
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
9352
|
+
}
|
|
9353
|
+
const requestedAt = new Date().toISOString();
|
|
9354
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
9355
|
+
status: "reviewing",
|
|
9356
|
+
completedAt: null,
|
|
9357
|
+
errorText: null,
|
|
9358
|
+
serverCloseout: {
|
|
9359
|
+
status: "pending",
|
|
9360
|
+
phase: "queued",
|
|
9361
|
+
requestedAt,
|
|
9362
|
+
updatedAt: requestedAt,
|
|
9363
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
9364
|
+
branch: latestRuntimeBranch,
|
|
9365
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
9366
|
+
}
|
|
9367
|
+
});
|
|
9368
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9369
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9370
|
+
title: "Server-owned closeout requested",
|
|
9371
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9372
|
+
tone: "info",
|
|
9373
|
+
status: "reviewing",
|
|
9374
|
+
createdAt: requestedAt,
|
|
9375
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9376
|
+
});
|
|
9377
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9378
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9379
|
+
await context.emitEvent("command.finished", {
|
|
9380
|
+
command: [
|
|
9381
|
+
"rig",
|
|
9382
|
+
"server",
|
|
9383
|
+
"task-run",
|
|
9384
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9385
|
+
...input.title ? ["--title", input.title] : []
|
|
9386
|
+
],
|
|
9387
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9388
|
+
exitCode: 0,
|
|
9389
|
+
durationMs: 0,
|
|
9390
|
+
startedAt,
|
|
9391
|
+
finishedAt: requestedAt
|
|
9392
|
+
});
|
|
9393
|
+
return;
|
|
9394
|
+
}
|
|
8480
9395
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8481
9396
|
appendPiStageLog({
|
|
8482
9397
|
projectRoot: context.projectRoot,
|
|
@@ -8495,7 +9410,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8495
9410
|
});
|
|
8496
9411
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
|
|
8497
9412
|
const feedbackCommand = buildHostAgentCommand(message2);
|
|
8498
|
-
const child =
|
|
9413
|
+
const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
|
|
8499
9414
|
cwd: latestRuntimeWorkspace ?? context.projectRoot,
|
|
8500
9415
|
env: childEnv,
|
|
8501
9416
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8504,11 +9419,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8504
9419
|
child.stdin.write(message2);
|
|
8505
9420
|
}
|
|
8506
9421
|
child.stdin.end();
|
|
9422
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9423
|
+
let feedbackAssistantText = "";
|
|
9424
|
+
const feedbackPendingToolUses = new Map;
|
|
8507
9425
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8508
9426
|
stdout.on("line", (line) => {
|
|
8509
9427
|
const trimmed = line.trim();
|
|
8510
9428
|
if (!trimmed)
|
|
8511
9429
|
return;
|
|
9430
|
+
try {
|
|
9431
|
+
const record = JSON.parse(trimmed);
|
|
9432
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9433
|
+
runId: input.runId,
|
|
9434
|
+
record,
|
|
9435
|
+
createdAtFallback: new Date().toISOString(),
|
|
9436
|
+
status: "reviewing",
|
|
9437
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9438
|
+
});
|
|
9439
|
+
for (const providerLog of providerLogs) {
|
|
9440
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9441
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9442
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9443
|
+
if (providerLog.title === "Tool activity")
|
|
9444
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9445
|
+
}
|
|
9446
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9447
|
+
projectRoot: context.projectRoot,
|
|
9448
|
+
runId: input.runId,
|
|
9449
|
+
messageId: feedbackAssistantMessageId,
|
|
9450
|
+
record,
|
|
9451
|
+
assistantText: feedbackAssistantText
|
|
9452
|
+
});
|
|
9453
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9454
|
+
if (hadAssistantDelta) {
|
|
9455
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9456
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9457
|
+
}
|
|
9458
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9459
|
+
return;
|
|
9460
|
+
} catch {}
|
|
8512
9461
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8513
9462
|
id: nextRunLogId(),
|
|
8514
9463
|
title: "Pi PR feedback fix output",
|
|
@@ -8534,10 +9483,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8534
9483
|
});
|
|
8535
9484
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8536
9485
|
});
|
|
8537
|
-
const exitCode = await new Promise((
|
|
8538
|
-
child.once("error", () =>
|
|
8539
|
-
child.once("close", (code) =>
|
|
9486
|
+
const exitCode = await new Promise((resolve21) => {
|
|
9487
|
+
child.once("error", () => resolve21(1));
|
|
9488
|
+
child.once("close", (code) => resolve21(code ?? 1));
|
|
8540
9489
|
});
|
|
9490
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9491
|
+
runId: input.runId,
|
|
9492
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9493
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9494
|
+
})) {
|
|
9495
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9496
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9497
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9498
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9499
|
+
}
|
|
9500
|
+
if (feedbackAssistantText.trim()) {
|
|
9501
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9502
|
+
id: feedbackAssistantMessageId,
|
|
9503
|
+
type: "assistant_message",
|
|
9504
|
+
text: feedbackAssistantText,
|
|
9505
|
+
state: "completed",
|
|
9506
|
+
createdAt: new Date().toISOString(),
|
|
9507
|
+
completedAt: new Date().toISOString()
|
|
9508
|
+
});
|
|
9509
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9510
|
+
}
|
|
8541
9511
|
if (exitCode !== 0) {
|
|
8542
9512
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8543
9513
|
}
|
|
@@ -8655,8 +9625,8 @@ async function executeTest(context, args) {
|
|
|
8655
9625
|
}
|
|
8656
9626
|
|
|
8657
9627
|
// packages/cli/src/commands/setup.ts
|
|
8658
|
-
import { existsSync as
|
|
8659
|
-
import { resolve as
|
|
9628
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9629
|
+
import { resolve as resolve21 } from "path";
|
|
8660
9630
|
import { createPluginHost } from "@rig/core";
|
|
8661
9631
|
import {
|
|
8662
9632
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -8719,8 +9689,8 @@ function runSetupInit(projectRoot) {
|
|
|
8719
9689
|
mkdirSync8(stateDir, { recursive: true });
|
|
8720
9690
|
mkdirSync8(logsDir, { recursive: true });
|
|
8721
9691
|
mkdirSync8(artifactsDir, { recursive: true });
|
|
8722
|
-
const failuresPath =
|
|
8723
|
-
if (!
|
|
9692
|
+
const failuresPath = resolve21(stateDir, "failed_approaches.md");
|
|
9693
|
+
if (!existsSync12(failuresPath)) {
|
|
8724
9694
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
8725
9695
|
|
|
8726
9696
|
`, "utf-8");
|
|
@@ -8738,18 +9708,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
8738
9708
|
}
|
|
8739
9709
|
async function runSetupPreflight(projectRoot) {
|
|
8740
9710
|
await runSetupCheck(projectRoot);
|
|
8741
|
-
const validationRoot =
|
|
8742
|
-
if (
|
|
9711
|
+
const validationRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9712
|
+
if (existsSync12(validationRoot)) {
|
|
8743
9713
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
8744
9714
|
for (const validator of validators) {
|
|
8745
|
-
const script =
|
|
8746
|
-
if (
|
|
9715
|
+
const script = resolve21(validationRoot, validator.name, "validate.sh");
|
|
9716
|
+
if (existsSync12(script)) {
|
|
8747
9717
|
console.log(`OK: validator script ${script}`);
|
|
8748
9718
|
}
|
|
8749
9719
|
}
|
|
8750
9720
|
}
|
|
8751
|
-
const hooksRoot =
|
|
8752
|
-
if (
|
|
9721
|
+
const hooksRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9722
|
+
if (existsSync12(hooksRoot)) {
|
|
8753
9723
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
8754
9724
|
for (const hook of hooks) {
|
|
8755
9725
|
console.log(`OK: hook ${hook}`);
|