@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/index.js
CHANGED
|
@@ -101,12 +101,13 @@ async function runCommand(context, parts) {
|
|
|
101
101
|
const envMode = process.env.RIG_BASH_MODE;
|
|
102
102
|
const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
|
|
103
103
|
const controlledPath = `${resolve(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
|
|
104
|
-
const
|
|
104
|
+
const usesInfrastructureBinary = parts[0] === "rig-server";
|
|
105
|
+
const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
|
|
105
106
|
const commandEnv = [
|
|
106
107
|
"env",
|
|
107
108
|
`PATH=${controlledPath}`,
|
|
108
109
|
`PROJECT_RIG_ROOT=${context.projectRoot}`,
|
|
109
|
-
`BASH=${controlledBash}
|
|
110
|
+
...controlledBash ? [`BASH=${controlledBash}`] : [],
|
|
110
111
|
`RIG_BASH_MODE=${effectiveMode}`,
|
|
111
112
|
`RIG_POLICY_FILE=${resolve(context.projectRoot, "rig/policy/policy.json")}`,
|
|
112
113
|
...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
|
|
@@ -2666,17 +2667,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2666
2667
|
}
|
|
2667
2668
|
|
|
2668
2669
|
// packages/cli/src/commands/_server-client.ts
|
|
2669
|
-
import { spawnSync } from "child_process";
|
|
2670
2670
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2671
2671
|
import { resolve as resolve9 } from "path";
|
|
2672
2672
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2673
|
-
var
|
|
2673
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2674
2674
|
function cleanToken(value) {
|
|
2675
2675
|
const trimmed = value?.trim();
|
|
2676
2676
|
return trimmed ? trimmed : null;
|
|
2677
2677
|
}
|
|
2678
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2679
|
-
|
|
2678
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2679
|
+
const scopedKey = resolve9(projectRoot ?? process.cwd());
|
|
2680
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2680
2681
|
}
|
|
2681
2682
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2682
2683
|
const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2690,25 +2691,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2690
2691
|
}
|
|
2691
2692
|
}
|
|
2692
2693
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2693
|
-
|
|
2694
|
-
|
|
2694
|
+
const scopedKey = resolve9(projectRoot);
|
|
2695
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2696
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2695
2697
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2696
|
-
if (privateSession)
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
}
|
|
2700
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2701
|
-
if (envToken) {
|
|
2702
|
-
cachedGitHubBearerToken = envToken;
|
|
2703
|
-
return cachedGitHubBearerToken;
|
|
2704
|
-
}
|
|
2705
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2706
|
-
encoding: "utf8",
|
|
2707
|
-
timeout: 5000,
|
|
2708
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2709
|
-
});
|
|
2710
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2711
|
-
return cachedGitHubBearerToken;
|
|
2698
|
+
if (privateSession)
|
|
2699
|
+
return privateSession;
|
|
2700
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2712
2701
|
}
|
|
2713
2702
|
async function ensureServerForCli(projectRoot) {
|
|
2714
2703
|
try {
|
|
@@ -2846,16 +2835,36 @@ async function registerProjectViaServer(context, input) {
|
|
|
2846
2835
|
function sleep(ms) {
|
|
2847
2836
|
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
2848
2837
|
}
|
|
2838
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
2839
|
+
if (!(error instanceof Error))
|
|
2840
|
+
return false;
|
|
2841
|
+
const message = error.message.toLowerCase();
|
|
2842
|
+
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");
|
|
2843
|
+
}
|
|
2849
2844
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
2850
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2851
|
-
method: "POST",
|
|
2852
|
-
headers: { "content-type": "application/json" },
|
|
2853
|
-
body: JSON.stringify({ projectRoot })
|
|
2854
|
-
});
|
|
2855
2845
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
2856
2846
|
const pollMs = options.pollMs ?? 1000;
|
|
2857
2847
|
const deadline = Date.now() + timeoutMs;
|
|
2858
2848
|
let lastError;
|
|
2849
|
+
let switched = null;
|
|
2850
|
+
while (Date.now() < deadline) {
|
|
2851
|
+
try {
|
|
2852
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2853
|
+
method: "POST",
|
|
2854
|
+
headers: { "content-type": "application/json" },
|
|
2855
|
+
body: JSON.stringify({ projectRoot })
|
|
2856
|
+
});
|
|
2857
|
+
break;
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
lastError = error;
|
|
2860
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
2861
|
+
throw error;
|
|
2862
|
+
await sleep(pollMs);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if (!switched) {
|
|
2866
|
+
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);
|
|
2867
|
+
}
|
|
2859
2868
|
while (Date.now() < deadline) {
|
|
2860
2869
|
try {
|
|
2861
2870
|
const status = await requestServerJson(context, "/api/server/status");
|
|
@@ -2873,6 +2882,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
2873
2882
|
}
|
|
2874
2883
|
throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
|
|
2875
2884
|
}
|
|
2885
|
+
async function listRunsViaServer(context, options = {}) {
|
|
2886
|
+
const url = new URL("http://rig.local/api/runs");
|
|
2887
|
+
if (options.limit !== undefined)
|
|
2888
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2889
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2890
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
2891
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
2892
|
+
}
|
|
2876
2893
|
async function getRunDetailsViaServer(context, runId) {
|
|
2877
2894
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
|
|
2878
2895
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
@@ -2886,6 +2903,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2886
2903
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2887
2904
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2888
2905
|
}
|
|
2906
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2907
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2908
|
+
if (options.limit !== undefined)
|
|
2909
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2910
|
+
if (options.cursor)
|
|
2911
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2912
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2913
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2914
|
+
}
|
|
2915
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2916
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2917
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2918
|
+
}
|
|
2919
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2920
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2921
|
+
url.searchParams.set("owner", owner);
|
|
2922
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2923
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2924
|
+
}
|
|
2925
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2926
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2927
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2928
|
+
}
|
|
2929
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2930
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2931
|
+
method: "POST",
|
|
2932
|
+
headers: { "content-type": "application/json" },
|
|
2933
|
+
body: JSON.stringify(input)
|
|
2934
|
+
});
|
|
2935
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2936
|
+
}
|
|
2889
2937
|
async function stopRunViaServer(context, runId) {
|
|
2890
2938
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2891
2939
|
method: "POST",
|
|
@@ -2938,7 +2986,8 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
2938
2986
|
import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
|
|
2939
2987
|
import { homedir as homedir3 } from "os";
|
|
2940
2988
|
import { resolve as resolve10 } from "path";
|
|
2941
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2989
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
2990
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2942
2991
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
2943
2992
|
export { default } from '@rig/pi-rig';
|
|
2944
2993
|
`;
|
|
@@ -2966,7 +3015,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
2966
3015
|
function piListContainsPiRig(output) {
|
|
2967
3016
|
return output.split(/\r?\n/).some((line) => {
|
|
2968
3017
|
const normalized = line.trim();
|
|
2969
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
3018
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
2970
3019
|
});
|
|
2971
3020
|
}
|
|
2972
3021
|
async function safeRun(runner, command, options) {
|
|
@@ -3082,7 +3131,7 @@ async function ensureRemotePiRigInstalled(input) {
|
|
|
3082
3131
|
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
3083
3132
|
method: "POST",
|
|
3084
3133
|
headers: { "content-type": "application/json" },
|
|
3085
|
-
body: JSON.stringify({ package:
|
|
3134
|
+
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
3086
3135
|
});
|
|
3087
3136
|
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
3088
3137
|
const piOk = record.piOk === true || record.ok === true;
|
|
@@ -3165,6 +3214,9 @@ function permissionAllowsPr(payload) {
|
|
|
3165
3214
|
}
|
|
3166
3215
|
return null;
|
|
3167
3216
|
}
|
|
3217
|
+
function isNotFoundError(error) {
|
|
3218
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3219
|
+
}
|
|
3168
3220
|
function projectCheckoutReady(payload) {
|
|
3169
3221
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3170
3222
|
return null;
|
|
@@ -3197,19 +3249,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3197
3249
|
const checks = [];
|
|
3198
3250
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
3199
3251
|
const taskId = options.taskId?.trim() || null;
|
|
3252
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3253
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3254
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3255
|
+
let legacyServerCompatibility = false;
|
|
3200
3256
|
try {
|
|
3201
3257
|
await request("/api/server/status");
|
|
3202
3258
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3203
3259
|
} catch (error) {
|
|
3204
|
-
|
|
3260
|
+
if (isNotFoundError(error)) {
|
|
3261
|
+
try {
|
|
3262
|
+
await request("/health");
|
|
3263
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3264
|
+
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"));
|
|
3265
|
+
} catch (healthError) {
|
|
3266
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3267
|
+
}
|
|
3268
|
+
} else {
|
|
3269
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3270
|
+
}
|
|
3205
3271
|
}
|
|
3206
3272
|
const repo = readRepoConnection(context.projectRoot);
|
|
3207
|
-
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>`."));
|
|
3273
|
+
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>`."));
|
|
3208
3274
|
try {
|
|
3209
3275
|
const auth = await request("/api/github/auth/status");
|
|
3210
|
-
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>`."));
|
|
3276
|
+
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>`."));
|
|
3211
3277
|
} catch (error) {
|
|
3212
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3278
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3213
3279
|
}
|
|
3214
3280
|
try {
|
|
3215
3281
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3237,9 +3303,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3237
3303
|
try {
|
|
3238
3304
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3239
3305
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3240
|
-
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."));
|
|
3306
|
+
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."));
|
|
3241
3307
|
} catch (error) {
|
|
3242
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3308
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3243
3309
|
}
|
|
3244
3310
|
try {
|
|
3245
3311
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -3442,6 +3508,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
|
|
|
3442
3508
|
const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
|
|
3443
3509
|
const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
|
|
3444
3510
|
const next = {
|
|
3511
|
+
...existing ?? {},
|
|
3445
3512
|
runId: input.runId,
|
|
3446
3513
|
projectRoot,
|
|
3447
3514
|
workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
|
|
@@ -4150,9 +4217,10 @@ async function executeInbox(context, args) {
|
|
|
4150
4217
|
|
|
4151
4218
|
// packages/cli/src/commands/init.ts
|
|
4152
4219
|
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4153
|
-
import { spawnSync
|
|
4220
|
+
import { spawnSync } from "child_process";
|
|
4154
4221
|
import { resolve as resolve17 } from "path";
|
|
4155
4222
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4223
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
4156
4224
|
|
|
4157
4225
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
4158
4226
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4466,6 +4534,7 @@ function countDoctorFailures(checks) {
|
|
|
4466
4534
|
|
|
4467
4535
|
// packages/cli/src/commands/init.ts
|
|
4468
4536
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
4537
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
4469
4538
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
4470
4539
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
4471
4540
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -4476,7 +4545,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4476
4545
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4477
4546
|
}
|
|
4478
4547
|
function detectOriginRepoSlug(projectRoot) {
|
|
4479
|
-
const result =
|
|
4548
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4480
4549
|
if (result.status !== 0)
|
|
4481
4550
|
return null;
|
|
4482
4551
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4532,11 +4601,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4532
4601
|
return source;
|
|
4533
4602
|
const projectId = JSON.stringify(options.githubProject);
|
|
4534
4603
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4604
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4605
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4606
|
+
`)},` : "";
|
|
4535
4607
|
return source.replace(` projects: { enabled: false },`, [
|
|
4536
4608
|
` projects: {`,
|
|
4537
4609
|
` enabled: true,`,
|
|
4538
4610
|
` projectId: ${projectId},`,
|
|
4539
|
-
` statusFieldId: ${statusFieldId}
|
|
4611
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4540
4612
|
` },`
|
|
4541
4613
|
].join(`
|
|
4542
4614
|
`));
|
|
@@ -4557,21 +4629,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4557
4629
|
}
|
|
4558
4630
|
}
|
|
4559
4631
|
function detectGhLogin() {
|
|
4560
|
-
const result =
|
|
4632
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4561
4633
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4562
4634
|
}
|
|
4563
4635
|
function readGhAuthToken() {
|
|
4564
|
-
const result =
|
|
4636
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4565
4637
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4566
4638
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4567
4639
|
}
|
|
4568
4640
|
return result.stdout.trim();
|
|
4569
4641
|
}
|
|
4642
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
4643
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
4644
|
+
encoding: "utf8",
|
|
4645
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4646
|
+
});
|
|
4647
|
+
if (result.status !== 0)
|
|
4648
|
+
return null;
|
|
4649
|
+
try {
|
|
4650
|
+
return readGhAuthToken();
|
|
4651
|
+
} catch {
|
|
4652
|
+
return null;
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4570
4655
|
async function loadClackPrompts() {
|
|
4571
4656
|
return await import("@clack/prompts");
|
|
4572
4657
|
}
|
|
4658
|
+
function clackTextOptions(options) {
|
|
4659
|
+
return {
|
|
4660
|
+
message: options.message,
|
|
4661
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4662
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4663
|
+
};
|
|
4664
|
+
}
|
|
4573
4665
|
async function promptRequiredText(prompts, options) {
|
|
4574
|
-
const value = await prompts.text(options);
|
|
4666
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4575
4667
|
if (prompts.isCancel(value))
|
|
4576
4668
|
throw new CliError2("Init cancelled.", 1);
|
|
4577
4669
|
const text2 = String(value ?? "").trim();
|
|
@@ -4580,7 +4672,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4580
4672
|
return text2;
|
|
4581
4673
|
}
|
|
4582
4674
|
async function promptOptionalText(prompts, options) {
|
|
4583
|
-
const value = await prompts.text(options);
|
|
4675
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4584
4676
|
if (prompts.isCancel(value))
|
|
4585
4677
|
throw new CliError2("Init cancelled.", 1);
|
|
4586
4678
|
return String(value ?? "").trim();
|
|
@@ -4591,6 +4683,164 @@ async function promptSelect(prompts, options) {
|
|
|
4591
4683
|
throw new CliError2("Init cancelled.", 1);
|
|
4592
4684
|
return String(value);
|
|
4593
4685
|
}
|
|
4686
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4687
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4688
|
+
}
|
|
4689
|
+
function recordArray(value, key) {
|
|
4690
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4691
|
+
return [];
|
|
4692
|
+
const raw = value[key];
|
|
4693
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4694
|
+
}
|
|
4695
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4696
|
+
if (token?.trim()) {
|
|
4697
|
+
try {
|
|
4698
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4699
|
+
} catch (directError) {
|
|
4700
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4701
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4702
|
+
return serverPayload;
|
|
4703
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4707
|
+
}
|
|
4708
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4709
|
+
if (token?.trim()) {
|
|
4710
|
+
try {
|
|
4711
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4712
|
+
} catch (directError) {
|
|
4713
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4714
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4715
|
+
return serverPayload;
|
|
4716
|
+
}
|
|
4717
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4721
|
+
}
|
|
4722
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4723
|
+
running: "Running/In progress",
|
|
4724
|
+
prOpen: "PR open/review",
|
|
4725
|
+
ciFixing: "CI/review fixing",
|
|
4726
|
+
merging: "Merging",
|
|
4727
|
+
done: "Done",
|
|
4728
|
+
needsAttention: "Needs attention"
|
|
4729
|
+
};
|
|
4730
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4731
|
+
running: "In Progress",
|
|
4732
|
+
prOpen: "In Review",
|
|
4733
|
+
ciFixing: "In Review",
|
|
4734
|
+
merging: "Merging",
|
|
4735
|
+
done: "Done",
|
|
4736
|
+
needsAttention: "Needs Attention"
|
|
4737
|
+
};
|
|
4738
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4739
|
+
const statuses = {};
|
|
4740
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4741
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4742
|
+
const value = await promptOptionalText(prompts, {
|
|
4743
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4744
|
+
placeholder: defaultLabel
|
|
4745
|
+
});
|
|
4746
|
+
statuses[key] = value || defaultLabel;
|
|
4747
|
+
}
|
|
4748
|
+
return statuses;
|
|
4749
|
+
}
|
|
4750
|
+
function projectScopeError(value) {
|
|
4751
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
4752
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
|
|
4753
|
+
}
|
|
4754
|
+
function optionName(option) {
|
|
4755
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
4756
|
+
}
|
|
4757
|
+
function autoProjectStatusValue(options, key, label) {
|
|
4758
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
4759
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
4760
|
+
if (!match)
|
|
4761
|
+
return null;
|
|
4762
|
+
return String(match.id ?? match.name);
|
|
4763
|
+
}
|
|
4764
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
4765
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4766
|
+
message: "GitHub Projects status sync",
|
|
4767
|
+
initialValue: "select",
|
|
4768
|
+
options: [
|
|
4769
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4770
|
+
{ value: "off", label: "Off" },
|
|
4771
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4772
|
+
]
|
|
4773
|
+
});
|
|
4774
|
+
if (projectChoice === "off")
|
|
4775
|
+
return { githubProject: "off" };
|
|
4776
|
+
if (projectChoice === "manual") {
|
|
4777
|
+
return {
|
|
4778
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4779
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4780
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4784
|
+
if (!owner)
|
|
4785
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4786
|
+
let activeToken = githubToken?.trim() || null;
|
|
4787
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4788
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
4789
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
4790
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
4791
|
+
const refreshedToken = refreshProjectToken();
|
|
4792
|
+
if (refreshedToken) {
|
|
4793
|
+
activeToken = refreshedToken;
|
|
4794
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4795
|
+
projects = recordArray(projectsPayload, "projects");
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
if (projects.length === 0) {
|
|
4799
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
4800
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
4801
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
4802
|
+
}
|
|
4803
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4804
|
+
message: "GitHub ProjectV2 project",
|
|
4805
|
+
options: [
|
|
4806
|
+
...projects.map((project) => ({
|
|
4807
|
+
value: String(project.id),
|
|
4808
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4809
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4810
|
+
})),
|
|
4811
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4812
|
+
]
|
|
4813
|
+
});
|
|
4814
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4815
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4816
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4817
|
+
const rawField = fieldPayloadRecord.field;
|
|
4818
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4819
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4820
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4821
|
+
if (options.length === 0) {
|
|
4822
|
+
return {
|
|
4823
|
+
githubProject: projectId,
|
|
4824
|
+
githubProjectStatusField: fieldId,
|
|
4825
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
4826
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
const statuses = {};
|
|
4830
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4831
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
4832
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
4833
|
+
message: `Project status option for ${label}`,
|
|
4834
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
return {
|
|
4838
|
+
githubProject: projectId,
|
|
4839
|
+
githubProjectStatusField: fieldId,
|
|
4840
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
4841
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4594
4844
|
function sleep2(ms) {
|
|
4595
4845
|
return new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
4596
4846
|
}
|
|
@@ -4604,12 +4854,29 @@ function apiSessionTokenFrom(payload) {
|
|
|
4604
4854
|
const token = payload.apiSessionToken;
|
|
4605
4855
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
4606
4856
|
}
|
|
4857
|
+
function cleanPayloadString(value) {
|
|
4858
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4859
|
+
}
|
|
4860
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
4861
|
+
if (!payload)
|
|
4862
|
+
return {};
|
|
4863
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
4864
|
+
return {
|
|
4865
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
4866
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
4867
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
4868
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
4869
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
4870
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
4871
|
+
};
|
|
4872
|
+
}
|
|
4607
4873
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
4608
4874
|
writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
4609
4875
|
authenticated: true,
|
|
4610
4876
|
source: input.source,
|
|
4611
4877
|
storedOnServer: true,
|
|
4612
4878
|
selectedRepo: input.selectedRepo,
|
|
4879
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
4613
4880
|
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
4614
4881
|
updatedAt: new Date().toISOString()
|
|
4615
4882
|
}, null, 2)}
|
|
@@ -4702,12 +4969,13 @@ async function runControlPlaneInit(context, options) {
|
|
|
4702
4969
|
if (token) {
|
|
4703
4970
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4704
4971
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4705
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4972
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4706
4973
|
if (serverKind === "remote") {
|
|
4707
4974
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4708
4975
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
4709
4976
|
selectedRepo: repo.slug,
|
|
4710
|
-
apiSessionToken
|
|
4977
|
+
apiSessionToken,
|
|
4978
|
+
authPayload: githubAuth
|
|
4711
4979
|
});
|
|
4712
4980
|
}
|
|
4713
4981
|
} else if (authMethod === "device") {
|
|
@@ -4726,9 +4994,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
4726
4994
|
if (completed) {
|
|
4727
4995
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4728
4996
|
if (apiSessionToken) {
|
|
4729
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
4997
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4730
4998
|
if (serverKind === "remote") {
|
|
4731
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
|
|
4999
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4732
5000
|
}
|
|
4733
5001
|
}
|
|
4734
5002
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
@@ -4746,19 +5014,25 @@ async function runControlPlaneInit(context, options) {
|
|
|
4746
5014
|
Object.assign(checkout, preparedCheckout);
|
|
4747
5015
|
}
|
|
4748
5016
|
}
|
|
5017
|
+
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
5018
|
+
if (serverKind === "remote" && checkoutPath && token) {
|
|
5019
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
5020
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
5021
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
5022
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
5023
|
+
}
|
|
4749
5024
|
const registered = await registerProjectViaServer(context, {
|
|
4750
5025
|
repoSlug: repo.slug,
|
|
4751
5026
|
checkout
|
|
4752
5027
|
});
|
|
4753
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
4754
5028
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4755
|
-
if (serverRootSwitch && token) {
|
|
4756
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
|
|
4757
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4758
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4759
|
-
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
|
|
4760
|
-
}
|
|
4761
5029
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
5030
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
5031
|
+
ok: false,
|
|
5032
|
+
ready: false,
|
|
5033
|
+
labelsReady: false,
|
|
5034
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5035
|
+
}));
|
|
4762
5036
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4763
5037
|
remote: true,
|
|
4764
5038
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4789,6 +5063,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4789
5063
|
githubAuth,
|
|
4790
5064
|
deviceAuth,
|
|
4791
5065
|
githubAuthWarning: remoteGhTokenWarning,
|
|
5066
|
+
labelSetup,
|
|
4792
5067
|
pi,
|
|
4793
5068
|
doctor
|
|
4794
5069
|
};
|
|
@@ -4895,12 +5170,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4895
5170
|
});
|
|
4896
5171
|
const serverChoice = await promptSelect(prompts, {
|
|
4897
5172
|
message: "Rig server",
|
|
5173
|
+
initialValue: "remote",
|
|
4898
5174
|
options: [
|
|
4899
|
-
{ value: "
|
|
4900
|
-
{ value: "
|
|
5175
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
5176
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
4901
5177
|
]
|
|
4902
5178
|
});
|
|
4903
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
5179
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
4904
5180
|
let remoteCheckout;
|
|
4905
5181
|
if (serverChoice === "remote") {
|
|
4906
5182
|
const checkout = await promptSelect(prompts, {
|
|
@@ -4932,38 +5208,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4932
5208
|
{ value: "skip", label: "Skip for now" }
|
|
4933
5209
|
]
|
|
4934
5210
|
});
|
|
5211
|
+
let remoteGhTokenConfirmed = false;
|
|
4935
5212
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
4936
5213
|
if (!prompts.confirm)
|
|
4937
5214
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
4938
5215
|
const confirmed = await prompts.confirm({
|
|
4939
5216
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
4940
|
-
initialValue:
|
|
5217
|
+
initialValue: true
|
|
4941
5218
|
});
|
|
4942
5219
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
4943
5220
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4944
5221
|
}
|
|
5222
|
+
remoteGhTokenConfirmed = true;
|
|
4945
5223
|
}
|
|
4946
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4947
|
-
const
|
|
4948
|
-
|
|
4949
|
-
options: [
|
|
4950
|
-
{ value: "off", label: "Off" },
|
|
4951
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4952
|
-
]
|
|
4953
|
-
});
|
|
4954
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4955
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
5224
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
5225
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
5226
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
4956
5227
|
const result = await runControlPlaneInit(context, {
|
|
4957
5228
|
server: serverChoice,
|
|
4958
5229
|
remoteUrl,
|
|
4959
5230
|
repoSlug,
|
|
4960
|
-
githubToken,
|
|
5231
|
+
githubToken: effectiveGithubToken,
|
|
4961
5232
|
githubAuthMethod: authMethod,
|
|
4962
|
-
githubProject,
|
|
4963
|
-
githubProjectStatusField,
|
|
5233
|
+
githubProject: projectConfig.githubProject,
|
|
5234
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
5235
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
4964
5236
|
remoteCheckout,
|
|
4965
5237
|
repair,
|
|
4966
|
-
privateStateOnly
|
|
5238
|
+
privateStateOnly,
|
|
5239
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
4967
5240
|
});
|
|
4968
5241
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
4969
5242
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
@@ -5059,7 +5332,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
5059
5332
|
}
|
|
5060
5333
|
|
|
5061
5334
|
// packages/cli/src/commands/github.ts
|
|
5062
|
-
import { spawnSync as
|
|
5335
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5063
5336
|
function printPayload(context, payload, fallback) {
|
|
5064
5337
|
if (context.outputMode === "json")
|
|
5065
5338
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -5067,7 +5340,7 @@ function printPayload(context, payload, fallback) {
|
|
|
5067
5340
|
console.log(fallback);
|
|
5068
5341
|
}
|
|
5069
5342
|
function readGhToken() {
|
|
5070
|
-
const result =
|
|
5343
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
5071
5344
|
if (result.status !== 0) {
|
|
5072
5345
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
5073
5346
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -6099,14 +6372,10 @@ async function executeRemote(context, args) {
|
|
|
6099
6372
|
}
|
|
6100
6373
|
|
|
6101
6374
|
// packages/cli/src/commands/run.ts
|
|
6102
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6103
|
-
import { resolve as resolve20 } from "path";
|
|
6104
6375
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6105
6376
|
import {
|
|
6106
6377
|
listAuthorityRuns as listAuthorityRuns3,
|
|
6107
|
-
readAuthorityRun as readAuthorityRun4
|
|
6108
|
-
readJsonlFile as readJsonlFile4,
|
|
6109
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6378
|
+
readAuthorityRun as readAuthorityRun4
|
|
6110
6379
|
} from "@rig/runtime/control-plane/authority-files";
|
|
6111
6380
|
import {
|
|
6112
6381
|
cleanupRunState,
|
|
@@ -6114,6 +6383,7 @@ import {
|
|
|
6114
6383
|
listOpenEpics,
|
|
6115
6384
|
resolveDefaultEpic,
|
|
6116
6385
|
runResume,
|
|
6386
|
+
runRestart,
|
|
6117
6387
|
runStatus,
|
|
6118
6388
|
runStop,
|
|
6119
6389
|
startRun,
|
|
@@ -6121,9 +6391,9 @@ import {
|
|
|
6121
6391
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
6122
6392
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
6123
6393
|
|
|
6124
|
-
// packages/cli/src/commands/_operator-
|
|
6394
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
6125
6395
|
import { createInterface } from "readline";
|
|
6126
|
-
|
|
6396
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
6127
6397
|
var CANONICAL_STAGES = [
|
|
6128
6398
|
"Connect",
|
|
6129
6399
|
"GitHub/task sync",
|
|
@@ -6138,18 +6408,188 @@ var CANONICAL_STAGES = [
|
|
|
6138
6408
|
"Merge",
|
|
6139
6409
|
"Complete"
|
|
6140
6410
|
];
|
|
6411
|
+
function logDetail(log3) {
|
|
6412
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6413
|
+
}
|
|
6414
|
+
function parseProviderProtocolLog(title, detail) {
|
|
6415
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
6416
|
+
return null;
|
|
6417
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
6418
|
+
return null;
|
|
6419
|
+
try {
|
|
6420
|
+
const record = JSON.parse(detail);
|
|
6421
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
6422
|
+
return null;
|
|
6423
|
+
const type = record.type;
|
|
6424
|
+
return typeof type === "string" && [
|
|
6425
|
+
"assistant",
|
|
6426
|
+
"message_start",
|
|
6427
|
+
"message_update",
|
|
6428
|
+
"message_end",
|
|
6429
|
+
"stream_event",
|
|
6430
|
+
"tool_result",
|
|
6431
|
+
"tool_execution_start",
|
|
6432
|
+
"tool_execution_update",
|
|
6433
|
+
"tool_execution_end",
|
|
6434
|
+
"turn_start",
|
|
6435
|
+
"turn_end"
|
|
6436
|
+
].includes(type) ? record : null;
|
|
6437
|
+
} catch {
|
|
6438
|
+
return null;
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
function renderProviderProtocolLog(record) {
|
|
6442
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
6443
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
6444
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
6445
|
+
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";
|
|
6446
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
6447
|
+
}
|
|
6448
|
+
return null;
|
|
6449
|
+
}
|
|
6450
|
+
function entryId(entry, fallback) {
|
|
6451
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6452
|
+
}
|
|
6141
6453
|
function renderOperatorSnapshot(snapshot) {
|
|
6142
6454
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
6143
6455
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
6144
6456
|
const status = String(run.status ?? "unknown");
|
|
6145
6457
|
const logs = snapshot.logs ?? [];
|
|
6458
|
+
const latestByStage = new Map;
|
|
6459
|
+
for (const log3 of logs) {
|
|
6460
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6461
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6462
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6463
|
+
if (stage)
|
|
6464
|
+
latestByStage.set(stage, log3);
|
|
6465
|
+
}
|
|
6146
6466
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
6147
|
-
const match =
|
|
6148
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6467
|
+
const match = latestByStage.get(stage);
|
|
6468
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
6149
6469
|
});
|
|
6150
6470
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
6151
6471
|
`);
|
|
6152
6472
|
}
|
|
6473
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6474
|
+
let lastSnapshot = "";
|
|
6475
|
+
const assistantTextById = new Map;
|
|
6476
|
+
const seenTimeline = new Set;
|
|
6477
|
+
const seenLogs = new Set;
|
|
6478
|
+
const writeLine = (line) => output.write(`${line}
|
|
6479
|
+
`);
|
|
6480
|
+
return {
|
|
6481
|
+
renderSnapshot(snapshot) {
|
|
6482
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6483
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6484
|
+
writeLine(rendered);
|
|
6485
|
+
lastSnapshot = rendered;
|
|
6486
|
+
}
|
|
6487
|
+
},
|
|
6488
|
+
renderTimeline(entries) {
|
|
6489
|
+
for (const [index, entry] of entries.entries()) {
|
|
6490
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6491
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6492
|
+
const text2 = entry.text;
|
|
6493
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6494
|
+
if (!previousText && text2.trim()) {
|
|
6495
|
+
writeLine("[Pi assistant]");
|
|
6496
|
+
}
|
|
6497
|
+
if (text2.startsWith(previousText)) {
|
|
6498
|
+
const delta = text2.slice(previousText.length);
|
|
6499
|
+
if (delta)
|
|
6500
|
+
output.write(delta);
|
|
6501
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6502
|
+
if (previousText)
|
|
6503
|
+
writeLine(`
|
|
6504
|
+
[Pi assistant]`);
|
|
6505
|
+
output.write(text2);
|
|
6506
|
+
}
|
|
6507
|
+
assistantTextById.set(id, text2);
|
|
6508
|
+
continue;
|
|
6509
|
+
}
|
|
6510
|
+
if (seenTimeline.has(id))
|
|
6511
|
+
continue;
|
|
6512
|
+
seenTimeline.add(id);
|
|
6513
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6514
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6515
|
+
continue;
|
|
6516
|
+
}
|
|
6517
|
+
if (entry.type === "timeline_warning") {
|
|
6518
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
},
|
|
6522
|
+
renderLogs(entries) {
|
|
6523
|
+
for (const [index, entry] of entries.entries()) {
|
|
6524
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6525
|
+
if (seenLogs.has(id))
|
|
6526
|
+
continue;
|
|
6527
|
+
seenLogs.add(id);
|
|
6528
|
+
const title = String(entry.title ?? "");
|
|
6529
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6530
|
+
continue;
|
|
6531
|
+
const detail = logDetail(entry);
|
|
6532
|
+
if (!detail)
|
|
6533
|
+
continue;
|
|
6534
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
6535
|
+
if (protocolRecord) {
|
|
6536
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
6537
|
+
if (protocolLine)
|
|
6538
|
+
writeLine(protocolLine);
|
|
6539
|
+
continue;
|
|
6540
|
+
}
|
|
6541
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
};
|
|
6545
|
+
}
|
|
6546
|
+
function createOperatorSurface(options = {}) {
|
|
6547
|
+
const input = options.input ?? process.stdin;
|
|
6548
|
+
const output = options.output ?? process.stdout;
|
|
6549
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6550
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6551
|
+
const writeLine = (line) => output.write(`${line}
|
|
6552
|
+
`);
|
|
6553
|
+
return {
|
|
6554
|
+
mode: "pi-compatible-text",
|
|
6555
|
+
...renderer,
|
|
6556
|
+
info: writeLine,
|
|
6557
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6558
|
+
`),
|
|
6559
|
+
attachCommandInput(handler) {
|
|
6560
|
+
if (options.interactive === false || !input.isTTY)
|
|
6561
|
+
return null;
|
|
6562
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6563
|
+
rl.on("line", (line) => {
|
|
6564
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6565
|
+
});
|
|
6566
|
+
return { close: () => rl.close() };
|
|
6567
|
+
}
|
|
6568
|
+
};
|
|
6569
|
+
}
|
|
6570
|
+
function taskId(task) {
|
|
6571
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6572
|
+
}
|
|
6573
|
+
function taskTitle(task) {
|
|
6574
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6575
|
+
}
|
|
6576
|
+
function taskStatus(task) {
|
|
6577
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6578
|
+
}
|
|
6579
|
+
function renderTaskPickerRows(tasks) {
|
|
6580
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6581
|
+
}
|
|
6582
|
+
async function promptForTaskSelection(question) {
|
|
6583
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6584
|
+
try {
|
|
6585
|
+
return await rl.question(question);
|
|
6586
|
+
} finally {
|
|
6587
|
+
rl.close();
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6592
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
6153
6593
|
function runStatusFromPayload(payload) {
|
|
6154
6594
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
6155
6595
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -6171,11 +6611,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
6171
6611
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
6172
6612
|
return { action: "continue", message: "Steering message queued." };
|
|
6173
6613
|
}
|
|
6174
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6614
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
6175
6615
|
const run = await getRunDetailsViaServer(context, runId);
|
|
6176
6616
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
6177
|
-
const
|
|
6178
|
-
|
|
6617
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6618
|
+
entries: [{
|
|
6619
|
+
id: `timeline-unavailable:${runId}`,
|
|
6620
|
+
type: "timeline_warning",
|
|
6621
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6622
|
+
createdAt: new Date().toISOString()
|
|
6623
|
+
}],
|
|
6624
|
+
nextCursor: options.timelineCursor ?? null
|
|
6625
|
+
}));
|
|
6626
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6627
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6628
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6629
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
6179
6630
|
}
|
|
6180
6631
|
async function attachRunOperatorView(context, input) {
|
|
6181
6632
|
let steered = false;
|
|
@@ -6183,45 +6634,266 @@ async function attachRunOperatorView(context, input) {
|
|
|
6183
6634
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
6184
6635
|
steered = true;
|
|
6185
6636
|
}
|
|
6637
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
6186
6638
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6187
6639
|
if (context.outputMode === "text") {
|
|
6188
|
-
|
|
6640
|
+
surface.renderSnapshot(snapshot);
|
|
6641
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6642
|
+
surface.renderLogs(snapshot.logs);
|
|
6189
6643
|
if (steered)
|
|
6190
|
-
|
|
6644
|
+
surface.info("Steering message queued.");
|
|
6191
6645
|
}
|
|
6192
6646
|
let detached = false;
|
|
6193
|
-
let
|
|
6647
|
+
let commandInput = null;
|
|
6194
6648
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
6195
6649
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
}
|
|
6206
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6650
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6651
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6652
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6653
|
+
if (result.message)
|
|
6654
|
+
surface.info(result.message);
|
|
6655
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6656
|
+
detached = true;
|
|
6657
|
+
commandInput?.close();
|
|
6658
|
+
}
|
|
6207
6659
|
});
|
|
6208
6660
|
}
|
|
6209
|
-
let lastRendered = snapshot.rendered;
|
|
6210
6661
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6662
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6211
6663
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6212
6664
|
await Bun.sleep(pollMs);
|
|
6213
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6665
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6666
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6667
|
+
surface.renderSnapshot(snapshot);
|
|
6668
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6669
|
+
surface.renderLogs(snapshot.logs);
|
|
6218
6670
|
}
|
|
6219
|
-
|
|
6671
|
+
commandInput?.close();
|
|
6220
6672
|
}
|
|
6221
6673
|
return { ...snapshot, steered, detached };
|
|
6222
6674
|
}
|
|
6223
6675
|
|
|
6676
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
6677
|
+
import pc3 from "picocolors";
|
|
6678
|
+
function stringField(record, key, fallback = "") {
|
|
6679
|
+
const value = record[key];
|
|
6680
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
6681
|
+
}
|
|
6682
|
+
function arrayField(record, key) {
|
|
6683
|
+
const value = record[key];
|
|
6684
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
|
|
6685
|
+
}
|
|
6686
|
+
function rawObject(record) {
|
|
6687
|
+
const raw = record.raw;
|
|
6688
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
6689
|
+
}
|
|
6690
|
+
function truncate(value, width) {
|
|
6691
|
+
if (value.length <= width)
|
|
6692
|
+
return value;
|
|
6693
|
+
if (width <= 1)
|
|
6694
|
+
return "\u2026";
|
|
6695
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
6696
|
+
}
|
|
6697
|
+
function pad(value, width) {
|
|
6698
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
6699
|
+
}
|
|
6700
|
+
function statusColor(status) {
|
|
6701
|
+
const normalized = status.toLowerCase();
|
|
6702
|
+
if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
|
|
6703
|
+
return pc3.green;
|
|
6704
|
+
if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
|
|
6705
|
+
return pc3.red;
|
|
6706
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
|
|
6707
|
+
return pc3.cyan;
|
|
6708
|
+
if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
|
|
6709
|
+
return pc3.yellow;
|
|
6710
|
+
return pc3.dim;
|
|
6711
|
+
}
|
|
6712
|
+
function formatTaskList(tasks, options = {}) {
|
|
6713
|
+
if (tasks.length === 0)
|
|
6714
|
+
return pc3.dim("No matching tasks.");
|
|
6715
|
+
if (options.raw)
|
|
6716
|
+
return tasks.map((task) => JSON.stringify(task)).join(`
|
|
6717
|
+
`);
|
|
6718
|
+
const rows = tasks.map((task) => {
|
|
6719
|
+
const raw = rawObject(task);
|
|
6720
|
+
const id = stringField(task, "id", "<unknown>");
|
|
6721
|
+
const status = stringField(task, "status", "unknown");
|
|
6722
|
+
const title = stringField(task, "title", "Untitled task");
|
|
6723
|
+
const source = stringField(task, "source", stringField(raw, "source", ""));
|
|
6724
|
+
const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
|
|
6725
|
+
return { id, status, title, source, labels };
|
|
6726
|
+
});
|
|
6727
|
+
const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
|
|
6728
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6729
|
+
const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6730
|
+
const body = rows.map((row) => {
|
|
6731
|
+
const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
|
|
6732
|
+
const source = row.source ? pc3.dim(` ${row.source}`) : "";
|
|
6733
|
+
return [
|
|
6734
|
+
pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
|
|
6735
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6736
|
+
`${row.title}${labels}${source}`
|
|
6737
|
+
].join(" ");
|
|
6738
|
+
});
|
|
6739
|
+
return [pc3.bold("Rig tasks"), header, ...body].join(`
|
|
6740
|
+
`);
|
|
6741
|
+
}
|
|
6742
|
+
function formatRunList(runs, options = {}) {
|
|
6743
|
+
if (runs.length === 0) {
|
|
6744
|
+
return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
|
|
6745
|
+
}
|
|
6746
|
+
const rows = runs.map((run) => {
|
|
6747
|
+
const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
|
|
6748
|
+
const status = stringField(run, "status", "unknown");
|
|
6749
|
+
const taskId2 = stringField(run, "taskId", "");
|
|
6750
|
+
const title = stringField(run, "title", taskId2 || "(untitled)");
|
|
6751
|
+
const runtime = stringField(run, "runtimeAdapter", "");
|
|
6752
|
+
return { runId, status, title, runtime };
|
|
6753
|
+
});
|
|
6754
|
+
const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
|
|
6755
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6756
|
+
const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6757
|
+
const body = rows.map((row) => [
|
|
6758
|
+
pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
|
|
6759
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6760
|
+
`${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
|
|
6761
|
+
].join(" "));
|
|
6762
|
+
return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
|
|
6763
|
+
`);
|
|
6764
|
+
}
|
|
6765
|
+
function formatSubmittedRun(input) {
|
|
6766
|
+
const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
|
|
6767
|
+
if (input.task) {
|
|
6768
|
+
const id = stringField(input.task, "id", "<unknown>");
|
|
6769
|
+
const status = stringField(input.task, "status", "unknown");
|
|
6770
|
+
const title = stringField(input.task, "title", "Untitled task");
|
|
6771
|
+
lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
|
|
6772
|
+
}
|
|
6773
|
+
return lines.join(`
|
|
6774
|
+
`);
|
|
6775
|
+
}
|
|
6776
|
+
|
|
6777
|
+
// packages/cli/src/commands/_pi-session.ts
|
|
6778
|
+
import { spawn as spawn2 } from "child_process";
|
|
6779
|
+
function buildPiRigSessionEnv(input) {
|
|
6780
|
+
return {
|
|
6781
|
+
RIG_PROJECT_ROOT: input.projectRoot,
|
|
6782
|
+
PROJECT_RIG_ROOT: input.projectRoot,
|
|
6783
|
+
RIG_RUN_ID: input.runId,
|
|
6784
|
+
RIG_SERVER_RUN_ID: input.runId,
|
|
6785
|
+
RIG_RUNTIME_ADAPTER: "pi",
|
|
6786
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
6787
|
+
RIG_SERVER_BASE_URL: input.serverUrl,
|
|
6788
|
+
RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
|
|
6789
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
6790
|
+
...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
|
|
6791
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
|
|
6792
|
+
};
|
|
6793
|
+
}
|
|
6794
|
+
function shellBinary(name) {
|
|
6795
|
+
const explicit = process.env.RIG_PI_BINARY?.trim();
|
|
6796
|
+
if (explicit)
|
|
6797
|
+
return explicit;
|
|
6798
|
+
return Bun.which(name) || name;
|
|
6799
|
+
}
|
|
6800
|
+
function buildPiRigSessionCommand(input) {
|
|
6801
|
+
const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
|
|
6802
|
+
const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
|
|
6803
|
+
const initialCommand = `/rig attach ${input.runId}`;
|
|
6804
|
+
return [
|
|
6805
|
+
shellBinary("pi"),
|
|
6806
|
+
"--no-extensions",
|
|
6807
|
+
"--extension",
|
|
6808
|
+
extensionSource,
|
|
6809
|
+
initialCommand
|
|
6810
|
+
];
|
|
6811
|
+
}
|
|
6812
|
+
async function launchPiRigSession(context, input) {
|
|
6813
|
+
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
6814
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6815
|
+
}
|
|
6816
|
+
if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
|
|
6817
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6818
|
+
}
|
|
6819
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
6820
|
+
const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
|
|
6821
|
+
const env = {
|
|
6822
|
+
...process.env,
|
|
6823
|
+
...buildPiRigSessionEnv({
|
|
6824
|
+
projectRoot: context.projectRoot,
|
|
6825
|
+
runId: input.runId,
|
|
6826
|
+
taskId: input.taskId,
|
|
6827
|
+
serverUrl: server.baseUrl,
|
|
6828
|
+
authToken: server.authToken
|
|
6829
|
+
})
|
|
6830
|
+
};
|
|
6831
|
+
process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
|
|
6832
|
+
`);
|
|
6833
|
+
process.stdout.write(`Pi command: ${formatCommand(command)}
|
|
6834
|
+
`);
|
|
6835
|
+
const launchedAt = Date.now();
|
|
6836
|
+
const child = spawn2(command[0], command.slice(1), {
|
|
6837
|
+
cwd: context.projectRoot,
|
|
6838
|
+
env,
|
|
6839
|
+
stdio: "inherit"
|
|
6840
|
+
});
|
|
6841
|
+
const launchError = await new Promise((resolve20) => {
|
|
6842
|
+
child.once("error", (error) => {
|
|
6843
|
+
resolve20({ error: error.message });
|
|
6844
|
+
});
|
|
6845
|
+
child.once("close", (code) => resolve20({ code }));
|
|
6846
|
+
});
|
|
6847
|
+
if ("error" in launchError) {
|
|
6848
|
+
process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
|
|
6849
|
+
`);
|
|
6850
|
+
return { launched: false, exitCode: null, command, error: launchError.error };
|
|
6851
|
+
}
|
|
6852
|
+
const exitCode = launchError.code;
|
|
6853
|
+
const elapsedMs = Date.now() - launchedAt;
|
|
6854
|
+
if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
|
|
6855
|
+
const error = `Pi exited during startup with code ${exitCode}.`;
|
|
6856
|
+
process.stderr.write(`${error} Falling back to Rig attach view.
|
|
6857
|
+
`);
|
|
6858
|
+
return { launched: false, exitCode, command, error };
|
|
6859
|
+
}
|
|
6860
|
+
return { launched: true, exitCode, command };
|
|
6861
|
+
}
|
|
6862
|
+
|
|
6224
6863
|
// packages/cli/src/commands/run.ts
|
|
6864
|
+
function normalizeRemoteRunDetails(payload) {
|
|
6865
|
+
const run = payload.run;
|
|
6866
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
6867
|
+
return null;
|
|
6868
|
+
return {
|
|
6869
|
+
...run,
|
|
6870
|
+
...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
|
|
6871
|
+
...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
|
|
6872
|
+
...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
|
|
6873
|
+
};
|
|
6874
|
+
}
|
|
6875
|
+
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
6876
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
6877
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
6878
|
+
}
|
|
6879
|
+
async function listRunsForSelectedConnection(context, options = {}) {
|
|
6880
|
+
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
6881
|
+
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
6882
|
+
}
|
|
6883
|
+
return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
|
|
6884
|
+
}
|
|
6885
|
+
function runStringField(run, key, fallback = "") {
|
|
6886
|
+
const value = run[key];
|
|
6887
|
+
return typeof value === "string" && value.trim() ? value : fallback;
|
|
6888
|
+
}
|
|
6889
|
+
function runDisplayTitle(run) {
|
|
6890
|
+
return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
|
|
6891
|
+
}
|
|
6892
|
+
function buildServerRunStatus(runs) {
|
|
6893
|
+
const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6894
|
+
const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6895
|
+
return { activeRuns, recentRuns, runs };
|
|
6896
|
+
}
|
|
6225
6897
|
function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
|
|
6226
6898
|
if (noEpicPrompt) {
|
|
6227
6899
|
return false;
|
|
@@ -6287,17 +6959,11 @@ async function executeRun(context, args) {
|
|
|
6287
6959
|
switch (command) {
|
|
6288
6960
|
case "list": {
|
|
6289
6961
|
requireNoExtraArgs(rest, "bun run rig run list");
|
|
6290
|
-
const runs =
|
|
6962
|
+
const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
|
|
6291
6963
|
if (context.outputMode === "text") {
|
|
6292
|
-
|
|
6293
|
-
console.log("No runs recorded in .rig/runs.");
|
|
6294
|
-
} else {
|
|
6295
|
-
for (const run of runs) {
|
|
6296
|
-
console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
|
|
6297
|
-
}
|
|
6298
|
-
}
|
|
6964
|
+
console.log(formatRunList(runs, { source }));
|
|
6299
6965
|
}
|
|
6300
|
-
return { ok: true, group: "run", command, details: { runs } };
|
|
6966
|
+
return { ok: true, group: "run", command, details: { runs, source } };
|
|
6301
6967
|
}
|
|
6302
6968
|
case "delete": {
|
|
6303
6969
|
let pending = rest;
|
|
@@ -6360,7 +7026,7 @@ async function executeRun(context, args) {
|
|
|
6360
7026
|
if (!run.value) {
|
|
6361
7027
|
throw new CliError2("run show requires --run <id>.");
|
|
6362
7028
|
}
|
|
6363
|
-
const record = readAuthorityRun4(context.projectRoot, run.value);
|
|
7029
|
+
const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
|
|
6364
7030
|
if (!record) {
|
|
6365
7031
|
throw new CliError2(`Run not found: ${run.value}`, 2);
|
|
6366
7032
|
}
|
|
@@ -6379,34 +7045,24 @@ async function executeRun(context, args) {
|
|
|
6379
7045
|
if (!run.value) {
|
|
6380
7046
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6381
7047
|
}
|
|
6382
|
-
const
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
return events2;
|
|
6391
|
-
};
|
|
6392
|
-
const events = printEvents();
|
|
7048
|
+
const renderer = createPiRunStreamRenderer();
|
|
7049
|
+
let cursor = null;
|
|
7050
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
7051
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
7052
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
7053
|
+
if (context.outputMode === "text") {
|
|
7054
|
+
renderer.renderTimeline(events);
|
|
7055
|
+
}
|
|
6393
7056
|
if (follow.value && context.outputMode === "text") {
|
|
6394
|
-
let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6395
7057
|
while (true) {
|
|
6396
7058
|
await Bun.sleep(1000);
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
continue;
|
|
6402
|
-
const delta = next.slice(lastLength);
|
|
6403
|
-
lastLength = next.length;
|
|
6404
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6405
|
-
console.log(line);
|
|
6406
|
-
}
|
|
7059
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
7060
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
7061
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
7062
|
+
renderer.renderTimeline(nextEvents);
|
|
6407
7063
|
}
|
|
6408
7064
|
}
|
|
6409
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
7065
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6410
7066
|
}
|
|
6411
7067
|
case "attach": {
|
|
6412
7068
|
let pending = rest;
|
|
@@ -6427,14 +7083,26 @@ async function executeRun(context, args) {
|
|
|
6427
7083
|
if (!runId) {
|
|
6428
7084
|
throw new CliError2("run attach requires a run id.", 2);
|
|
6429
7085
|
}
|
|
7086
|
+
let steered = false;
|
|
7087
|
+
const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
|
|
7088
|
+
if (shouldTryPiAttach && messageOption.value?.trim()) {
|
|
7089
|
+
await steerRunViaServer(context, runId, messageOption.value.trim());
|
|
7090
|
+
steered = true;
|
|
7091
|
+
}
|
|
7092
|
+
if (shouldTryPiAttach) {
|
|
7093
|
+
const piSession = await launchPiRigSession(context, { runId });
|
|
7094
|
+
if (piSession.launched) {
|
|
7095
|
+
return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
6430
7098
|
const attached = await attachRunOperatorView(context, {
|
|
6431
7099
|
runId,
|
|
6432
|
-
message: messageOption.value ?? null,
|
|
7100
|
+
message: shouldTryPiAttach ? null : messageOption.value ?? null,
|
|
6433
7101
|
once: once.value,
|
|
6434
7102
|
follow: follow.value,
|
|
6435
7103
|
pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
|
|
6436
7104
|
});
|
|
6437
|
-
return { ok: true, group: "run", command, details: attached };
|
|
7105
|
+
return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
|
|
6438
7106
|
}
|
|
6439
7107
|
case "status": {
|
|
6440
7108
|
requireNoExtraArgs(rest, "bun run rig run status");
|
|
@@ -6444,17 +7112,19 @@ async function executeRun(context, args) {
|
|
|
6444
7112
|
}
|
|
6445
7113
|
return { ok: true, group: "run", command };
|
|
6446
7114
|
}
|
|
6447
|
-
const summary = runStatus(context.projectRoot, runtimeContext);
|
|
7115
|
+
const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
|
|
7116
|
+
const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
7117
|
+
const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
6448
7118
|
if (context.outputMode === "text") {
|
|
6449
|
-
console.log(`Active runs: ${
|
|
6450
|
-
for (const run of
|
|
6451
|
-
console.log(`- ${run
|
|
7119
|
+
console.log(`Active runs: ${activeRuns.length}`);
|
|
7120
|
+
for (const run of activeRuns) {
|
|
7121
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6452
7122
|
}
|
|
6453
|
-
if (
|
|
7123
|
+
if (recentRuns.length > 0) {
|
|
6454
7124
|
console.log("");
|
|
6455
7125
|
console.log("Recent runs:");
|
|
6456
|
-
for (const run of
|
|
6457
|
-
console.log(`- ${run
|
|
7126
|
+
for (const run of recentRuns) {
|
|
7127
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6458
7128
|
}
|
|
6459
7129
|
}
|
|
6460
7130
|
}
|
|
@@ -6541,6 +7211,20 @@ async function executeRun(context, args) {
|
|
|
6541
7211
|
}
|
|
6542
7212
|
return { ok: true, group: "run", command, details: resumed };
|
|
6543
7213
|
}
|
|
7214
|
+
case "restart": {
|
|
7215
|
+
requireNoExtraArgs(rest, "bun run rig run restart");
|
|
7216
|
+
if (context.dryRun) {
|
|
7217
|
+
if (context.outputMode === "text") {
|
|
7218
|
+
console.log("[dry-run] rig run restart");
|
|
7219
|
+
}
|
|
7220
|
+
return { ok: true, group: "run", command };
|
|
7221
|
+
}
|
|
7222
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
7223
|
+
if (context.outputMode === "text") {
|
|
7224
|
+
console.log(`Restarted run: ${restarted.runId}`);
|
|
7225
|
+
}
|
|
7226
|
+
return { ok: true, group: "run", command, details: restarted };
|
|
7227
|
+
}
|
|
6544
7228
|
case "stop": {
|
|
6545
7229
|
const runOption = takeOption(rest, "--run");
|
|
6546
7230
|
const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
|
|
@@ -6598,7 +7282,7 @@ async function executeServer(context, args, options) {
|
|
|
6598
7282
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
6599
7283
|
pending = authTokenResult.rest;
|
|
6600
7284
|
requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
6601
|
-
const commandParts = ["
|
|
7285
|
+
const commandParts = ["rig-server", "start"];
|
|
6602
7286
|
if (hostResult.value) {
|
|
6603
7287
|
commandParts.push("--host", hostResult.value);
|
|
6604
7288
|
}
|
|
@@ -6621,7 +7305,7 @@ async function executeServer(context, args, options) {
|
|
|
6621
7305
|
const eventResult = takeOption(pending, "--event");
|
|
6622
7306
|
pending = eventResult.rest;
|
|
6623
7307
|
requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
|
|
6624
|
-
const commandParts = ["
|
|
7308
|
+
const commandParts = ["rig-server", "notify-test"];
|
|
6625
7309
|
if (eventResult.value) {
|
|
6626
7310
|
commandParts.push("--event", eventResult.value);
|
|
6627
7311
|
}
|
|
@@ -6682,10 +7366,10 @@ async function executeServer(context, args, options) {
|
|
|
6682
7366
|
}
|
|
6683
7367
|
|
|
6684
7368
|
// packages/cli/src/commands/task.ts
|
|
6685
|
-
import { readFileSync as
|
|
6686
|
-
import { spawnSync as
|
|
6687
|
-
import {
|
|
6688
|
-
import {
|
|
7369
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7370
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7371
|
+
import { resolve as resolve20 } from "path";
|
|
7372
|
+
import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
|
|
6689
7373
|
import {
|
|
6690
7374
|
taskArtifactDir,
|
|
6691
7375
|
taskArtifacts,
|
|
@@ -6703,19 +7387,10 @@ import {
|
|
|
6703
7387
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6704
7388
|
|
|
6705
7389
|
// packages/cli/src/commands/_task-picker.ts
|
|
6706
|
-
import {
|
|
6707
|
-
function
|
|
7390
|
+
import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
|
|
7391
|
+
function taskId2(task) {
|
|
6708
7392
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6709
7393
|
}
|
|
6710
|
-
function taskTitle(task) {
|
|
6711
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6712
|
-
}
|
|
6713
|
-
function taskStatus(task) {
|
|
6714
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6715
|
-
}
|
|
6716
|
-
function renderTaskPickerRows(tasks) {
|
|
6717
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6718
|
-
}
|
|
6719
7394
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6720
7395
|
if (tasks.length === 0)
|
|
6721
7396
|
return null;
|
|
@@ -6725,25 +7400,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6725
7400
|
if (!isTty) {
|
|
6726
7401
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6727
7402
|
}
|
|
6728
|
-
|
|
6729
|
-
const
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
7403
|
+
if (io.prompt || io.renderer) {
|
|
7404
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
7405
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
7406
|
+
`) };
|
|
7407
|
+
renderer.writeLine("Select Rig task:");
|
|
7408
|
+
for (const row of renderTaskPickerRows(tasks))
|
|
7409
|
+
renderer.writeLine(` ${row}`);
|
|
7410
|
+
const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
7411
|
+
if (!answer2)
|
|
7412
|
+
return null;
|
|
7413
|
+
if (/^\d+$/.test(answer2)) {
|
|
7414
|
+
const index2 = Number.parseInt(answer2, 10) - 1;
|
|
7415
|
+
return tasks[index2] ?? null;
|
|
6734
7416
|
}
|
|
7417
|
+
return tasks.find((task) => taskId2(task) === answer2) ?? null;
|
|
7418
|
+
}
|
|
7419
|
+
const options = tasks.map((task, index2) => ({
|
|
7420
|
+
value: `${index2}`,
|
|
7421
|
+
label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
|
|
7422
|
+
hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
|
|
7423
|
+
}));
|
|
7424
|
+
const answer = await select2({
|
|
7425
|
+
message: "Select Rig task",
|
|
7426
|
+
options
|
|
6735
7427
|
});
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
console.log(` ${row}`);
|
|
6739
|
-
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6740
|
-
if (!answer)
|
|
7428
|
+
if (isCancel2(answer)) {
|
|
7429
|
+
cancel2("No task selected.");
|
|
6741
7430
|
return null;
|
|
6742
|
-
if (/^\d+$/.test(answer)) {
|
|
6743
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
6744
|
-
return tasks[index] ?? null;
|
|
6745
7431
|
}
|
|
6746
|
-
|
|
7432
|
+
const index = Number.parseInt(String(answer), 10);
|
|
7433
|
+
return Number.isFinite(index) ? tasks[index] ?? null : null;
|
|
6747
7434
|
}
|
|
6748
7435
|
|
|
6749
7436
|
// packages/cli/src/commands/task.ts
|
|
@@ -6821,7 +7508,7 @@ function normalizePrMode(value) {
|
|
|
6821
7508
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6822
7509
|
}
|
|
6823
7510
|
function detectLocalDirtyState(projectRoot) {
|
|
6824
|
-
const result =
|
|
7511
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6825
7512
|
if (result.status !== 0)
|
|
6826
7513
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6827
7514
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6855,13 +7542,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6855
7542
|
if (explicit)
|
|
6856
7543
|
return { mode: explicit, state };
|
|
6857
7544
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6858
|
-
const
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
7545
|
+
const answer = await confirm2({
|
|
7546
|
+
message: "Include current uncommitted changes in run baseline?",
|
|
7547
|
+
initialValue: false
|
|
7548
|
+
});
|
|
7549
|
+
if (isCancel3(answer)) {
|
|
7550
|
+
cancel3("Run cancelled.");
|
|
7551
|
+
throw new CliError2("Run cancelled by user.", 1);
|
|
6864
7552
|
}
|
|
7553
|
+
return { mode: answer ? "dirty-snapshot" : "head", state };
|
|
6865
7554
|
}
|
|
6866
7555
|
return { mode: "head", state };
|
|
6867
7556
|
}
|
|
@@ -6896,10 +7585,7 @@ function summarizeTask(task, options = {}) {
|
|
|
6896
7585
|
};
|
|
6897
7586
|
}
|
|
6898
7587
|
function printTaskSummary(task) {
|
|
6899
|
-
|
|
6900
|
-
const title = readTaskString(task, "title") ?? "Untitled task";
|
|
6901
|
-
const status = readTaskString(task, "status") ?? "unknown";
|
|
6902
|
-
console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
|
|
7588
|
+
console.log(formatTaskList([task]));
|
|
6903
7589
|
}
|
|
6904
7590
|
async function validatorRegistryForTaskCommands(projectRoot) {
|
|
6905
7591
|
return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
|
|
@@ -6917,16 +7603,8 @@ async function executeTask(context, args, options) {
|
|
|
6917
7603
|
requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
6918
7604
|
const tasks = await listWorkspaceTasksViaServer(context, filters);
|
|
6919
7605
|
if (context.outputMode === "text") {
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
} else {
|
|
6923
|
-
for (const task of tasks) {
|
|
6924
|
-
if (rawResult.value)
|
|
6925
|
-
console.log(JSON.stringify(summarizeTask(task, { raw: true })));
|
|
6926
|
-
else
|
|
6927
|
-
printTaskSummary(task);
|
|
6928
|
-
}
|
|
6929
|
-
}
|
|
7606
|
+
const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
|
|
7607
|
+
console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
|
|
6930
7608
|
}
|
|
6931
7609
|
return {
|
|
6932
7610
|
ok: true,
|
|
@@ -6940,12 +7618,12 @@ async function executeTask(context, args, options) {
|
|
|
6940
7618
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
6941
7619
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
6942
7620
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
6943
|
-
const
|
|
6944
|
-
if (!
|
|
7621
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7622
|
+
if (!taskId3)
|
|
6945
7623
|
throw new CliError2("task show requires a task id.", 2);
|
|
6946
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7624
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
6947
7625
|
if (!task)
|
|
6948
|
-
throw new CliError2(`Task not found: ${
|
|
7626
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
6949
7627
|
const summary = summarizeTask(task, { raw: true });
|
|
6950
7628
|
if (context.outputMode === "text")
|
|
6951
7629
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -7016,7 +7694,7 @@ async function executeTask(context, args, options) {
|
|
|
7016
7694
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
7017
7695
|
let content;
|
|
7018
7696
|
if (fileFlag.value) {
|
|
7019
|
-
content =
|
|
7697
|
+
content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
|
|
7020
7698
|
} else {
|
|
7021
7699
|
content = await readStdin();
|
|
7022
7700
|
}
|
|
@@ -7145,16 +7823,23 @@ async function executeTask(context, args, options) {
|
|
|
7145
7823
|
});
|
|
7146
7824
|
let attachDetails = null;
|
|
7147
7825
|
if (!detachResult.value && context.outputMode === "text") {
|
|
7148
|
-
console.log(
|
|
7149
|
-
if (
|
|
7150
|
-
|
|
7826
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
7827
|
+
if (runtimeAdapter === "pi") {
|
|
7828
|
+
const piSession = await launchPiRigSession(context, {
|
|
7829
|
+
runId: submitted.runId,
|
|
7830
|
+
taskId: selectedTaskId,
|
|
7831
|
+
title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
|
|
7832
|
+
runtimeAdapter
|
|
7833
|
+
});
|
|
7834
|
+
attachDetails = { mode: "pi", ...piSession };
|
|
7835
|
+
if (!piSession.launched) {
|
|
7836
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7837
|
+
}
|
|
7838
|
+
} else {
|
|
7839
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7151
7840
|
}
|
|
7152
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7153
7841
|
} else if (context.outputMode === "text") {
|
|
7154
|
-
console.log(
|
|
7155
|
-
if (selectedTask) {
|
|
7156
|
-
printTaskSummary(selectedTask);
|
|
7157
|
-
}
|
|
7842
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
7158
7843
|
}
|
|
7159
7844
|
return {
|
|
7160
7845
|
ok: true,
|
|
@@ -7237,9 +7922,9 @@ async function executeTask(context, args, options) {
|
|
|
7237
7922
|
}
|
|
7238
7923
|
|
|
7239
7924
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7240
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7241
|
-
import { resolve as
|
|
7242
|
-
import { spawn as
|
|
7925
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7926
|
+
import { resolve as resolve21 } from "path";
|
|
7927
|
+
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
7243
7928
|
import { createInterface as createLineInterface } from "readline";
|
|
7244
7929
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7245
7930
|
import {
|
|
@@ -7264,15 +7949,31 @@ import {
|
|
|
7264
7949
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7265
7950
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7266
7951
|
import {
|
|
7267
|
-
buildTaskRunLifecycleComment
|
|
7268
|
-
updateConfiguredTaskSourceTask
|
|
7952
|
+
buildTaskRunLifecycleComment
|
|
7269
7953
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7270
7954
|
import {
|
|
7271
7955
|
closeIssueAfterMergedPr,
|
|
7272
7956
|
commitRunChanges,
|
|
7273
7957
|
runPrAutomation
|
|
7274
7958
|
} from "@rig/runtime/control-plane/native/pr-automation";
|
|
7959
|
+
function looksLikeGitHubToken(value) {
|
|
7960
|
+
const token = value?.trim();
|
|
7961
|
+
if (!token)
|
|
7962
|
+
return false;
|
|
7963
|
+
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
7964
|
+
}
|
|
7965
|
+
function githubBridgeEnv(token) {
|
|
7966
|
+
const clean = token?.trim();
|
|
7967
|
+
if (!clean)
|
|
7968
|
+
return {};
|
|
7969
|
+
return {
|
|
7970
|
+
RIG_GITHUB_TOKEN: clean,
|
|
7971
|
+
GITHUB_TOKEN: clean,
|
|
7972
|
+
GH_TOKEN: clean
|
|
7973
|
+
};
|
|
7974
|
+
}
|
|
7275
7975
|
function buildPiRigBridgeEnv(input) {
|
|
7976
|
+
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
7276
7977
|
return {
|
|
7277
7978
|
RIG_PROJECT_ROOT: input.projectRoot,
|
|
7278
7979
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
@@ -7282,11 +7983,11 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7282
7983
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
7283
7984
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
7284
7985
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
7285
|
-
...
|
|
7986
|
+
...githubBridgeEnv(githubToken)
|
|
7286
7987
|
};
|
|
7287
7988
|
}
|
|
7288
7989
|
function runGitSync(cwd, args, input) {
|
|
7289
|
-
const result =
|
|
7990
|
+
const result = spawnSync4("git", [...args], {
|
|
7290
7991
|
cwd,
|
|
7291
7992
|
input,
|
|
7292
7993
|
encoding: "utf8",
|
|
@@ -7304,12 +8005,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7304
8005
|
return 0;
|
|
7305
8006
|
let copied = 0;
|
|
7306
8007
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7307
|
-
const sourcePath =
|
|
7308
|
-
const targetPath =
|
|
8008
|
+
const sourcePath = resolve21(sourceRoot, relativePath);
|
|
8009
|
+
const targetPath = resolve21(targetRoot, relativePath);
|
|
7309
8010
|
try {
|
|
7310
8011
|
if (!statSync2(sourcePath).isFile())
|
|
7311
8012
|
continue;
|
|
7312
|
-
mkdirSync8(
|
|
8013
|
+
mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
|
|
7313
8014
|
copyFileSync3(sourcePath, targetPath);
|
|
7314
8015
|
copied += 1;
|
|
7315
8016
|
} catch {}
|
|
@@ -7348,7 +8049,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7348
8049
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7349
8050
|
return {
|
|
7350
8051
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7351
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
8052
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7352
8053
|
};
|
|
7353
8054
|
}
|
|
7354
8055
|
function positiveInt(value, fallback) {
|
|
@@ -7356,7 +8057,7 @@ function positiveInt(value, fallback) {
|
|
|
7356
8057
|
}
|
|
7357
8058
|
function resolveTaskRunAutomationLimits(config, env = process.env) {
|
|
7358
8059
|
const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
|
|
7359
|
-
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations,
|
|
8060
|
+
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
|
|
7360
8061
|
return {
|
|
7361
8062
|
maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
|
|
7362
8063
|
maxPrFixIterations: configuredPrFixIterations
|
|
@@ -7451,7 +8152,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
|
|
|
7451
8152
|
}
|
|
7452
8153
|
function createCommandRunner(binary) {
|
|
7453
8154
|
return async (args, options) => {
|
|
7454
|
-
const child =
|
|
8155
|
+
const child = spawn3(binary, [...args], {
|
|
7455
8156
|
cwd: options?.cwd,
|
|
7456
8157
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7457
8158
|
});
|
|
@@ -7459,9 +8160,9 @@ function createCommandRunner(binary) {
|
|
|
7459
8160
|
const stderrChunks = [];
|
|
7460
8161
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7461
8162
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7462
|
-
return await new Promise((
|
|
7463
|
-
child.once("error", (error) =>
|
|
7464
|
-
child.once("close", (code) =>
|
|
8163
|
+
return await new Promise((resolve22) => {
|
|
8164
|
+
child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
|
|
8165
|
+
child.once("close", (code) => resolve22({
|
|
7465
8166
|
exitCode: code ?? 1,
|
|
7466
8167
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7467
8168
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7481,12 +8182,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7481
8182
|
return input.configuredBranch;
|
|
7482
8183
|
}
|
|
7483
8184
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7484
|
-
const
|
|
7485
|
-
if (!
|
|
8185
|
+
const taskId3 = input.taskId?.trim();
|
|
8186
|
+
if (!taskId3) {
|
|
7486
8187
|
return { status: "skipped" };
|
|
7487
8188
|
}
|
|
7488
|
-
const
|
|
7489
|
-
const
|
|
8189
|
+
const configInput = input.config ?? null;
|
|
8190
|
+
const config = configInput ?? {};
|
|
8191
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7490
8192
|
if (prMode === "off" || prMode === "ask") {
|
|
7491
8193
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7492
8194
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7502,7 +8204,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7502
8204
|
gitCommand
|
|
7503
8205
|
});
|
|
7504
8206
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7505
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
8207
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7506
8208
|
const stage = input.appendStage ?? (() => {
|
|
7507
8209
|
return;
|
|
7508
8210
|
});
|
|
@@ -7520,7 +8222,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7520
8222
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7521
8223
|
const commit = await commitRunChanges({
|
|
7522
8224
|
cwd: workspace,
|
|
7523
|
-
message: `rig: complete task ${
|
|
8225
|
+
message: `rig: complete task ${taskId3}`,
|
|
7524
8226
|
command: gitCommand
|
|
7525
8227
|
});
|
|
7526
8228
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7528,13 +8230,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7528
8230
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7529
8231
|
const pr = await prAutomation({
|
|
7530
8232
|
projectRoot: workspace,
|
|
7531
|
-
taskId:
|
|
8233
|
+
taskId: taskId3,
|
|
7532
8234
|
runId: input.runId,
|
|
7533
8235
|
branch,
|
|
7534
8236
|
config,
|
|
7535
8237
|
sourceTask: input.sourceTask,
|
|
7536
8238
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
8239
|
+
artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
|
|
7537
8240
|
command: ghCommand,
|
|
8241
|
+
gitCommand,
|
|
7538
8242
|
steerPi,
|
|
7539
8243
|
lifecycle: {
|
|
7540
8244
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7542,7 +8246,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7542
8246
|
try {
|
|
7543
8247
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7544
8248
|
await updateTaskSource(input.projectRoot, {
|
|
7545
|
-
taskId:
|
|
8249
|
+
taskId: taskId3,
|
|
7546
8250
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7547
8251
|
update: {
|
|
7548
8252
|
status: "under_review",
|
|
@@ -7572,7 +8276,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7572
8276
|
`), "reviewing", "error");
|
|
7573
8277
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7574
8278
|
await updateTaskSource(input.projectRoot, {
|
|
7575
|
-
taskId:
|
|
8279
|
+
taskId: taskId3,
|
|
7576
8280
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7577
8281
|
update: {
|
|
7578
8282
|
status: "ci_fixing",
|
|
@@ -7583,8 +8287,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7583
8287
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7584
8288
|
})
|
|
7585
8289
|
}
|
|
7586
|
-
}).catch(() => {
|
|
7587
|
-
return;
|
|
7588
8290
|
});
|
|
7589
8291
|
}
|
|
7590
8292
|
},
|
|
@@ -7592,7 +8294,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7592
8294
|
stage("Merge", prUrl, "running", "tool");
|
|
7593
8295
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7594
8296
|
await updateTaskSource(input.projectRoot, {
|
|
7595
|
-
taskId:
|
|
8297
|
+
taskId: taskId3,
|
|
7596
8298
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7597
8299
|
update: {
|
|
7598
8300
|
status: "merging",
|
|
@@ -7603,8 +8305,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7603
8305
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7604
8306
|
})
|
|
7605
8307
|
}
|
|
7606
|
-
}).catch(() => {
|
|
7607
|
-
return;
|
|
7608
8308
|
});
|
|
7609
8309
|
}
|
|
7610
8310
|
},
|
|
@@ -7621,7 +8321,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7621
8321
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7622
8322
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7623
8323
|
await updateTaskSource(input.projectRoot, {
|
|
7624
|
-
taskId:
|
|
8324
|
+
taskId: taskId3,
|
|
7625
8325
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7626
8326
|
update: {
|
|
7627
8327
|
status: "needs_attention",
|
|
@@ -7633,8 +8333,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7633
8333
|
errorText: detail
|
|
7634
8334
|
})
|
|
7635
8335
|
}
|
|
7636
|
-
}).catch(() => {
|
|
7637
|
-
|
|
8336
|
+
}).catch((error) => {
|
|
8337
|
+
try {
|
|
8338
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
8339
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
8340
|
+
title: "Task source needs-attention update failed",
|
|
8341
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
8342
|
+
tone: "error",
|
|
8343
|
+
status: "needs_attention",
|
|
8344
|
+
createdAt: new Date().toISOString()
|
|
8345
|
+
});
|
|
8346
|
+
} catch {}
|
|
7638
8347
|
});
|
|
7639
8348
|
}
|
|
7640
8349
|
return { status: "needs_attention", pr };
|
|
@@ -7642,7 +8351,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7642
8351
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7643
8352
|
await closeIssueAfterMergedPr({
|
|
7644
8353
|
projectRoot: input.projectRoot,
|
|
7645
|
-
taskId:
|
|
8354
|
+
taskId: taskId3,
|
|
7646
8355
|
runId: input.runId,
|
|
7647
8356
|
prUrl: pr.prUrl,
|
|
7648
8357
|
sourceTask: input.sourceTask,
|
|
@@ -7652,12 +8361,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7652
8361
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7653
8362
|
return { status: "completed", pr };
|
|
7654
8363
|
}
|
|
7655
|
-
function summarizeValidationFailure(projectRoot,
|
|
7656
|
-
if (!
|
|
8364
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
8365
|
+
if (!taskId3) {
|
|
7657
8366
|
return null;
|
|
7658
8367
|
}
|
|
7659
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7660
|
-
const summary = readJsonFile3(
|
|
8368
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
8369
|
+
const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
|
|
7661
8370
|
if (!summary || summary.status !== "fail") {
|
|
7662
8371
|
continue;
|
|
7663
8372
|
}
|
|
@@ -7738,9 +8447,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7738
8447
|
if (!input.taskId || !input.workspaceDir) {
|
|
7739
8448
|
return { accepted: false, reason: null };
|
|
7740
8449
|
}
|
|
7741
|
-
const artifactDir =
|
|
7742
|
-
const reviewStatusPath =
|
|
7743
|
-
const taskResultPath =
|
|
8450
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8451
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8452
|
+
const taskResultPath = resolve21(artifactDir, "task-result.json");
|
|
7744
8453
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7745
8454
|
if (reviewStatus !== "APPROVED") {
|
|
7746
8455
|
return { accepted: false, reason: null };
|
|
@@ -7777,12 +8486,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7777
8486
|
if (!input.taskId || !input.workspaceDir) {
|
|
7778
8487
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7779
8488
|
}
|
|
7780
|
-
const artifactDir =
|
|
7781
|
-
const reviewStatePath =
|
|
7782
|
-
const reviewFeedbackPath =
|
|
7783
|
-
const reviewStatusPath =
|
|
7784
|
-
const failedApproachesPath =
|
|
7785
|
-
const validationSummaryPath =
|
|
8489
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8490
|
+
const reviewStatePath = resolve21(artifactDir, "review-state.json");
|
|
8491
|
+
const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
|
|
8492
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8493
|
+
const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
8494
|
+
const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
|
|
7786
8495
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7787
8496
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7788
8497
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7836,12 +8545,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7836
8545
|
}
|
|
7837
8546
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7838
8547
|
}
|
|
8548
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8549
|
+
let nextAssistantText = input.assistantText;
|
|
8550
|
+
if (input.record.type === "message_update") {
|
|
8551
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8552
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8553
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8554
|
+
}
|
|
8555
|
+
} else if (input.record.type === "stream_event") {
|
|
8556
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8557
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8558
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8559
|
+
nextAssistantText += delta.text;
|
|
8560
|
+
}
|
|
8561
|
+
} else if (input.record.type === "assistant") {
|
|
8562
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8563
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8564
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8565
|
+
if (fullText.length > nextAssistantText.length)
|
|
8566
|
+
nextAssistantText = fullText;
|
|
8567
|
+
}
|
|
8568
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8569
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8570
|
+
id: input.messageId,
|
|
8571
|
+
type: "assistant_message",
|
|
8572
|
+
text: nextAssistantText,
|
|
8573
|
+
state: "streaming",
|
|
8574
|
+
createdAt: new Date().toISOString()
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
8577
|
+
return nextAssistantText;
|
|
8578
|
+
}
|
|
8579
|
+
function appendPiToolTimelineFromRecord(input) {
|
|
8580
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
8581
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
8582
|
+
return false;
|
|
8583
|
+
const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
|
|
8584
|
+
const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
|
|
8585
|
+
const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
|
|
8586
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8587
|
+
id: `tool:${toolCallId}:${type}`,
|
|
8588
|
+
type,
|
|
8589
|
+
toolName,
|
|
8590
|
+
status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
|
|
8591
|
+
createdAt: new Date().toISOString()
|
|
8592
|
+
});
|
|
8593
|
+
return true;
|
|
8594
|
+
}
|
|
8595
|
+
function isNonRenderablePiProtocolRecord(record) {
|
|
8596
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
8597
|
+
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");
|
|
8598
|
+
}
|
|
8599
|
+
function appendToolTimelineFromLog(input) {
|
|
8600
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8601
|
+
if (title !== "Tool activity")
|
|
8602
|
+
return;
|
|
8603
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8604
|
+
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;
|
|
8605
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8606
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8607
|
+
id: `tool:${logId}`,
|
|
8608
|
+
type: "tool_execution_update",
|
|
8609
|
+
toolName,
|
|
8610
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8611
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8612
|
+
payload,
|
|
8613
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8614
|
+
});
|
|
8615
|
+
}
|
|
7839
8616
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7840
|
-
if (!
|
|
8617
|
+
if (!existsSync12(reviewStatusPath)) {
|
|
7841
8618
|
return null;
|
|
7842
8619
|
}
|
|
7843
8620
|
try {
|
|
7844
|
-
const status =
|
|
8621
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7845
8622
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7846
8623
|
} catch {
|
|
7847
8624
|
return null;
|
|
@@ -7859,14 +8636,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7859
8636
|
function runSourceTaskIdentity(sourceTask) {
|
|
7860
8637
|
return sourceTask;
|
|
7861
8638
|
}
|
|
7862
|
-
|
|
7863
|
-
if (!
|
|
8639
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8640
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8641
|
+
return null;
|
|
8642
|
+
const record = sourceTask;
|
|
8643
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8644
|
+
if (direct?.trim())
|
|
8645
|
+
return direct.trim();
|
|
8646
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8647
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8648
|
+
}
|
|
8649
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8650
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8651
|
+
id: input.taskId,
|
|
8652
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8653
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8654
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8655
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8656
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8657
|
+
});
|
|
8658
|
+
if (serverResult.ok === false) {
|
|
8659
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8660
|
+
}
|
|
8661
|
+
return {
|
|
8662
|
+
updated: serverResult.ok !== false,
|
|
8663
|
+
taskId: input.taskId,
|
|
8664
|
+
status: input.update.status,
|
|
8665
|
+
source: "server",
|
|
8666
|
+
sourceKind: "server",
|
|
8667
|
+
projectSync: serverResult.projectSync
|
|
8668
|
+
};
|
|
8669
|
+
};
|
|
8670
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8671
|
+
if (!taskId3)
|
|
7864
8672
|
return;
|
|
7865
8673
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7866
8674
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7867
8675
|
return;
|
|
7868
8676
|
const result = await updateTaskSource(projectRoot, {
|
|
7869
|
-
taskId:
|
|
8677
|
+
taskId: taskId3,
|
|
7870
8678
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7871
8679
|
update: {
|
|
7872
8680
|
status,
|
|
@@ -7885,14 +8693,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7885
8693
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7886
8694
|
}
|
|
7887
8695
|
}
|
|
7888
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8696
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7889
8697
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7890
8698
|
const raw = run?.sourceTask;
|
|
7891
8699
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7892
8700
|
return null;
|
|
7893
8701
|
}
|
|
7894
8702
|
const record = raw;
|
|
7895
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8703
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7896
8704
|
if (!id) {
|
|
7897
8705
|
return null;
|
|
7898
8706
|
}
|
|
@@ -7924,6 +8732,9 @@ function stringArrayField(record, key) {
|
|
|
7924
8732
|
}
|
|
7925
8733
|
async function executeRigOwnedTaskRun(context, input) {
|
|
7926
8734
|
const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
|
|
8735
|
+
const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
|
|
8736
|
+
const resumeMode = process.env.RIG_RUN_RESUME === "1";
|
|
8737
|
+
const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
|
|
7927
8738
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
7928
8739
|
let prompt = buildRunPrompt({
|
|
7929
8740
|
projectRoot: context.projectRoot,
|
|
@@ -7979,14 +8790,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7979
8790
|
taskId: runtimeTaskId,
|
|
7980
8791
|
createdAt: startedAt,
|
|
7981
8792
|
runtimeAdapter: input.runtimeAdapter,
|
|
7982
|
-
status: "created"
|
|
8793
|
+
status: resumeMode ? "preparing" : "created"
|
|
7983
8794
|
});
|
|
7984
8795
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
7985
8796
|
status: "preparing",
|
|
7986
8797
|
startedAt,
|
|
7987
8798
|
completedAt: null,
|
|
7988
8799
|
errorText: null,
|
|
7989
|
-
artifactRoot: null,
|
|
8800
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
7990
8801
|
runtimeAdapter: input.runtimeAdapter,
|
|
7991
8802
|
runtimeMode: input.runtimeMode,
|
|
7992
8803
|
interactionMode: input.interactionMode,
|
|
@@ -8002,9 +8813,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8002
8813
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
8003
8814
|
});
|
|
8004
8815
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8005
|
-
id: `log:${input.runId}:start`,
|
|
8006
|
-
title: "Rig task run started",
|
|
8007
|
-
detail: input.taskId ?? input.title ?? runtimeTaskId,
|
|
8816
|
+
id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
|
|
8817
|
+
title: resumeMode ? "Rig task run resumed" : "Rig task run started",
|
|
8818
|
+
detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
|
|
8008
8819
|
tone: "info",
|
|
8009
8820
|
status: "preparing",
|
|
8010
8821
|
createdAt: startedAt
|
|
@@ -8025,15 +8836,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8025
8836
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
8026
8837
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
8027
8838
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
8028
|
-
const planningArtifactPath =
|
|
8839
|
+
const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
8029
8840
|
const persistedPlanning = {
|
|
8030
8841
|
...planningClassification,
|
|
8031
8842
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
8032
8843
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
8033
8844
|
classifiedAt: new Date().toISOString()
|
|
8034
8845
|
};
|
|
8035
|
-
mkdirSync8(
|
|
8036
|
-
writeFileSync6(
|
|
8846
|
+
mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8847
|
+
writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
8037
8848
|
`, "utf8");
|
|
8038
8849
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
8039
8850
|
prompt = `${prompt}
|
|
@@ -8082,11 +8893,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8082
8893
|
let reviewAction;
|
|
8083
8894
|
let verificationStarted = false;
|
|
8084
8895
|
let reviewStarted = false;
|
|
8085
|
-
let latestRuntimeWorkspace = null;
|
|
8086
|
-
let latestSessionDir = null;
|
|
8087
|
-
let latestLogsDir = null;
|
|
8896
|
+
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
8897
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
|
|
8898
|
+
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
8088
8899
|
let latestProviderCommand = null;
|
|
8089
|
-
let latestRuntimeBranch = null;
|
|
8900
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
8090
8901
|
let snapshotSidecarPromise = null;
|
|
8091
8902
|
let dirtyBaselineApplied = false;
|
|
8092
8903
|
const childEnv = {
|
|
@@ -8104,7 +8915,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8104
8915
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
8105
8916
|
RIG_SERVER_RUN_ID: input.runId
|
|
8106
8917
|
},
|
|
8107
|
-
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
|
|
8918
|
+
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
|
|
8919
|
+
...resumeMode ? {
|
|
8920
|
+
RIG_RUN_RESUME: "1",
|
|
8921
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
8922
|
+
} : {}
|
|
8108
8923
|
};
|
|
8109
8924
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
8110
8925
|
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
@@ -8162,10 +8977,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8162
8977
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8163
8978
|
status: "running",
|
|
8164
8979
|
worktreePath: latestRuntimeWorkspace,
|
|
8165
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8980
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
8166
8981
|
logRoot: latestLogsDir,
|
|
8167
|
-
sessionPath: latestSessionDir ?
|
|
8168
|
-
sessionLogPath: latestLogsDir ?
|
|
8982
|
+
sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
|
|
8983
|
+
sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
|
|
8169
8984
|
branch: runtimeId
|
|
8170
8985
|
});
|
|
8171
8986
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -8173,7 +8988,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8173
8988
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
8174
8989
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
8175
8990
|
if (readyFile) {
|
|
8176
|
-
mkdirSync8(
|
|
8991
|
+
mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
|
|
8177
8992
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
8178
8993
|
`, "utf8");
|
|
8179
8994
|
}
|
|
@@ -8306,6 +9121,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8306
9121
|
try {
|
|
8307
9122
|
const record = JSON.parse(trimmed);
|
|
8308
9123
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
9124
|
+
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
9125
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9126
|
+
return;
|
|
9127
|
+
}
|
|
8309
9128
|
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
8310
9129
|
runId: input.runId,
|
|
8311
9130
|
record,
|
|
@@ -8322,7 +9141,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8322
9141
|
if (providerLogs.length > 0) {
|
|
8323
9142
|
for (const providerLog of providerLogs) {
|
|
8324
9143
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9144
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8325
9145
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9146
|
+
if (providerLog.title === "Tool activity")
|
|
9147
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8326
9148
|
}
|
|
8327
9149
|
}
|
|
8328
9150
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8374,6 +9196,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8374
9196
|
return;
|
|
8375
9197
|
}
|
|
8376
9198
|
}
|
|
9199
|
+
if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
|
|
9200
|
+
return;
|
|
9201
|
+
}
|
|
8377
9202
|
if (record.type === "assistant") {
|
|
8378
9203
|
const message2 = record.message && typeof record.message === "object" ? record.message : record;
|
|
8379
9204
|
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
@@ -8410,9 +9235,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8410
9235
|
let reviewFailureDetail = null;
|
|
8411
9236
|
const stderrLines = [];
|
|
8412
9237
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
8413
|
-
|
|
9238
|
+
if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
|
|
9239
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9240
|
+
id: `log:${input.runId}:resume-closeout-phase`,
|
|
9241
|
+
title: "Resume continuing from closeout phase",
|
|
9242
|
+
detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
|
|
9243
|
+
tone: "info",
|
|
9244
|
+
status: resumePreviousStatus,
|
|
9245
|
+
createdAt: new Date().toISOString()
|
|
9246
|
+
});
|
|
9247
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
|
|
9248
|
+
exit = { code: 0, signal: null };
|
|
9249
|
+
} else if (resumeMode && latestRuntimeWorkspace) {
|
|
9250
|
+
const acceptedArtifactState = readTaskRunAcceptedArtifactState({
|
|
9251
|
+
taskId: input.taskId ?? runtimeTaskId,
|
|
9252
|
+
workspaceDir: latestRuntimeWorkspace
|
|
9253
|
+
});
|
|
9254
|
+
if (acceptedArtifactState.accepted) {
|
|
9255
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9256
|
+
id: `log:${input.runId}:resume-accepted-artifacts`,
|
|
9257
|
+
title: "Resume found accepted artifacts; continuing closeout",
|
|
9258
|
+
detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
|
|
9259
|
+
tone: "info",
|
|
9260
|
+
status: "validating",
|
|
9261
|
+
createdAt: new Date().toISOString()
|
|
9262
|
+
});
|
|
9263
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
|
|
9264
|
+
exit = { code: 0, signal: null };
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
|
|
8414
9268
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
8415
|
-
const child =
|
|
9269
|
+
const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
8416
9270
|
cwd: context.projectRoot,
|
|
8417
9271
|
env: childEnv,
|
|
8418
9272
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8451,7 +9305,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8451
9305
|
let acceptedArtifactObservedAt = null;
|
|
8452
9306
|
let acceptedArtifactPollTimer = null;
|
|
8453
9307
|
let acceptedArtifactKillTimer = null;
|
|
8454
|
-
const attemptExit = await new Promise((
|
|
9308
|
+
const attemptExit = await new Promise((resolve22) => {
|
|
8455
9309
|
let settled = false;
|
|
8456
9310
|
const settle = (result) => {
|
|
8457
9311
|
if (settled)
|
|
@@ -8459,7 +9313,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8459
9313
|
settled = true;
|
|
8460
9314
|
if (acceptedArtifactPollTimer)
|
|
8461
9315
|
clearInterval(acceptedArtifactPollTimer);
|
|
8462
|
-
|
|
9316
|
+
resolve22(result);
|
|
8463
9317
|
};
|
|
8464
9318
|
const pollAcceptedArtifacts = () => {
|
|
8465
9319
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8532,7 +9386,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8532
9386
|
});
|
|
8533
9387
|
for (const pendingLog of pendingLogs) {
|
|
8534
9388
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9389
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8535
9390
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9391
|
+
if (pendingLog.title === "Tool activity")
|
|
9392
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8536
9393
|
}
|
|
8537
9394
|
process.off("SIGTERM", forwardSigterm);
|
|
8538
9395
|
if (attemptExit.error) {
|
|
@@ -8658,8 +9515,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8658
9515
|
}
|
|
8659
9516
|
if (planningClassification.planningRequired) {
|
|
8660
9517
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8661
|
-
const expectedPlanPath =
|
|
8662
|
-
if (!
|
|
9518
|
+
const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
|
|
9519
|
+
if (!existsSync12(expectedPlanPath)) {
|
|
8663
9520
|
const failedAt = new Date().toISOString();
|
|
8664
9521
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8665
9522
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8679,6 +9536,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8679
9536
|
throw new CliError2(failureDetail, 1);
|
|
8680
9537
|
}
|
|
8681
9538
|
}
|
|
9539
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
9540
|
+
appendPiStageLog({
|
|
9541
|
+
projectRoot: context.projectRoot,
|
|
9542
|
+
runId: input.runId,
|
|
9543
|
+
stage: "Validate",
|
|
9544
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
9545
|
+
status: "completed"
|
|
9546
|
+
});
|
|
9547
|
+
if (verificationAction && !reviewStarted) {
|
|
9548
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
9549
|
+
}
|
|
9550
|
+
if (!reviewAction) {
|
|
9551
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
9552
|
+
}
|
|
9553
|
+
if (reviewAction) {
|
|
9554
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
9555
|
+
}
|
|
9556
|
+
const requestedAt = new Date().toISOString();
|
|
9557
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
9558
|
+
status: "reviewing",
|
|
9559
|
+
completedAt: null,
|
|
9560
|
+
errorText: null,
|
|
9561
|
+
serverCloseout: {
|
|
9562
|
+
status: "pending",
|
|
9563
|
+
phase: "queued",
|
|
9564
|
+
requestedAt,
|
|
9565
|
+
updatedAt: requestedAt,
|
|
9566
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
9567
|
+
branch: latestRuntimeBranch,
|
|
9568
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
9569
|
+
}
|
|
9570
|
+
});
|
|
9571
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9572
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9573
|
+
title: "Server-owned closeout requested",
|
|
9574
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9575
|
+
tone: "info",
|
|
9576
|
+
status: "reviewing",
|
|
9577
|
+
createdAt: requestedAt,
|
|
9578
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9579
|
+
});
|
|
9580
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9581
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9582
|
+
await context.emitEvent("command.finished", {
|
|
9583
|
+
command: [
|
|
9584
|
+
"rig",
|
|
9585
|
+
"server",
|
|
9586
|
+
"task-run",
|
|
9587
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9588
|
+
...input.title ? ["--title", input.title] : []
|
|
9589
|
+
],
|
|
9590
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9591
|
+
exitCode: 0,
|
|
9592
|
+
durationMs: 0,
|
|
9593
|
+
startedAt,
|
|
9594
|
+
finishedAt: requestedAt
|
|
9595
|
+
});
|
|
9596
|
+
return;
|
|
9597
|
+
}
|
|
8682
9598
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8683
9599
|
appendPiStageLog({
|
|
8684
9600
|
projectRoot: context.projectRoot,
|
|
@@ -8697,7 +9613,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8697
9613
|
});
|
|
8698
9614
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
|
|
8699
9615
|
const feedbackCommand = buildHostAgentCommand(message2);
|
|
8700
|
-
const child =
|
|
9616
|
+
const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
|
|
8701
9617
|
cwd: latestRuntimeWorkspace ?? context.projectRoot,
|
|
8702
9618
|
env: childEnv,
|
|
8703
9619
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8706,11 +9622,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8706
9622
|
child.stdin.write(message2);
|
|
8707
9623
|
}
|
|
8708
9624
|
child.stdin.end();
|
|
9625
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9626
|
+
let feedbackAssistantText = "";
|
|
9627
|
+
const feedbackPendingToolUses = new Map;
|
|
8709
9628
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8710
9629
|
stdout.on("line", (line) => {
|
|
8711
9630
|
const trimmed = line.trim();
|
|
8712
9631
|
if (!trimmed)
|
|
8713
9632
|
return;
|
|
9633
|
+
try {
|
|
9634
|
+
const record = JSON.parse(trimmed);
|
|
9635
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9636
|
+
runId: input.runId,
|
|
9637
|
+
record,
|
|
9638
|
+
createdAtFallback: new Date().toISOString(),
|
|
9639
|
+
status: "reviewing",
|
|
9640
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9641
|
+
});
|
|
9642
|
+
for (const providerLog of providerLogs) {
|
|
9643
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9644
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9645
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9646
|
+
if (providerLog.title === "Tool activity")
|
|
9647
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9648
|
+
}
|
|
9649
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9650
|
+
projectRoot: context.projectRoot,
|
|
9651
|
+
runId: input.runId,
|
|
9652
|
+
messageId: feedbackAssistantMessageId,
|
|
9653
|
+
record,
|
|
9654
|
+
assistantText: feedbackAssistantText
|
|
9655
|
+
});
|
|
9656
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9657
|
+
if (hadAssistantDelta) {
|
|
9658
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9659
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9660
|
+
}
|
|
9661
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9662
|
+
return;
|
|
9663
|
+
} catch {}
|
|
8714
9664
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8715
9665
|
id: nextRunLogId(),
|
|
8716
9666
|
title: "Pi PR feedback fix output",
|
|
@@ -8736,10 +9686,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8736
9686
|
});
|
|
8737
9687
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8738
9688
|
});
|
|
8739
|
-
const exitCode = await new Promise((
|
|
8740
|
-
child.once("error", () =>
|
|
8741
|
-
child.once("close", (code) =>
|
|
9689
|
+
const exitCode = await new Promise((resolve22) => {
|
|
9690
|
+
child.once("error", () => resolve22(1));
|
|
9691
|
+
child.once("close", (code) => resolve22(code ?? 1));
|
|
8742
9692
|
});
|
|
9693
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9694
|
+
runId: input.runId,
|
|
9695
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9696
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9697
|
+
})) {
|
|
9698
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9699
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9700
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9701
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9702
|
+
}
|
|
9703
|
+
if (feedbackAssistantText.trim()) {
|
|
9704
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9705
|
+
id: feedbackAssistantMessageId,
|
|
9706
|
+
type: "assistant_message",
|
|
9707
|
+
text: feedbackAssistantText,
|
|
9708
|
+
state: "completed",
|
|
9709
|
+
createdAt: new Date().toISOString(),
|
|
9710
|
+
completedAt: new Date().toISOString()
|
|
9711
|
+
});
|
|
9712
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9713
|
+
}
|
|
8743
9714
|
if (exitCode !== 0) {
|
|
8744
9715
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8745
9716
|
}
|
|
@@ -8857,8 +9828,8 @@ async function executeTest(context, args) {
|
|
|
8857
9828
|
}
|
|
8858
9829
|
|
|
8859
9830
|
// packages/cli/src/commands/setup.ts
|
|
8860
|
-
import { existsSync as
|
|
8861
|
-
import { resolve as
|
|
9831
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9832
|
+
import { resolve as resolve22 } from "path";
|
|
8862
9833
|
import { createPluginHost } from "@rig/core";
|
|
8863
9834
|
import {
|
|
8864
9835
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -8921,8 +9892,8 @@ function runSetupInit(projectRoot) {
|
|
|
8921
9892
|
mkdirSync9(stateDir, { recursive: true });
|
|
8922
9893
|
mkdirSync9(logsDir, { recursive: true });
|
|
8923
9894
|
mkdirSync9(artifactsDir, { recursive: true });
|
|
8924
|
-
const failuresPath =
|
|
8925
|
-
if (!
|
|
9895
|
+
const failuresPath = resolve22(stateDir, "failed_approaches.md");
|
|
9896
|
+
if (!existsSync13(failuresPath)) {
|
|
8926
9897
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
8927
9898
|
|
|
8928
9899
|
`, "utf-8");
|
|
@@ -8940,18 +9911,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
8940
9911
|
}
|
|
8941
9912
|
async function runSetupPreflight(projectRoot) {
|
|
8942
9913
|
await runSetupCheck(projectRoot);
|
|
8943
|
-
const validationRoot =
|
|
8944
|
-
if (
|
|
9914
|
+
const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9915
|
+
if (existsSync13(validationRoot)) {
|
|
8945
9916
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
8946
9917
|
for (const validator of validators) {
|
|
8947
|
-
const script =
|
|
8948
|
-
if (
|
|
9918
|
+
const script = resolve22(validationRoot, validator.name, "validate.sh");
|
|
9919
|
+
if (existsSync13(script)) {
|
|
8949
9920
|
console.log(`OK: validator script ${script}`);
|
|
8950
9921
|
}
|
|
8951
9922
|
}
|
|
8952
9923
|
}
|
|
8953
|
-
const hooksRoot =
|
|
8954
|
-
if (
|
|
9924
|
+
const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9925
|
+
if (existsSync13(hooksRoot)) {
|
|
8955
9926
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
8956
9927
|
for (const hook of hooks) {
|
|
8957
9928
|
console.log(`OK: hook ${hook}`);
|
|
@@ -9345,8 +10316,8 @@ var __testOnly = {
|
|
|
9345
10316
|
validateRequiredBugPromptValue
|
|
9346
10317
|
};
|
|
9347
10318
|
// packages/cli/src/launcher.ts
|
|
9348
|
-
import { existsSync as
|
|
9349
|
-
import { resolve as
|
|
10319
|
+
import { existsSync as existsSync14 } from "fs";
|
|
10320
|
+
import { basename as basename2, resolve as resolve23 } from "path";
|
|
9350
10321
|
import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
|
|
9351
10322
|
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
|
|
9352
10323
|
function parsePolicyMode(value) {
|
|
@@ -9359,7 +10330,7 @@ function parsePolicyMode(value) {
|
|
|
9359
10330
|
throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
|
|
9360
10331
|
}
|
|
9361
10332
|
function hasRigProjectMarker(candidate) {
|
|
9362
|
-
return
|
|
10333
|
+
return existsSync14(resolve23(candidate, RIG_DEFINITION_DIRNAME)) || existsSync14(resolve23(candidate, RIG_STATE_DIRNAME)) || existsSync14(resolve23(candidate, "rig.config.ts")) || existsSync14(resolve23(candidate, "rig.config.json")) || existsSync14(resolve23(candidate, ".git"));
|
|
9363
10334
|
}
|
|
9364
10335
|
function resolveProjectRoot({
|
|
9365
10336
|
envProjectRoot,
|
|
@@ -9368,17 +10339,19 @@ function resolveProjectRoot({
|
|
|
9368
10339
|
cwd = process.cwd()
|
|
9369
10340
|
}) {
|
|
9370
10341
|
if (envProjectRoot) {
|
|
9371
|
-
return
|
|
10342
|
+
return resolve23(cwd, envProjectRoot);
|
|
9372
10343
|
}
|
|
9373
10344
|
const fallbackImportDir = importDir ?? cwd;
|
|
9374
|
-
const
|
|
10345
|
+
const execName = basename2(execPath).toLowerCase();
|
|
10346
|
+
const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
|
|
10347
|
+
const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
|
|
9375
10348
|
for (const candidate of candidates) {
|
|
9376
10349
|
const nearest = resolveNearestRigProjectRoot(candidate);
|
|
9377
10350
|
if (hasRigProjectMarker(nearest)) {
|
|
9378
10351
|
return nearest;
|
|
9379
10352
|
}
|
|
9380
10353
|
}
|
|
9381
|
-
return
|
|
10354
|
+
return resolve23(cwd);
|
|
9382
10355
|
}
|
|
9383
10356
|
function normalizeCliErrorCode(message2, isCliError) {
|
|
9384
10357
|
if (message2.startsWith("Invalid --policy-mode value:")) {
|
|
@@ -9445,7 +10418,7 @@ async function runRigCli(module, options = {}) {
|
|
|
9445
10418
|
runId: context.runId,
|
|
9446
10419
|
outcome,
|
|
9447
10420
|
eventsFile: context.eventBus.getEventsFile(),
|
|
9448
|
-
policyFile:
|
|
10421
|
+
policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
|
|
9449
10422
|
policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
|
|
9450
10423
|
}, null, 2));
|
|
9451
10424
|
}
|