@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/bin/rig.js
CHANGED
|
@@ -105,12 +105,13 @@ async function runCommand(context, parts) {
|
|
|
105
105
|
const envMode = process.env.RIG_BASH_MODE;
|
|
106
106
|
const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
|
|
107
107
|
const controlledPath = `${resolve(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
|
|
108
|
-
const
|
|
108
|
+
const usesInfrastructureBinary = parts[0] === "rig-server";
|
|
109
|
+
const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
|
|
109
110
|
const commandEnv = [
|
|
110
111
|
"env",
|
|
111
112
|
`PATH=${controlledPath}`,
|
|
112
113
|
`PROJECT_RIG_ROOT=${context.projectRoot}`,
|
|
113
|
-
`BASH=${controlledBash}
|
|
114
|
+
...controlledBash ? [`BASH=${controlledBash}`] : [],
|
|
114
115
|
`RIG_BASH_MODE=${effectiveMode}`,
|
|
115
116
|
`RIG_POLICY_FILE=${resolve(context.projectRoot, "rig/policy/policy.json")}`,
|
|
116
117
|
...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
|
|
@@ -2670,17 +2671,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2670
2671
|
}
|
|
2671
2672
|
|
|
2672
2673
|
// packages/cli/src/commands/_server-client.ts
|
|
2673
|
-
import { spawnSync } from "child_process";
|
|
2674
2674
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2675
2675
|
import { resolve as resolve9 } from "path";
|
|
2676
2676
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2677
|
-
var
|
|
2677
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2678
2678
|
function cleanToken(value) {
|
|
2679
2679
|
const trimmed = value?.trim();
|
|
2680
2680
|
return trimmed ? trimmed : null;
|
|
2681
2681
|
}
|
|
2682
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2683
|
-
|
|
2682
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2683
|
+
const scopedKey = resolve9(projectRoot ?? process.cwd());
|
|
2684
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2684
2685
|
}
|
|
2685
2686
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2686
2687
|
const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2694,25 +2695,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2694
2695
|
}
|
|
2695
2696
|
}
|
|
2696
2697
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2697
|
-
|
|
2698
|
-
|
|
2698
|
+
const scopedKey = resolve9(projectRoot);
|
|
2699
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2700
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2699
2701
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2700
|
-
if (privateSession)
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
}
|
|
2704
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2705
|
-
if (envToken) {
|
|
2706
|
-
cachedGitHubBearerToken = envToken;
|
|
2707
|
-
return cachedGitHubBearerToken;
|
|
2708
|
-
}
|
|
2709
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2710
|
-
encoding: "utf8",
|
|
2711
|
-
timeout: 5000,
|
|
2712
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2713
|
-
});
|
|
2714
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2715
|
-
return cachedGitHubBearerToken;
|
|
2702
|
+
if (privateSession)
|
|
2703
|
+
return privateSession;
|
|
2704
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2716
2705
|
}
|
|
2717
2706
|
async function ensureServerForCli(projectRoot) {
|
|
2718
2707
|
try {
|
|
@@ -2850,16 +2839,36 @@ async function registerProjectViaServer(context, input) {
|
|
|
2850
2839
|
function sleep(ms) {
|
|
2851
2840
|
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
2852
2841
|
}
|
|
2842
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
2843
|
+
if (!(error instanceof Error))
|
|
2844
|
+
return false;
|
|
2845
|
+
const message = error.message.toLowerCase();
|
|
2846
|
+
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");
|
|
2847
|
+
}
|
|
2853
2848
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
2854
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2855
|
-
method: "POST",
|
|
2856
|
-
headers: { "content-type": "application/json" },
|
|
2857
|
-
body: JSON.stringify({ projectRoot })
|
|
2858
|
-
});
|
|
2859
2849
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
2860
2850
|
const pollMs = options.pollMs ?? 1000;
|
|
2861
2851
|
const deadline = Date.now() + timeoutMs;
|
|
2862
2852
|
let lastError;
|
|
2853
|
+
let switched = null;
|
|
2854
|
+
while (Date.now() < deadline) {
|
|
2855
|
+
try {
|
|
2856
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
2857
|
+
method: "POST",
|
|
2858
|
+
headers: { "content-type": "application/json" },
|
|
2859
|
+
body: JSON.stringify({ projectRoot })
|
|
2860
|
+
});
|
|
2861
|
+
break;
|
|
2862
|
+
} catch (error) {
|
|
2863
|
+
lastError = error;
|
|
2864
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
2865
|
+
throw error;
|
|
2866
|
+
await sleep(pollMs);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
if (!switched) {
|
|
2870
|
+
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);
|
|
2871
|
+
}
|
|
2863
2872
|
while (Date.now() < deadline) {
|
|
2864
2873
|
try {
|
|
2865
2874
|
const status = await requestServerJson(context, "/api/server/status");
|
|
@@ -2877,6 +2886,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
2877
2886
|
}
|
|
2878
2887
|
throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
|
|
2879
2888
|
}
|
|
2889
|
+
async function listRunsViaServer(context, options = {}) {
|
|
2890
|
+
const url = new URL("http://rig.local/api/runs");
|
|
2891
|
+
if (options.limit !== undefined)
|
|
2892
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2893
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2894
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
2895
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
2896
|
+
}
|
|
2880
2897
|
async function getRunDetailsViaServer(context, runId) {
|
|
2881
2898
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
|
|
2882
2899
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
@@ -2890,6 +2907,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2890
2907
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2891
2908
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2892
2909
|
}
|
|
2910
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2911
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2912
|
+
if (options.limit !== undefined)
|
|
2913
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2914
|
+
if (options.cursor)
|
|
2915
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2916
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2917
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2918
|
+
}
|
|
2919
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2920
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2921
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2922
|
+
}
|
|
2923
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2924
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2925
|
+
url.searchParams.set("owner", owner);
|
|
2926
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2927
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2928
|
+
}
|
|
2929
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2930
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2931
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2932
|
+
}
|
|
2933
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2934
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2935
|
+
method: "POST",
|
|
2936
|
+
headers: { "content-type": "application/json" },
|
|
2937
|
+
body: JSON.stringify(input)
|
|
2938
|
+
});
|
|
2939
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2940
|
+
}
|
|
2893
2941
|
async function stopRunViaServer(context, runId) {
|
|
2894
2942
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2895
2943
|
method: "POST",
|
|
@@ -2942,7 +2990,8 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
2942
2990
|
import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
|
|
2943
2991
|
import { homedir as homedir3 } from "os";
|
|
2944
2992
|
import { resolve as resolve10 } from "path";
|
|
2945
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2993
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
2994
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
2946
2995
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
2947
2996
|
export { default } from '@rig/pi-rig';
|
|
2948
2997
|
`;
|
|
@@ -2970,7 +3019,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
2970
3019
|
function piListContainsPiRig(output) {
|
|
2971
3020
|
return output.split(/\r?\n/).some((line) => {
|
|
2972
3021
|
const normalized = line.trim();
|
|
2973
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
3022
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
2974
3023
|
});
|
|
2975
3024
|
}
|
|
2976
3025
|
async function safeRun(runner, command, options) {
|
|
@@ -3086,7 +3135,7 @@ async function ensureRemotePiRigInstalled(input) {
|
|
|
3086
3135
|
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
3087
3136
|
method: "POST",
|
|
3088
3137
|
headers: { "content-type": "application/json" },
|
|
3089
|
-
body: JSON.stringify({ package:
|
|
3138
|
+
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
3090
3139
|
});
|
|
3091
3140
|
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
3092
3141
|
const piOk = record.piOk === true || record.ok === true;
|
|
@@ -3169,6 +3218,9 @@ function permissionAllowsPr(payload) {
|
|
|
3169
3218
|
}
|
|
3170
3219
|
return null;
|
|
3171
3220
|
}
|
|
3221
|
+
function isNotFoundError(error) {
|
|
3222
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3223
|
+
}
|
|
3172
3224
|
function projectCheckoutReady(payload) {
|
|
3173
3225
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3174
3226
|
return null;
|
|
@@ -3201,19 +3253,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3201
3253
|
const checks = [];
|
|
3202
3254
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
3203
3255
|
const taskId = options.taskId?.trim() || null;
|
|
3256
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3257
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3258
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3259
|
+
let legacyServerCompatibility = false;
|
|
3204
3260
|
try {
|
|
3205
3261
|
await request("/api/server/status");
|
|
3206
3262
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3207
3263
|
} catch (error) {
|
|
3208
|
-
|
|
3264
|
+
if (isNotFoundError(error)) {
|
|
3265
|
+
try {
|
|
3266
|
+
await request("/health");
|
|
3267
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3268
|
+
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"));
|
|
3269
|
+
} catch (healthError) {
|
|
3270
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3271
|
+
}
|
|
3272
|
+
} else {
|
|
3273
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3274
|
+
}
|
|
3209
3275
|
}
|
|
3210
3276
|
const repo = readRepoConnection(context.projectRoot);
|
|
3211
|
-
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>`."));
|
|
3277
|
+
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>`."));
|
|
3212
3278
|
try {
|
|
3213
3279
|
const auth = await request("/api/github/auth/status");
|
|
3214
|
-
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>`."));
|
|
3280
|
+
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>`."));
|
|
3215
3281
|
} catch (error) {
|
|
3216
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3282
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3217
3283
|
}
|
|
3218
3284
|
try {
|
|
3219
3285
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3241,9 +3307,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3241
3307
|
try {
|
|
3242
3308
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3243
3309
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3244
|
-
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."));
|
|
3310
|
+
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."));
|
|
3245
3311
|
} catch (error) {
|
|
3246
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3312
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3247
3313
|
}
|
|
3248
3314
|
try {
|
|
3249
3315
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -3446,6 +3512,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
|
|
|
3446
3512
|
const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
|
|
3447
3513
|
const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
|
|
3448
3514
|
const next = {
|
|
3515
|
+
...existing ?? {},
|
|
3449
3516
|
runId: input.runId,
|
|
3450
3517
|
projectRoot,
|
|
3451
3518
|
workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
|
|
@@ -4154,9 +4221,10 @@ async function executeInbox(context, args) {
|
|
|
4154
4221
|
|
|
4155
4222
|
// packages/cli/src/commands/init.ts
|
|
4156
4223
|
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4157
|
-
import { spawnSync
|
|
4224
|
+
import { spawnSync } from "child_process";
|
|
4158
4225
|
import { resolve as resolve17 } from "path";
|
|
4159
4226
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4227
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
4160
4228
|
|
|
4161
4229
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
4162
4230
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4470,6 +4538,7 @@ function countDoctorFailures(checks) {
|
|
|
4470
4538
|
|
|
4471
4539
|
// packages/cli/src/commands/init.ts
|
|
4472
4540
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
4541
|
+
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
4473
4542
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
4474
4543
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
4475
4544
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -4480,7 +4549,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4480
4549
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4481
4550
|
}
|
|
4482
4551
|
function detectOriginRepoSlug(projectRoot) {
|
|
4483
|
-
const result =
|
|
4552
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4484
4553
|
if (result.status !== 0)
|
|
4485
4554
|
return null;
|
|
4486
4555
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4536,11 +4605,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4536
4605
|
return source;
|
|
4537
4606
|
const projectId = JSON.stringify(options.githubProject);
|
|
4538
4607
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4608
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4609
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4610
|
+
`)},` : "";
|
|
4539
4611
|
return source.replace(` projects: { enabled: false },`, [
|
|
4540
4612
|
` projects: {`,
|
|
4541
4613
|
` enabled: true,`,
|
|
4542
4614
|
` projectId: ${projectId},`,
|
|
4543
|
-
` statusFieldId: ${statusFieldId}
|
|
4615
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4544
4616
|
` },`
|
|
4545
4617
|
].join(`
|
|
4546
4618
|
`));
|
|
@@ -4561,21 +4633,41 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4561
4633
|
}
|
|
4562
4634
|
}
|
|
4563
4635
|
function detectGhLogin() {
|
|
4564
|
-
const result =
|
|
4636
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4565
4637
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4566
4638
|
}
|
|
4567
4639
|
function readGhAuthToken() {
|
|
4568
|
-
const result =
|
|
4640
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4569
4641
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4570
4642
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4571
4643
|
}
|
|
4572
4644
|
return result.stdout.trim();
|
|
4573
4645
|
}
|
|
4646
|
+
function refreshGhProjectScopesAndReadToken() {
|
|
4647
|
+
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
4648
|
+
encoding: "utf8",
|
|
4649
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4650
|
+
});
|
|
4651
|
+
if (result.status !== 0)
|
|
4652
|
+
return null;
|
|
4653
|
+
try {
|
|
4654
|
+
return readGhAuthToken();
|
|
4655
|
+
} catch {
|
|
4656
|
+
return null;
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4574
4659
|
async function loadClackPrompts() {
|
|
4575
4660
|
return await import("@clack/prompts");
|
|
4576
4661
|
}
|
|
4662
|
+
function clackTextOptions(options) {
|
|
4663
|
+
return {
|
|
4664
|
+
message: options.message,
|
|
4665
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4666
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4577
4669
|
async function promptRequiredText(prompts, options) {
|
|
4578
|
-
const value = await prompts.text(options);
|
|
4670
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4579
4671
|
if (prompts.isCancel(value))
|
|
4580
4672
|
throw new CliError2("Init cancelled.", 1);
|
|
4581
4673
|
const text2 = String(value ?? "").trim();
|
|
@@ -4584,7 +4676,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4584
4676
|
return text2;
|
|
4585
4677
|
}
|
|
4586
4678
|
async function promptOptionalText(prompts, options) {
|
|
4587
|
-
const value = await prompts.text(options);
|
|
4679
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4588
4680
|
if (prompts.isCancel(value))
|
|
4589
4681
|
throw new CliError2("Init cancelled.", 1);
|
|
4590
4682
|
return String(value ?? "").trim();
|
|
@@ -4595,6 +4687,164 @@ async function promptSelect(prompts, options) {
|
|
|
4595
4687
|
throw new CliError2("Init cancelled.", 1);
|
|
4596
4688
|
return String(value);
|
|
4597
4689
|
}
|
|
4690
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4691
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4692
|
+
}
|
|
4693
|
+
function recordArray(value, key) {
|
|
4694
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4695
|
+
return [];
|
|
4696
|
+
const raw = value[key];
|
|
4697
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4698
|
+
}
|
|
4699
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4700
|
+
if (token?.trim()) {
|
|
4701
|
+
try {
|
|
4702
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4703
|
+
} catch (directError) {
|
|
4704
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4705
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4706
|
+
return serverPayload;
|
|
4707
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4711
|
+
}
|
|
4712
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4713
|
+
if (token?.trim()) {
|
|
4714
|
+
try {
|
|
4715
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4716
|
+
} catch (directError) {
|
|
4717
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4718
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4719
|
+
return serverPayload;
|
|
4720
|
+
}
|
|
4721
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4725
|
+
}
|
|
4726
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4727
|
+
running: "Running/In progress",
|
|
4728
|
+
prOpen: "PR open/review",
|
|
4729
|
+
ciFixing: "CI/review fixing",
|
|
4730
|
+
merging: "Merging",
|
|
4731
|
+
done: "Done",
|
|
4732
|
+
needsAttention: "Needs attention"
|
|
4733
|
+
};
|
|
4734
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4735
|
+
running: "In Progress",
|
|
4736
|
+
prOpen: "In Review",
|
|
4737
|
+
ciFixing: "In Review",
|
|
4738
|
+
merging: "Merging",
|
|
4739
|
+
done: "Done",
|
|
4740
|
+
needsAttention: "Needs Attention"
|
|
4741
|
+
};
|
|
4742
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4743
|
+
const statuses = {};
|
|
4744
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4745
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4746
|
+
const value = await promptOptionalText(prompts, {
|
|
4747
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4748
|
+
placeholder: defaultLabel
|
|
4749
|
+
});
|
|
4750
|
+
statuses[key] = value || defaultLabel;
|
|
4751
|
+
}
|
|
4752
|
+
return statuses;
|
|
4753
|
+
}
|
|
4754
|
+
function projectScopeError(value) {
|
|
4755
|
+
const text2 = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
4756
|
+
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text2);
|
|
4757
|
+
}
|
|
4758
|
+
function optionName(option) {
|
|
4759
|
+
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
4760
|
+
}
|
|
4761
|
+
function autoProjectStatusValue(options, key, label) {
|
|
4762
|
+
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
4763
|
+
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
4764
|
+
if (!match)
|
|
4765
|
+
return null;
|
|
4766
|
+
return String(match.id ?? match.name);
|
|
4767
|
+
}
|
|
4768
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
4769
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4770
|
+
message: "GitHub Projects status sync",
|
|
4771
|
+
initialValue: "select",
|
|
4772
|
+
options: [
|
|
4773
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4774
|
+
{ value: "off", label: "Off" },
|
|
4775
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4776
|
+
]
|
|
4777
|
+
});
|
|
4778
|
+
if (projectChoice === "off")
|
|
4779
|
+
return { githubProject: "off" };
|
|
4780
|
+
if (projectChoice === "manual") {
|
|
4781
|
+
return {
|
|
4782
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4783
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4784
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4785
|
+
};
|
|
4786
|
+
}
|
|
4787
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4788
|
+
if (!owner)
|
|
4789
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4790
|
+
let activeToken = githubToken?.trim() || null;
|
|
4791
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4792
|
+
let projects = recordArray(projectsPayload, "projects");
|
|
4793
|
+
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
4794
|
+
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
4795
|
+
const refreshedToken = refreshProjectToken();
|
|
4796
|
+
if (refreshedToken) {
|
|
4797
|
+
activeToken = refreshedToken;
|
|
4798
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4799
|
+
projects = recordArray(projectsPayload, "projects");
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
if (projects.length === 0) {
|
|
4803
|
+
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
4804
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
4805
|
+
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
4806
|
+
}
|
|
4807
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4808
|
+
message: "GitHub ProjectV2 project",
|
|
4809
|
+
options: [
|
|
4810
|
+
...projects.map((project) => ({
|
|
4811
|
+
value: String(project.id),
|
|
4812
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4813
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4814
|
+
})),
|
|
4815
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4816
|
+
]
|
|
4817
|
+
});
|
|
4818
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4819
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4820
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4821
|
+
const rawField = fieldPayloadRecord.field;
|
|
4822
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4823
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4824
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4825
|
+
if (options.length === 0) {
|
|
4826
|
+
return {
|
|
4827
|
+
githubProject: projectId,
|
|
4828
|
+
githubProjectStatusField: fieldId,
|
|
4829
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
4830
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
const statuses = {};
|
|
4834
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4835
|
+
const auto = autoProjectStatusValue(options, key, label);
|
|
4836
|
+
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
4837
|
+
message: `Project status option for ${label}`,
|
|
4838
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4841
|
+
return {
|
|
4842
|
+
githubProject: projectId,
|
|
4843
|
+
githubProjectStatusField: fieldId,
|
|
4844
|
+
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
4845
|
+
...activeToken ? { githubToken: activeToken } : {}
|
|
4846
|
+
};
|
|
4847
|
+
}
|
|
4598
4848
|
function sleep2(ms) {
|
|
4599
4849
|
return new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
4600
4850
|
}
|
|
@@ -4608,12 +4858,29 @@ function apiSessionTokenFrom(payload) {
|
|
|
4608
4858
|
const token = payload.apiSessionToken;
|
|
4609
4859
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
4610
4860
|
}
|
|
4861
|
+
function cleanPayloadString(value) {
|
|
4862
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4863
|
+
}
|
|
4864
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
4865
|
+
if (!payload)
|
|
4866
|
+
return {};
|
|
4867
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
4868
|
+
return {
|
|
4869
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
4870
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
4871
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
4872
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
4873
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
4874
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4611
4877
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
4612
4878
|
writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
4613
4879
|
authenticated: true,
|
|
4614
4880
|
source: input.source,
|
|
4615
4881
|
storedOnServer: true,
|
|
4616
4882
|
selectedRepo: input.selectedRepo,
|
|
4883
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
4617
4884
|
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
4618
4885
|
updatedAt: new Date().toISOString()
|
|
4619
4886
|
}, null, 2)}
|
|
@@ -4706,12 +4973,13 @@ async function runControlPlaneInit(context, options) {
|
|
|
4706
4973
|
if (token) {
|
|
4707
4974
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4708
4975
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4709
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4976
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4710
4977
|
if (serverKind === "remote") {
|
|
4711
4978
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4712
4979
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
4713
4980
|
selectedRepo: repo.slug,
|
|
4714
|
-
apiSessionToken
|
|
4981
|
+
apiSessionToken,
|
|
4982
|
+
authPayload: githubAuth
|
|
4715
4983
|
});
|
|
4716
4984
|
}
|
|
4717
4985
|
} else if (authMethod === "device") {
|
|
@@ -4730,9 +4998,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
4730
4998
|
if (completed) {
|
|
4731
4999
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4732
5000
|
if (apiSessionToken) {
|
|
4733
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
5001
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4734
5002
|
if (serverKind === "remote") {
|
|
4735
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
|
|
5003
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4736
5004
|
}
|
|
4737
5005
|
}
|
|
4738
5006
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
@@ -4750,19 +5018,25 @@ async function runControlPlaneInit(context, options) {
|
|
|
4750
5018
|
Object.assign(checkout, preparedCheckout);
|
|
4751
5019
|
}
|
|
4752
5020
|
}
|
|
5021
|
+
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
5022
|
+
if (serverKind === "remote" && checkoutPath && token) {
|
|
5023
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
5024
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
5025
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
5026
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
5027
|
+
}
|
|
4753
5028
|
const registered = await registerProjectViaServer(context, {
|
|
4754
5029
|
repoSlug: repo.slug,
|
|
4755
5030
|
checkout
|
|
4756
5031
|
});
|
|
4757
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
4758
5032
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4759
|
-
if (serverRootSwitch && token) {
|
|
4760
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
|
|
4761
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4762
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4763
|
-
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
|
|
4764
|
-
}
|
|
4765
5033
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
5034
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
5035
|
+
ok: false,
|
|
5036
|
+
ready: false,
|
|
5037
|
+
labelsReady: false,
|
|
5038
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5039
|
+
}));
|
|
4766
5040
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4767
5041
|
remote: true,
|
|
4768
5042
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4793,6 +5067,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4793
5067
|
githubAuth,
|
|
4794
5068
|
deviceAuth,
|
|
4795
5069
|
githubAuthWarning: remoteGhTokenWarning,
|
|
5070
|
+
labelSetup,
|
|
4796
5071
|
pi,
|
|
4797
5072
|
doctor
|
|
4798
5073
|
};
|
|
@@ -4899,12 +5174,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4899
5174
|
});
|
|
4900
5175
|
const serverChoice = await promptSelect(prompts, {
|
|
4901
5176
|
message: "Rig server",
|
|
5177
|
+
initialValue: "remote",
|
|
4902
5178
|
options: [
|
|
4903
|
-
{ value: "
|
|
4904
|
-
{ value: "
|
|
5179
|
+
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
5180
|
+
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
4905
5181
|
]
|
|
4906
5182
|
});
|
|
4907
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder:
|
|
5183
|
+
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
4908
5184
|
let remoteCheckout;
|
|
4909
5185
|
if (serverChoice === "remote") {
|
|
4910
5186
|
const checkout = await promptSelect(prompts, {
|
|
@@ -4936,38 +5212,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4936
5212
|
{ value: "skip", label: "Skip for now" }
|
|
4937
5213
|
]
|
|
4938
5214
|
});
|
|
5215
|
+
let remoteGhTokenConfirmed = false;
|
|
4939
5216
|
if (serverChoice === "remote" && authMethod === "gh") {
|
|
4940
5217
|
if (!prompts.confirm)
|
|
4941
5218
|
throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
|
|
4942
5219
|
const confirmed = await prompts.confirm({
|
|
4943
5220
|
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
4944
|
-
initialValue:
|
|
5221
|
+
initialValue: true
|
|
4945
5222
|
});
|
|
4946
5223
|
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
4947
5224
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4948
5225
|
}
|
|
5226
|
+
remoteGhTokenConfirmed = true;
|
|
4949
5227
|
}
|
|
4950
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
options: [
|
|
4954
|
-
{ value: "off", label: "Off" },
|
|
4955
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4956
|
-
]
|
|
4957
|
-
});
|
|
4958
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4959
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
5228
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
5229
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
5230
|
+
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
4960
5231
|
const result = await runControlPlaneInit(context, {
|
|
4961
5232
|
server: serverChoice,
|
|
4962
5233
|
remoteUrl,
|
|
4963
5234
|
repoSlug,
|
|
4964
|
-
githubToken,
|
|
5235
|
+
githubToken: effectiveGithubToken,
|
|
4965
5236
|
githubAuthMethod: authMethod,
|
|
4966
|
-
githubProject,
|
|
4967
|
-
githubProjectStatusField,
|
|
5237
|
+
githubProject: projectConfig.githubProject,
|
|
5238
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
5239
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
4968
5240
|
remoteCheckout,
|
|
4969
5241
|
repair,
|
|
4970
|
-
privateStateOnly
|
|
5242
|
+
privateStateOnly,
|
|
5243
|
+
yes: remoteGhTokenConfirmed || undefined
|
|
4971
5244
|
});
|
|
4972
5245
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
4973
5246
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
@@ -5063,7 +5336,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
5063
5336
|
}
|
|
5064
5337
|
|
|
5065
5338
|
// packages/cli/src/commands/github.ts
|
|
5066
|
-
import { spawnSync as
|
|
5339
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5067
5340
|
function printPayload(context, payload, fallback) {
|
|
5068
5341
|
if (context.outputMode === "json")
|
|
5069
5342
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -5071,7 +5344,7 @@ function printPayload(context, payload, fallback) {
|
|
|
5071
5344
|
console.log(fallback);
|
|
5072
5345
|
}
|
|
5073
5346
|
function readGhToken() {
|
|
5074
|
-
const result =
|
|
5347
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
5075
5348
|
if (result.status !== 0) {
|
|
5076
5349
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
5077
5350
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -6103,14 +6376,10 @@ async function executeRemote(context, args) {
|
|
|
6103
6376
|
}
|
|
6104
6377
|
|
|
6105
6378
|
// packages/cli/src/commands/run.ts
|
|
6106
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6107
|
-
import { resolve as resolve20 } from "path";
|
|
6108
6379
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6109
6380
|
import {
|
|
6110
6381
|
listAuthorityRuns as listAuthorityRuns3,
|
|
6111
|
-
readAuthorityRun as readAuthorityRun4
|
|
6112
|
-
readJsonlFile as readJsonlFile4,
|
|
6113
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6382
|
+
readAuthorityRun as readAuthorityRun4
|
|
6114
6383
|
} from "@rig/runtime/control-plane/authority-files";
|
|
6115
6384
|
import {
|
|
6116
6385
|
cleanupRunState,
|
|
@@ -6118,6 +6387,7 @@ import {
|
|
|
6118
6387
|
listOpenEpics,
|
|
6119
6388
|
resolveDefaultEpic,
|
|
6120
6389
|
runResume,
|
|
6390
|
+
runRestart,
|
|
6121
6391
|
runStatus,
|
|
6122
6392
|
runStop,
|
|
6123
6393
|
startRun,
|
|
@@ -6125,9 +6395,9 @@ import {
|
|
|
6125
6395
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
6126
6396
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
6127
6397
|
|
|
6128
|
-
// packages/cli/src/commands/_operator-
|
|
6398
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
6129
6399
|
import { createInterface } from "readline";
|
|
6130
|
-
|
|
6400
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
6131
6401
|
var CANONICAL_STAGES = [
|
|
6132
6402
|
"Connect",
|
|
6133
6403
|
"GitHub/task sync",
|
|
@@ -6142,18 +6412,188 @@ var CANONICAL_STAGES = [
|
|
|
6142
6412
|
"Merge",
|
|
6143
6413
|
"Complete"
|
|
6144
6414
|
];
|
|
6415
|
+
function logDetail(log3) {
|
|
6416
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6417
|
+
}
|
|
6418
|
+
function parseProviderProtocolLog(title, detail) {
|
|
6419
|
+
if (title.trim().toLowerCase() !== "agent output")
|
|
6420
|
+
return null;
|
|
6421
|
+
if (!detail.startsWith("{") || !detail.endsWith("}"))
|
|
6422
|
+
return null;
|
|
6423
|
+
try {
|
|
6424
|
+
const record = JSON.parse(detail);
|
|
6425
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
6426
|
+
return null;
|
|
6427
|
+
const type = record.type;
|
|
6428
|
+
return typeof type === "string" && [
|
|
6429
|
+
"assistant",
|
|
6430
|
+
"message_start",
|
|
6431
|
+
"message_update",
|
|
6432
|
+
"message_end",
|
|
6433
|
+
"stream_event",
|
|
6434
|
+
"tool_result",
|
|
6435
|
+
"tool_execution_start",
|
|
6436
|
+
"tool_execution_update",
|
|
6437
|
+
"tool_execution_end",
|
|
6438
|
+
"turn_start",
|
|
6439
|
+
"turn_end"
|
|
6440
|
+
].includes(type) ? record : null;
|
|
6441
|
+
} catch {
|
|
6442
|
+
return null;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
function renderProviderProtocolLog(record) {
|
|
6446
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
6447
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") {
|
|
6448
|
+
const toolName = String(record.toolName ?? record.name ?? "tool");
|
|
6449
|
+
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";
|
|
6450
|
+
return `[Pi tool] ${toolName} ${status}`;
|
|
6451
|
+
}
|
|
6452
|
+
return null;
|
|
6453
|
+
}
|
|
6454
|
+
function entryId(entry, fallback) {
|
|
6455
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6456
|
+
}
|
|
6145
6457
|
function renderOperatorSnapshot(snapshot) {
|
|
6146
6458
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
6147
6459
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
6148
6460
|
const status = String(run.status ?? "unknown");
|
|
6149
6461
|
const logs = snapshot.logs ?? [];
|
|
6462
|
+
const latestByStage = new Map;
|
|
6463
|
+
for (const log3 of logs) {
|
|
6464
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6465
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6466
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6467
|
+
if (stage)
|
|
6468
|
+
latestByStage.set(stage, log3);
|
|
6469
|
+
}
|
|
6150
6470
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
6151
|
-
const match =
|
|
6152
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6471
|
+
const match = latestByStage.get(stage);
|
|
6472
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
6153
6473
|
});
|
|
6154
6474
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
6155
6475
|
`);
|
|
6156
6476
|
}
|
|
6477
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6478
|
+
let lastSnapshot = "";
|
|
6479
|
+
const assistantTextById = new Map;
|
|
6480
|
+
const seenTimeline = new Set;
|
|
6481
|
+
const seenLogs = new Set;
|
|
6482
|
+
const writeLine = (line) => output.write(`${line}
|
|
6483
|
+
`);
|
|
6484
|
+
return {
|
|
6485
|
+
renderSnapshot(snapshot) {
|
|
6486
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6487
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6488
|
+
writeLine(rendered);
|
|
6489
|
+
lastSnapshot = rendered;
|
|
6490
|
+
}
|
|
6491
|
+
},
|
|
6492
|
+
renderTimeline(entries) {
|
|
6493
|
+
for (const [index, entry] of entries.entries()) {
|
|
6494
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6495
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6496
|
+
const text2 = entry.text;
|
|
6497
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6498
|
+
if (!previousText && text2.trim()) {
|
|
6499
|
+
writeLine("[Pi assistant]");
|
|
6500
|
+
}
|
|
6501
|
+
if (text2.startsWith(previousText)) {
|
|
6502
|
+
const delta = text2.slice(previousText.length);
|
|
6503
|
+
if (delta)
|
|
6504
|
+
output.write(delta);
|
|
6505
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6506
|
+
if (previousText)
|
|
6507
|
+
writeLine(`
|
|
6508
|
+
[Pi assistant]`);
|
|
6509
|
+
output.write(text2);
|
|
6510
|
+
}
|
|
6511
|
+
assistantTextById.set(id, text2);
|
|
6512
|
+
continue;
|
|
6513
|
+
}
|
|
6514
|
+
if (seenTimeline.has(id))
|
|
6515
|
+
continue;
|
|
6516
|
+
seenTimeline.add(id);
|
|
6517
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6518
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6519
|
+
continue;
|
|
6520
|
+
}
|
|
6521
|
+
if (entry.type === "timeline_warning") {
|
|
6522
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
},
|
|
6526
|
+
renderLogs(entries) {
|
|
6527
|
+
for (const [index, entry] of entries.entries()) {
|
|
6528
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6529
|
+
if (seenLogs.has(id))
|
|
6530
|
+
continue;
|
|
6531
|
+
seenLogs.add(id);
|
|
6532
|
+
const title = String(entry.title ?? "");
|
|
6533
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6534
|
+
continue;
|
|
6535
|
+
const detail = logDetail(entry);
|
|
6536
|
+
if (!detail)
|
|
6537
|
+
continue;
|
|
6538
|
+
const protocolRecord = parseProviderProtocolLog(title, detail);
|
|
6539
|
+
if (protocolRecord) {
|
|
6540
|
+
const protocolLine = renderProviderProtocolLog(protocolRecord);
|
|
6541
|
+
if (protocolLine)
|
|
6542
|
+
writeLine(protocolLine);
|
|
6543
|
+
continue;
|
|
6544
|
+
}
|
|
6545
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
};
|
|
6549
|
+
}
|
|
6550
|
+
function createOperatorSurface(options = {}) {
|
|
6551
|
+
const input = options.input ?? process.stdin;
|
|
6552
|
+
const output = options.output ?? process.stdout;
|
|
6553
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6554
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6555
|
+
const writeLine = (line) => output.write(`${line}
|
|
6556
|
+
`);
|
|
6557
|
+
return {
|
|
6558
|
+
mode: "pi-compatible-text",
|
|
6559
|
+
...renderer,
|
|
6560
|
+
info: writeLine,
|
|
6561
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6562
|
+
`),
|
|
6563
|
+
attachCommandInput(handler) {
|
|
6564
|
+
if (options.interactive === false || !input.isTTY)
|
|
6565
|
+
return null;
|
|
6566
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6567
|
+
rl.on("line", (line) => {
|
|
6568
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6569
|
+
});
|
|
6570
|
+
return { close: () => rl.close() };
|
|
6571
|
+
}
|
|
6572
|
+
};
|
|
6573
|
+
}
|
|
6574
|
+
function taskId(task) {
|
|
6575
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6576
|
+
}
|
|
6577
|
+
function taskTitle(task) {
|
|
6578
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6579
|
+
}
|
|
6580
|
+
function taskStatus(task) {
|
|
6581
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6582
|
+
}
|
|
6583
|
+
function renderTaskPickerRows(tasks) {
|
|
6584
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6585
|
+
}
|
|
6586
|
+
async function promptForTaskSelection(question) {
|
|
6587
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6588
|
+
try {
|
|
6589
|
+
return await rl.question(question);
|
|
6590
|
+
} finally {
|
|
6591
|
+
rl.close();
|
|
6592
|
+
}
|
|
6593
|
+
}
|
|
6594
|
+
|
|
6595
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6596
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
6157
6597
|
function runStatusFromPayload(payload) {
|
|
6158
6598
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
6159
6599
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -6175,11 +6615,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
6175
6615
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
6176
6616
|
return { action: "continue", message: "Steering message queued." };
|
|
6177
6617
|
}
|
|
6178
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6618
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
6179
6619
|
const run = await getRunDetailsViaServer(context, runId);
|
|
6180
6620
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
6181
|
-
const
|
|
6182
|
-
|
|
6621
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6622
|
+
entries: [{
|
|
6623
|
+
id: `timeline-unavailable:${runId}`,
|
|
6624
|
+
type: "timeline_warning",
|
|
6625
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6626
|
+
createdAt: new Date().toISOString()
|
|
6627
|
+
}],
|
|
6628
|
+
nextCursor: options.timelineCursor ?? null
|
|
6629
|
+
}));
|
|
6630
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6631
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6632
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6633
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
6183
6634
|
}
|
|
6184
6635
|
async function attachRunOperatorView(context, input) {
|
|
6185
6636
|
let steered = false;
|
|
@@ -6187,45 +6638,266 @@ async function attachRunOperatorView(context, input) {
|
|
|
6187
6638
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
6188
6639
|
steered = true;
|
|
6189
6640
|
}
|
|
6641
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
6190
6642
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6191
6643
|
if (context.outputMode === "text") {
|
|
6192
|
-
|
|
6644
|
+
surface.renderSnapshot(snapshot);
|
|
6645
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6646
|
+
surface.renderLogs(snapshot.logs);
|
|
6193
6647
|
if (steered)
|
|
6194
|
-
|
|
6648
|
+
surface.info("Steering message queued.");
|
|
6195
6649
|
}
|
|
6196
6650
|
let detached = false;
|
|
6197
|
-
let
|
|
6651
|
+
let commandInput = null;
|
|
6198
6652
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
6199
6653
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
}
|
|
6210
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6654
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6655
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6656
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6657
|
+
if (result.message)
|
|
6658
|
+
surface.info(result.message);
|
|
6659
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6660
|
+
detached = true;
|
|
6661
|
+
commandInput?.close();
|
|
6662
|
+
}
|
|
6211
6663
|
});
|
|
6212
6664
|
}
|
|
6213
|
-
let lastRendered = snapshot.rendered;
|
|
6214
6665
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6666
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6215
6667
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6216
6668
|
await Bun.sleep(pollMs);
|
|
6217
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6669
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6670
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6671
|
+
surface.renderSnapshot(snapshot);
|
|
6672
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6673
|
+
surface.renderLogs(snapshot.logs);
|
|
6222
6674
|
}
|
|
6223
|
-
|
|
6675
|
+
commandInput?.close();
|
|
6224
6676
|
}
|
|
6225
6677
|
return { ...snapshot, steered, detached };
|
|
6226
6678
|
}
|
|
6227
6679
|
|
|
6680
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
6681
|
+
import pc3 from "picocolors";
|
|
6682
|
+
function stringField(record, key, fallback = "") {
|
|
6683
|
+
const value = record[key];
|
|
6684
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
6685
|
+
}
|
|
6686
|
+
function arrayField(record, key) {
|
|
6687
|
+
const value = record[key];
|
|
6688
|
+
return Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.trim() ? [entry.trim()] : []) : [];
|
|
6689
|
+
}
|
|
6690
|
+
function rawObject(record) {
|
|
6691
|
+
const raw = record.raw;
|
|
6692
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
6693
|
+
}
|
|
6694
|
+
function truncate(value, width) {
|
|
6695
|
+
if (value.length <= width)
|
|
6696
|
+
return value;
|
|
6697
|
+
if (width <= 1)
|
|
6698
|
+
return "\u2026";
|
|
6699
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
6700
|
+
}
|
|
6701
|
+
function pad(value, width) {
|
|
6702
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
6703
|
+
}
|
|
6704
|
+
function statusColor(status) {
|
|
6705
|
+
const normalized = status.toLowerCase();
|
|
6706
|
+
if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
|
|
6707
|
+
return pc3.green;
|
|
6708
|
+
if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
|
|
6709
|
+
return pc3.red;
|
|
6710
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
|
|
6711
|
+
return pc3.cyan;
|
|
6712
|
+
if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
|
|
6713
|
+
return pc3.yellow;
|
|
6714
|
+
return pc3.dim;
|
|
6715
|
+
}
|
|
6716
|
+
function formatTaskList(tasks, options = {}) {
|
|
6717
|
+
if (tasks.length === 0)
|
|
6718
|
+
return pc3.dim("No matching tasks.");
|
|
6719
|
+
if (options.raw)
|
|
6720
|
+
return tasks.map((task) => JSON.stringify(task)).join(`
|
|
6721
|
+
`);
|
|
6722
|
+
const rows = tasks.map((task) => {
|
|
6723
|
+
const raw = rawObject(task);
|
|
6724
|
+
const id = stringField(task, "id", "<unknown>");
|
|
6725
|
+
const status = stringField(task, "status", "unknown");
|
|
6726
|
+
const title = stringField(task, "title", "Untitled task");
|
|
6727
|
+
const source = stringField(task, "source", stringField(raw, "source", ""));
|
|
6728
|
+
const labels = arrayField(task, "labels").length > 0 ? arrayField(task, "labels") : arrayField(raw, "labels");
|
|
6729
|
+
return { id, status, title, source, labels };
|
|
6730
|
+
});
|
|
6731
|
+
const idWidth = Math.min(18, Math.max(4, ...rows.map((row) => row.id.length)));
|
|
6732
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6733
|
+
const header = `${pc3.bold(pad("TASK", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6734
|
+
const body = rows.map((row) => {
|
|
6735
|
+
const labels = row.labels.length > 0 ? pc3.dim(` ${row.labels.slice(0, 4).map((label) => `#${label}`).join(" ")}`) : "";
|
|
6736
|
+
const source = row.source ? pc3.dim(` ${row.source}`) : "";
|
|
6737
|
+
return [
|
|
6738
|
+
pc3.bold(pad(truncate(row.id, idWidth), idWidth)),
|
|
6739
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6740
|
+
`${row.title}${labels}${source}`
|
|
6741
|
+
].join(" ");
|
|
6742
|
+
});
|
|
6743
|
+
return [pc3.bold("Rig tasks"), header, ...body].join(`
|
|
6744
|
+
`);
|
|
6745
|
+
}
|
|
6746
|
+
function formatRunList(runs, options = {}) {
|
|
6747
|
+
if (runs.length === 0) {
|
|
6748
|
+
return pc3.dim(options.source === "server" ? "No runs recorded on the selected Rig server." : "No runs recorded in .rig/runs.");
|
|
6749
|
+
}
|
|
6750
|
+
const rows = runs.map((run) => {
|
|
6751
|
+
const runId = stringField(run, "runId", stringField(run, "id", "(unknown-run)"));
|
|
6752
|
+
const status = stringField(run, "status", "unknown");
|
|
6753
|
+
const taskId2 = stringField(run, "taskId", "");
|
|
6754
|
+
const title = stringField(run, "title", taskId2 || "(untitled)");
|
|
6755
|
+
const runtime = stringField(run, "runtimeAdapter", "");
|
|
6756
|
+
return { runId, status, title, runtime };
|
|
6757
|
+
});
|
|
6758
|
+
const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
|
|
6759
|
+
const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
|
|
6760
|
+
const header = `${pc3.bold(pad("RUN", idWidth))} ${pc3.bold(pad("STATUS", statusWidth))} ${pc3.bold("TITLE")}`;
|
|
6761
|
+
const body = rows.map((row) => [
|
|
6762
|
+
pc3.bold(pad(truncate(row.runId, idWidth), idWidth)),
|
|
6763
|
+
statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
|
|
6764
|
+
`${row.title}${row.runtime ? pc3.dim(` ${row.runtime}`) : ""}`
|
|
6765
|
+
].join(" "));
|
|
6766
|
+
return [pc3.bold(options.source === "server" ? "Rig runs (server)" : "Rig runs"), header, ...body].join(`
|
|
6767
|
+
`);
|
|
6768
|
+
}
|
|
6769
|
+
function formatSubmittedRun(input) {
|
|
6770
|
+
const lines = [`${pc3.green("Run submitted")}: ${pc3.bold(input.runId)}`];
|
|
6771
|
+
if (input.task) {
|
|
6772
|
+
const id = stringField(input.task, "id", "<unknown>");
|
|
6773
|
+
const status = stringField(input.task, "status", "unknown");
|
|
6774
|
+
const title = stringField(input.task, "title", "Untitled task");
|
|
6775
|
+
lines.push(`${pc3.dim("task")} ${pc3.bold(id)} ${statusColor(status)(status)} ${title}`);
|
|
6776
|
+
}
|
|
6777
|
+
return lines.join(`
|
|
6778
|
+
`);
|
|
6779
|
+
}
|
|
6780
|
+
|
|
6781
|
+
// packages/cli/src/commands/_pi-session.ts
|
|
6782
|
+
import { spawn as spawn2 } from "child_process";
|
|
6783
|
+
function buildPiRigSessionEnv(input) {
|
|
6784
|
+
return {
|
|
6785
|
+
RIG_PROJECT_ROOT: input.projectRoot,
|
|
6786
|
+
PROJECT_RIG_ROOT: input.projectRoot,
|
|
6787
|
+
RIG_RUN_ID: input.runId,
|
|
6788
|
+
RIG_SERVER_RUN_ID: input.runId,
|
|
6789
|
+
RIG_RUNTIME_ADAPTER: "pi",
|
|
6790
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
6791
|
+
RIG_SERVER_BASE_URL: input.serverUrl,
|
|
6792
|
+
RIG_STEERING_POLL_MS: process.env.RIG_STEERING_POLL_MS?.trim() || "1000",
|
|
6793
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
6794
|
+
...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
|
|
6795
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {}
|
|
6796
|
+
};
|
|
6797
|
+
}
|
|
6798
|
+
function shellBinary(name) {
|
|
6799
|
+
const explicit = process.env.RIG_PI_BINARY?.trim();
|
|
6800
|
+
if (explicit)
|
|
6801
|
+
return explicit;
|
|
6802
|
+
return Bun.which(name) || name;
|
|
6803
|
+
}
|
|
6804
|
+
function buildPiRigSessionCommand(input) {
|
|
6805
|
+
const configuredExtension = input.extensionSource ?? process.env.RIG_PI_RIG_EXTENSION_SOURCE?.trim();
|
|
6806
|
+
const extensionSource = configuredExtension && configuredExtension.length > 0 ? configuredExtension : resolvePiRigPackageSource(input.projectRoot);
|
|
6807
|
+
const initialCommand = `/rig attach ${input.runId}`;
|
|
6808
|
+
return [
|
|
6809
|
+
shellBinary("pi"),
|
|
6810
|
+
"--no-extensions",
|
|
6811
|
+
"--extension",
|
|
6812
|
+
extensionSource,
|
|
6813
|
+
initialCommand
|
|
6814
|
+
];
|
|
6815
|
+
}
|
|
6816
|
+
async function launchPiRigSession(context, input) {
|
|
6817
|
+
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
6818
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6819
|
+
}
|
|
6820
|
+
if (process.env.RIG_DISABLE_PI_LAUNCH === "1") {
|
|
6821
|
+
return { launched: false, exitCode: null, command: [] };
|
|
6822
|
+
}
|
|
6823
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
6824
|
+
const command = buildPiRigSessionCommand({ ...input, projectRoot: context.projectRoot });
|
|
6825
|
+
const env = {
|
|
6826
|
+
...process.env,
|
|
6827
|
+
...buildPiRigSessionEnv({
|
|
6828
|
+
projectRoot: context.projectRoot,
|
|
6829
|
+
runId: input.runId,
|
|
6830
|
+
taskId: input.taskId,
|
|
6831
|
+
serverUrl: server.baseUrl,
|
|
6832
|
+
authToken: server.authToken
|
|
6833
|
+
})
|
|
6834
|
+
};
|
|
6835
|
+
process.stdout.write(`Launching Pi for Rig run ${input.runId}\u2026
|
|
6836
|
+
`);
|
|
6837
|
+
process.stdout.write(`Pi command: ${formatCommand(command)}
|
|
6838
|
+
`);
|
|
6839
|
+
const launchedAt = Date.now();
|
|
6840
|
+
const child = spawn2(command[0], command.slice(1), {
|
|
6841
|
+
cwd: context.projectRoot,
|
|
6842
|
+
env,
|
|
6843
|
+
stdio: "inherit"
|
|
6844
|
+
});
|
|
6845
|
+
const launchError = await new Promise((resolve20) => {
|
|
6846
|
+
child.once("error", (error) => {
|
|
6847
|
+
resolve20({ error: error.message });
|
|
6848
|
+
});
|
|
6849
|
+
child.once("close", (code) => resolve20({ code }));
|
|
6850
|
+
});
|
|
6851
|
+
if ("error" in launchError) {
|
|
6852
|
+
process.stderr.write(`Failed to launch Pi; falling back to Rig attach view: ${launchError.error}
|
|
6853
|
+
`);
|
|
6854
|
+
return { launched: false, exitCode: null, command, error: launchError.error };
|
|
6855
|
+
}
|
|
6856
|
+
const exitCode = launchError.code;
|
|
6857
|
+
const elapsedMs = Date.now() - launchedAt;
|
|
6858
|
+
if (typeof exitCode === "number" && exitCode !== 0 && elapsedMs < 5000) {
|
|
6859
|
+
const error = `Pi exited during startup with code ${exitCode}.`;
|
|
6860
|
+
process.stderr.write(`${error} Falling back to Rig attach view.
|
|
6861
|
+
`);
|
|
6862
|
+
return { launched: false, exitCode, command, error };
|
|
6863
|
+
}
|
|
6864
|
+
return { launched: true, exitCode, command };
|
|
6865
|
+
}
|
|
6866
|
+
|
|
6228
6867
|
// packages/cli/src/commands/run.ts
|
|
6868
|
+
function normalizeRemoteRunDetails(payload) {
|
|
6869
|
+
const run = payload.run;
|
|
6870
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
6871
|
+
return null;
|
|
6872
|
+
return {
|
|
6873
|
+
...run,
|
|
6874
|
+
...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
|
|
6875
|
+
...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
|
|
6876
|
+
...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
6880
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
6881
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
6882
|
+
}
|
|
6883
|
+
async function listRunsForSelectedConnection(context, options = {}) {
|
|
6884
|
+
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
6885
|
+
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
6886
|
+
}
|
|
6887
|
+
return { runs: listAuthorityRuns3(context.projectRoot), source: "local" };
|
|
6888
|
+
}
|
|
6889
|
+
function runStringField(run, key, fallback = "") {
|
|
6890
|
+
const value = run[key];
|
|
6891
|
+
return typeof value === "string" && value.trim() ? value : fallback;
|
|
6892
|
+
}
|
|
6893
|
+
function runDisplayTitle(run) {
|
|
6894
|
+
return runStringField(run, "title", runStringField(run, "taskId", "(untitled)"));
|
|
6895
|
+
}
|
|
6896
|
+
function buildServerRunStatus(runs) {
|
|
6897
|
+
const activeRuns = runs.filter((run) => !REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6898
|
+
const recentRuns = runs.filter((run) => REMOTE_TERMINAL_RUN_STATUSES.has(runStringField(run, "status").toLowerCase()));
|
|
6899
|
+
return { activeRuns, recentRuns, runs };
|
|
6900
|
+
}
|
|
6229
6901
|
function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
|
|
6230
6902
|
if (noEpicPrompt) {
|
|
6231
6903
|
return false;
|
|
@@ -6291,17 +6963,11 @@ async function executeRun(context, args) {
|
|
|
6291
6963
|
switch (command) {
|
|
6292
6964
|
case "list": {
|
|
6293
6965
|
requireNoExtraArgs(rest, "bun run rig run list");
|
|
6294
|
-
const runs =
|
|
6966
|
+
const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
|
|
6295
6967
|
if (context.outputMode === "text") {
|
|
6296
|
-
|
|
6297
|
-
console.log("No runs recorded in .rig/runs.");
|
|
6298
|
-
} else {
|
|
6299
|
-
for (const run of runs) {
|
|
6300
|
-
console.log(`- ${run.runId} \xB7 ${run.status} \xB7 ${run.title}`);
|
|
6301
|
-
}
|
|
6302
|
-
}
|
|
6968
|
+
console.log(formatRunList(runs, { source }));
|
|
6303
6969
|
}
|
|
6304
|
-
return { ok: true, group: "run", command, details: { runs } };
|
|
6970
|
+
return { ok: true, group: "run", command, details: { runs, source } };
|
|
6305
6971
|
}
|
|
6306
6972
|
case "delete": {
|
|
6307
6973
|
let pending = rest;
|
|
@@ -6364,7 +7030,7 @@ async function executeRun(context, args) {
|
|
|
6364
7030
|
if (!run.value) {
|
|
6365
7031
|
throw new CliError2("run show requires --run <id>.");
|
|
6366
7032
|
}
|
|
6367
|
-
const record = readAuthorityRun4(context.projectRoot, run.value);
|
|
7033
|
+
const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
|
|
6368
7034
|
if (!record) {
|
|
6369
7035
|
throw new CliError2(`Run not found: ${run.value}`, 2);
|
|
6370
7036
|
}
|
|
@@ -6383,34 +7049,24 @@ async function executeRun(context, args) {
|
|
|
6383
7049
|
if (!run.value) {
|
|
6384
7050
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6385
7051
|
}
|
|
6386
|
-
const
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
return events2;
|
|
6395
|
-
};
|
|
6396
|
-
const events = printEvents();
|
|
7052
|
+
const renderer = createPiRunStreamRenderer();
|
|
7053
|
+
let cursor = null;
|
|
7054
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
7055
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
7056
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
7057
|
+
if (context.outputMode === "text") {
|
|
7058
|
+
renderer.renderTimeline(events);
|
|
7059
|
+
}
|
|
6397
7060
|
if (follow.value && context.outputMode === "text") {
|
|
6398
|
-
let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6399
7061
|
while (true) {
|
|
6400
7062
|
await Bun.sleep(1000);
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
continue;
|
|
6406
|
-
const delta = next.slice(lastLength);
|
|
6407
|
-
lastLength = next.length;
|
|
6408
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6409
|
-
console.log(line);
|
|
6410
|
-
}
|
|
7063
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
7064
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
7065
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
7066
|
+
renderer.renderTimeline(nextEvents);
|
|
6411
7067
|
}
|
|
6412
7068
|
}
|
|
6413
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
7069
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6414
7070
|
}
|
|
6415
7071
|
case "attach": {
|
|
6416
7072
|
let pending = rest;
|
|
@@ -6431,14 +7087,26 @@ async function executeRun(context, args) {
|
|
|
6431
7087
|
if (!runId) {
|
|
6432
7088
|
throw new CliError2("run attach requires a run id.", 2);
|
|
6433
7089
|
}
|
|
7090
|
+
let steered = false;
|
|
7091
|
+
const shouldTryPiAttach = context.outputMode === "text" && follow.value && !once.value && Boolean(process.stdin.isTTY && process.stdout.isTTY) && process.env.RIG_DISABLE_PI_LAUNCH !== "1";
|
|
7092
|
+
if (shouldTryPiAttach && messageOption.value?.trim()) {
|
|
7093
|
+
await steerRunViaServer(context, runId, messageOption.value.trim());
|
|
7094
|
+
steered = true;
|
|
7095
|
+
}
|
|
7096
|
+
if (shouldTryPiAttach) {
|
|
7097
|
+
const piSession = await launchPiRigSession(context, { runId });
|
|
7098
|
+
if (piSession.launched) {
|
|
7099
|
+
return { ok: true, group: "run", command, details: { runId, steered, mode: "pi", ...piSession } };
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
6434
7102
|
const attached = await attachRunOperatorView(context, {
|
|
6435
7103
|
runId,
|
|
6436
|
-
message: messageOption.value ?? null,
|
|
7104
|
+
message: shouldTryPiAttach ? null : messageOption.value ?? null,
|
|
6437
7105
|
once: once.value,
|
|
6438
7106
|
follow: follow.value,
|
|
6439
7107
|
pollMs: parsePositiveInt(pollMs.value, "--poll-ms", 2000)
|
|
6440
7108
|
});
|
|
6441
|
-
return { ok: true, group: "run", command, details: attached };
|
|
7109
|
+
return { ok: true, group: "run", command, details: { ...attached, steered: attached.steered || steered } };
|
|
6442
7110
|
}
|
|
6443
7111
|
case "status": {
|
|
6444
7112
|
requireNoExtraArgs(rest, "bun run rig run status");
|
|
@@ -6448,17 +7116,19 @@ async function executeRun(context, args) {
|
|
|
6448
7116
|
}
|
|
6449
7117
|
return { ok: true, group: "run", command };
|
|
6450
7118
|
}
|
|
6451
|
-
const summary = runStatus(context.projectRoot, runtimeContext);
|
|
7119
|
+
const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
|
|
7120
|
+
const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
7121
|
+
const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
6452
7122
|
if (context.outputMode === "text") {
|
|
6453
|
-
console.log(`Active runs: ${
|
|
6454
|
-
for (const run of
|
|
6455
|
-
console.log(`- ${run
|
|
7123
|
+
console.log(`Active runs: ${activeRuns.length}`);
|
|
7124
|
+
for (const run of activeRuns) {
|
|
7125
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6456
7126
|
}
|
|
6457
|
-
if (
|
|
7127
|
+
if (recentRuns.length > 0) {
|
|
6458
7128
|
console.log("");
|
|
6459
7129
|
console.log("Recent runs:");
|
|
6460
|
-
for (const run of
|
|
6461
|
-
console.log(`- ${run
|
|
7130
|
+
for (const run of recentRuns) {
|
|
7131
|
+
console.log(`- ${runStringField(run, "runId", "(unknown-run)")} \xB7 ${runStringField(run, "status", "unknown")} \xB7 ${runStringField(run, "taskId", runDisplayTitle(run))}`);
|
|
6462
7132
|
}
|
|
6463
7133
|
}
|
|
6464
7134
|
}
|
|
@@ -6545,6 +7215,20 @@ async function executeRun(context, args) {
|
|
|
6545
7215
|
}
|
|
6546
7216
|
return { ok: true, group: "run", command, details: resumed };
|
|
6547
7217
|
}
|
|
7218
|
+
case "restart": {
|
|
7219
|
+
requireNoExtraArgs(rest, "bun run rig run restart");
|
|
7220
|
+
if (context.dryRun) {
|
|
7221
|
+
if (context.outputMode === "text") {
|
|
7222
|
+
console.log("[dry-run] rig run restart");
|
|
7223
|
+
}
|
|
7224
|
+
return { ok: true, group: "run", command };
|
|
7225
|
+
}
|
|
7226
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
7227
|
+
if (context.outputMode === "text") {
|
|
7228
|
+
console.log(`Restarted run: ${restarted.runId}`);
|
|
7229
|
+
}
|
|
7230
|
+
return { ok: true, group: "run", command, details: restarted };
|
|
7231
|
+
}
|
|
6548
7232
|
case "stop": {
|
|
6549
7233
|
const runOption = takeOption(rest, "--run");
|
|
6550
7234
|
const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
|
|
@@ -6602,7 +7286,7 @@ async function executeServer(context, args, options) {
|
|
|
6602
7286
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
6603
7287
|
pending = authTokenResult.rest;
|
|
6604
7288
|
requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
6605
|
-
const commandParts = ["
|
|
7289
|
+
const commandParts = ["rig-server", "start"];
|
|
6606
7290
|
if (hostResult.value) {
|
|
6607
7291
|
commandParts.push("--host", hostResult.value);
|
|
6608
7292
|
}
|
|
@@ -6625,7 +7309,7 @@ async function executeServer(context, args, options) {
|
|
|
6625
7309
|
const eventResult = takeOption(pending, "--event");
|
|
6626
7310
|
pending = eventResult.rest;
|
|
6627
7311
|
requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
|
|
6628
|
-
const commandParts = ["
|
|
7312
|
+
const commandParts = ["rig-server", "notify-test"];
|
|
6629
7313
|
if (eventResult.value) {
|
|
6630
7314
|
commandParts.push("--event", eventResult.value);
|
|
6631
7315
|
}
|
|
@@ -6686,10 +7370,10 @@ async function executeServer(context, args, options) {
|
|
|
6686
7370
|
}
|
|
6687
7371
|
|
|
6688
7372
|
// packages/cli/src/commands/task.ts
|
|
6689
|
-
import { readFileSync as
|
|
6690
|
-
import { spawnSync as
|
|
6691
|
-
import {
|
|
6692
|
-
import {
|
|
7373
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7374
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7375
|
+
import { resolve as resolve20 } from "path";
|
|
7376
|
+
import { cancel as cancel3, confirm as confirm2, isCancel as isCancel3 } from "@clack/prompts";
|
|
6693
7377
|
import {
|
|
6694
7378
|
taskArtifactDir,
|
|
6695
7379
|
taskArtifacts,
|
|
@@ -6707,19 +7391,10 @@ import {
|
|
|
6707
7391
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6708
7392
|
|
|
6709
7393
|
// packages/cli/src/commands/_task-picker.ts
|
|
6710
|
-
import {
|
|
6711
|
-
function
|
|
7394
|
+
import { cancel as cancel2, isCancel as isCancel2, select as select2 } from "@clack/prompts";
|
|
7395
|
+
function taskId2(task) {
|
|
6712
7396
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6713
7397
|
}
|
|
6714
|
-
function taskTitle(task) {
|
|
6715
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6716
|
-
}
|
|
6717
|
-
function taskStatus(task) {
|
|
6718
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6719
|
-
}
|
|
6720
|
-
function renderTaskPickerRows(tasks) {
|
|
6721
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6722
|
-
}
|
|
6723
7398
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6724
7399
|
if (tasks.length === 0)
|
|
6725
7400
|
return null;
|
|
@@ -6729,25 +7404,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6729
7404
|
if (!isTty) {
|
|
6730
7405
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6731
7406
|
}
|
|
6732
|
-
|
|
6733
|
-
const
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
7407
|
+
if (io.prompt || io.renderer) {
|
|
7408
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
7409
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
7410
|
+
`) };
|
|
7411
|
+
renderer.writeLine("Select Rig task:");
|
|
7412
|
+
for (const row of renderTaskPickerRows(tasks))
|
|
7413
|
+
renderer.writeLine(` ${row}`);
|
|
7414
|
+
const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
7415
|
+
if (!answer2)
|
|
7416
|
+
return null;
|
|
7417
|
+
if (/^\d+$/.test(answer2)) {
|
|
7418
|
+
const index2 = Number.parseInt(answer2, 10) - 1;
|
|
7419
|
+
return tasks[index2] ?? null;
|
|
6738
7420
|
}
|
|
7421
|
+
return tasks.find((task) => taskId2(task) === answer2) ?? null;
|
|
7422
|
+
}
|
|
7423
|
+
const options = tasks.map((task, index2) => ({
|
|
7424
|
+
value: `${index2}`,
|
|
7425
|
+
label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
|
|
7426
|
+
hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
|
|
7427
|
+
}));
|
|
7428
|
+
const answer = await select2({
|
|
7429
|
+
message: "Select Rig task",
|
|
7430
|
+
options
|
|
6739
7431
|
});
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
console.log(` ${row}`);
|
|
6743
|
-
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6744
|
-
if (!answer)
|
|
7432
|
+
if (isCancel2(answer)) {
|
|
7433
|
+
cancel2("No task selected.");
|
|
6745
7434
|
return null;
|
|
6746
|
-
if (/^\d+$/.test(answer)) {
|
|
6747
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
6748
|
-
return tasks[index] ?? null;
|
|
6749
7435
|
}
|
|
6750
|
-
|
|
7436
|
+
const index = Number.parseInt(String(answer), 10);
|
|
7437
|
+
return Number.isFinite(index) ? tasks[index] ?? null : null;
|
|
6751
7438
|
}
|
|
6752
7439
|
|
|
6753
7440
|
// packages/cli/src/commands/task.ts
|
|
@@ -6825,7 +7512,7 @@ function normalizePrMode(value) {
|
|
|
6825
7512
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6826
7513
|
}
|
|
6827
7514
|
function detectLocalDirtyState(projectRoot) {
|
|
6828
|
-
const result =
|
|
7515
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6829
7516
|
if (result.status !== 0)
|
|
6830
7517
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6831
7518
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6859,13 +7546,15 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6859
7546
|
if (explicit)
|
|
6860
7547
|
return { mode: explicit, state };
|
|
6861
7548
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6862
|
-
const
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
7549
|
+
const answer = await confirm2({
|
|
7550
|
+
message: "Include current uncommitted changes in run baseline?",
|
|
7551
|
+
initialValue: false
|
|
7552
|
+
});
|
|
7553
|
+
if (isCancel3(answer)) {
|
|
7554
|
+
cancel3("Run cancelled.");
|
|
7555
|
+
throw new CliError2("Run cancelled by user.", 1);
|
|
6868
7556
|
}
|
|
7557
|
+
return { mode: answer ? "dirty-snapshot" : "head", state };
|
|
6869
7558
|
}
|
|
6870
7559
|
return { mode: "head", state };
|
|
6871
7560
|
}
|
|
@@ -6900,10 +7589,7 @@ function summarizeTask(task, options = {}) {
|
|
|
6900
7589
|
};
|
|
6901
7590
|
}
|
|
6902
7591
|
function printTaskSummary(task) {
|
|
6903
|
-
|
|
6904
|
-
const title = readTaskString(task, "title") ?? "Untitled task";
|
|
6905
|
-
const status = readTaskString(task, "status") ?? "unknown";
|
|
6906
|
-
console.log(`- ${id} \xB7 ${status} \xB7 ${title}`);
|
|
7592
|
+
console.log(formatTaskList([task]));
|
|
6907
7593
|
}
|
|
6908
7594
|
async function validatorRegistryForTaskCommands(projectRoot) {
|
|
6909
7595
|
return buildPluginHostContext(projectRoot).then((ctx) => ctx?.validatorRegistry ?? undefined).catch(() => {
|
|
@@ -6921,16 +7607,8 @@ async function executeTask(context, args, options) {
|
|
|
6921
7607
|
requireNoExtraArgs(remaining, "bun run rig task list [--raw] [--assignee <login|@me>] [--assigned-to <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
6922
7608
|
const tasks = await listWorkspaceTasksViaServer(context, filters);
|
|
6923
7609
|
if (context.outputMode === "text") {
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
} else {
|
|
6927
|
-
for (const task of tasks) {
|
|
6928
|
-
if (rawResult.value)
|
|
6929
|
-
console.log(JSON.stringify(summarizeTask(task, { raw: true })));
|
|
6930
|
-
else
|
|
6931
|
-
printTaskSummary(task);
|
|
6932
|
-
}
|
|
6933
|
-
}
|
|
7610
|
+
const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
|
|
7611
|
+
console.log(formatTaskList(renderedTasks, { raw: rawResult.value }));
|
|
6934
7612
|
}
|
|
6935
7613
|
return {
|
|
6936
7614
|
ok: true,
|
|
@@ -6944,12 +7622,12 @@ async function executeTask(context, args, options) {
|
|
|
6944
7622
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
6945
7623
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
6946
7624
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
6947
|
-
const
|
|
6948
|
-
if (!
|
|
7625
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7626
|
+
if (!taskId3)
|
|
6949
7627
|
throw new CliError2("task show requires a task id.", 2);
|
|
6950
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7628
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
6951
7629
|
if (!task)
|
|
6952
|
-
throw new CliError2(`Task not found: ${
|
|
7630
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
6953
7631
|
const summary = summarizeTask(task, { raw: true });
|
|
6954
7632
|
if (context.outputMode === "text")
|
|
6955
7633
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -7020,7 +7698,7 @@ async function executeTask(context, args, options) {
|
|
|
7020
7698
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
7021
7699
|
let content;
|
|
7022
7700
|
if (fileFlag.value) {
|
|
7023
|
-
content =
|
|
7701
|
+
content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
|
|
7024
7702
|
} else {
|
|
7025
7703
|
content = await readStdin();
|
|
7026
7704
|
}
|
|
@@ -7149,16 +7827,23 @@ async function executeTask(context, args, options) {
|
|
|
7149
7827
|
});
|
|
7150
7828
|
let attachDetails = null;
|
|
7151
7829
|
if (!detachResult.value && context.outputMode === "text") {
|
|
7152
|
-
console.log(
|
|
7153
|
-
if (
|
|
7154
|
-
|
|
7830
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
7831
|
+
if (runtimeAdapter === "pi") {
|
|
7832
|
+
const piSession = await launchPiRigSession(context, {
|
|
7833
|
+
runId: submitted.runId,
|
|
7834
|
+
taskId: selectedTaskId,
|
|
7835
|
+
title: titleResult.value ?? readTaskString(selectedTask ?? {}, "title"),
|
|
7836
|
+
runtimeAdapter
|
|
7837
|
+
});
|
|
7838
|
+
attachDetails = { mode: "pi", ...piSession };
|
|
7839
|
+
if (!piSession.launched) {
|
|
7840
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7841
|
+
}
|
|
7842
|
+
} else {
|
|
7843
|
+
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7155
7844
|
}
|
|
7156
|
-
attachDetails = await attachRunOperatorView(context, { runId: submitted.runId, follow: true });
|
|
7157
7845
|
} else if (context.outputMode === "text") {
|
|
7158
|
-
console.log(
|
|
7159
|
-
if (selectedTask) {
|
|
7160
|
-
printTaskSummary(selectedTask);
|
|
7161
|
-
}
|
|
7846
|
+
console.log(formatSubmittedRun({ runId: submitted.runId, task: selectedTask ? summarizeTask(selectedTask) : null }));
|
|
7162
7847
|
}
|
|
7163
7848
|
return {
|
|
7164
7849
|
ok: true,
|
|
@@ -7241,9 +7926,9 @@ async function executeTask(context, args, options) {
|
|
|
7241
7926
|
}
|
|
7242
7927
|
|
|
7243
7928
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7244
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7245
|
-
import { resolve as
|
|
7246
|
-
import { spawn as
|
|
7929
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7930
|
+
import { resolve as resolve21 } from "path";
|
|
7931
|
+
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
7247
7932
|
import { createInterface as createLineInterface } from "readline";
|
|
7248
7933
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7249
7934
|
import {
|
|
@@ -7268,15 +7953,31 @@ import {
|
|
|
7268
7953
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7269
7954
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7270
7955
|
import {
|
|
7271
|
-
buildTaskRunLifecycleComment
|
|
7272
|
-
updateConfiguredTaskSourceTask
|
|
7956
|
+
buildTaskRunLifecycleComment
|
|
7273
7957
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7274
7958
|
import {
|
|
7275
7959
|
closeIssueAfterMergedPr,
|
|
7276
7960
|
commitRunChanges,
|
|
7277
7961
|
runPrAutomation
|
|
7278
7962
|
} from "@rig/runtime/control-plane/native/pr-automation";
|
|
7963
|
+
function looksLikeGitHubToken(value) {
|
|
7964
|
+
const token = value?.trim();
|
|
7965
|
+
if (!token)
|
|
7966
|
+
return false;
|
|
7967
|
+
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
7968
|
+
}
|
|
7969
|
+
function githubBridgeEnv(token) {
|
|
7970
|
+
const clean = token?.trim();
|
|
7971
|
+
if (!clean)
|
|
7972
|
+
return {};
|
|
7973
|
+
return {
|
|
7974
|
+
RIG_GITHUB_TOKEN: clean,
|
|
7975
|
+
GITHUB_TOKEN: clean,
|
|
7976
|
+
GH_TOKEN: clean
|
|
7977
|
+
};
|
|
7978
|
+
}
|
|
7279
7979
|
function buildPiRigBridgeEnv(input) {
|
|
7980
|
+
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
7280
7981
|
return {
|
|
7281
7982
|
RIG_PROJECT_ROOT: input.projectRoot,
|
|
7282
7983
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
@@ -7286,11 +7987,11 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7286
7987
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
7287
7988
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
7288
7989
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
7289
|
-
...
|
|
7990
|
+
...githubBridgeEnv(githubToken)
|
|
7290
7991
|
};
|
|
7291
7992
|
}
|
|
7292
7993
|
function runGitSync(cwd, args, input) {
|
|
7293
|
-
const result =
|
|
7994
|
+
const result = spawnSync4("git", [...args], {
|
|
7294
7995
|
cwd,
|
|
7295
7996
|
input,
|
|
7296
7997
|
encoding: "utf8",
|
|
@@ -7308,12 +8009,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7308
8009
|
return 0;
|
|
7309
8010
|
let copied = 0;
|
|
7310
8011
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7311
|
-
const sourcePath =
|
|
7312
|
-
const targetPath =
|
|
8012
|
+
const sourcePath = resolve21(sourceRoot, relativePath);
|
|
8013
|
+
const targetPath = resolve21(targetRoot, relativePath);
|
|
7313
8014
|
try {
|
|
7314
8015
|
if (!statSync2(sourcePath).isFile())
|
|
7315
8016
|
continue;
|
|
7316
|
-
mkdirSync8(
|
|
8017
|
+
mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
|
|
7317
8018
|
copyFileSync3(sourcePath, targetPath);
|
|
7318
8019
|
copied += 1;
|
|
7319
8020
|
} catch {}
|
|
@@ -7352,7 +8053,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7352
8053
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7353
8054
|
return {
|
|
7354
8055
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7355
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
8056
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7356
8057
|
};
|
|
7357
8058
|
}
|
|
7358
8059
|
function positiveInt(value, fallback) {
|
|
@@ -7360,7 +8061,7 @@ function positiveInt(value, fallback) {
|
|
|
7360
8061
|
}
|
|
7361
8062
|
function resolveTaskRunAutomationLimits(config, env = process.env) {
|
|
7362
8063
|
const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
|
|
7363
|
-
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations,
|
|
8064
|
+
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
|
|
7364
8065
|
return {
|
|
7365
8066
|
maxValidationAttempts: parsePositiveInt2(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
|
|
7366
8067
|
maxPrFixIterations: configuredPrFixIterations
|
|
@@ -7455,7 +8156,7 @@ async function runCheckedCommand(command, args, cwd, label = "git") {
|
|
|
7455
8156
|
}
|
|
7456
8157
|
function createCommandRunner(binary) {
|
|
7457
8158
|
return async (args, options) => {
|
|
7458
|
-
const child =
|
|
8159
|
+
const child = spawn3(binary, [...args], {
|
|
7459
8160
|
cwd: options?.cwd,
|
|
7460
8161
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7461
8162
|
});
|
|
@@ -7463,9 +8164,9 @@ function createCommandRunner(binary) {
|
|
|
7463
8164
|
const stderrChunks = [];
|
|
7464
8165
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7465
8166
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7466
|
-
return await new Promise((
|
|
7467
|
-
child.once("error", (error) =>
|
|
7468
|
-
child.once("close", (code) =>
|
|
8167
|
+
return await new Promise((resolve22) => {
|
|
8168
|
+
child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
|
|
8169
|
+
child.once("close", (code) => resolve22({
|
|
7469
8170
|
exitCode: code ?? 1,
|
|
7470
8171
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7471
8172
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7485,12 +8186,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7485
8186
|
return input.configuredBranch;
|
|
7486
8187
|
}
|
|
7487
8188
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7488
|
-
const
|
|
7489
|
-
if (!
|
|
8189
|
+
const taskId3 = input.taskId?.trim();
|
|
8190
|
+
if (!taskId3) {
|
|
7490
8191
|
return { status: "skipped" };
|
|
7491
8192
|
}
|
|
7492
|
-
const
|
|
7493
|
-
const
|
|
8193
|
+
const configInput = input.config ?? null;
|
|
8194
|
+
const config = configInput ?? {};
|
|
8195
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7494
8196
|
if (prMode === "off" || prMode === "ask") {
|
|
7495
8197
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7496
8198
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7506,7 +8208,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7506
8208
|
gitCommand
|
|
7507
8209
|
});
|
|
7508
8210
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7509
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
8211
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7510
8212
|
const stage = input.appendStage ?? (() => {
|
|
7511
8213
|
return;
|
|
7512
8214
|
});
|
|
@@ -7524,7 +8226,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7524
8226
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7525
8227
|
const commit = await commitRunChanges({
|
|
7526
8228
|
cwd: workspace,
|
|
7527
|
-
message: `rig: complete task ${
|
|
8229
|
+
message: `rig: complete task ${taskId3}`,
|
|
7528
8230
|
command: gitCommand
|
|
7529
8231
|
});
|
|
7530
8232
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7532,13 +8234,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7532
8234
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7533
8235
|
const pr = await prAutomation({
|
|
7534
8236
|
projectRoot: workspace,
|
|
7535
|
-
taskId:
|
|
8237
|
+
taskId: taskId3,
|
|
7536
8238
|
runId: input.runId,
|
|
7537
8239
|
branch,
|
|
7538
8240
|
config,
|
|
7539
8241
|
sourceTask: input.sourceTask,
|
|
7540
8242
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
8243
|
+
artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
|
|
7541
8244
|
command: ghCommand,
|
|
8245
|
+
gitCommand,
|
|
7542
8246
|
steerPi,
|
|
7543
8247
|
lifecycle: {
|
|
7544
8248
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7546,7 +8250,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7546
8250
|
try {
|
|
7547
8251
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7548
8252
|
await updateTaskSource(input.projectRoot, {
|
|
7549
|
-
taskId:
|
|
8253
|
+
taskId: taskId3,
|
|
7550
8254
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7551
8255
|
update: {
|
|
7552
8256
|
status: "under_review",
|
|
@@ -7576,7 +8280,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7576
8280
|
`), "reviewing", "error");
|
|
7577
8281
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7578
8282
|
await updateTaskSource(input.projectRoot, {
|
|
7579
|
-
taskId:
|
|
8283
|
+
taskId: taskId3,
|
|
7580
8284
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7581
8285
|
update: {
|
|
7582
8286
|
status: "ci_fixing",
|
|
@@ -7587,8 +8291,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7587
8291
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7588
8292
|
})
|
|
7589
8293
|
}
|
|
7590
|
-
}).catch(() => {
|
|
7591
|
-
return;
|
|
7592
8294
|
});
|
|
7593
8295
|
}
|
|
7594
8296
|
},
|
|
@@ -7596,7 +8298,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7596
8298
|
stage("Merge", prUrl, "running", "tool");
|
|
7597
8299
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7598
8300
|
await updateTaskSource(input.projectRoot, {
|
|
7599
|
-
taskId:
|
|
8301
|
+
taskId: taskId3,
|
|
7600
8302
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7601
8303
|
update: {
|
|
7602
8304
|
status: "merging",
|
|
@@ -7607,8 +8309,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7607
8309
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7608
8310
|
})
|
|
7609
8311
|
}
|
|
7610
|
-
}).catch(() => {
|
|
7611
|
-
return;
|
|
7612
8312
|
});
|
|
7613
8313
|
}
|
|
7614
8314
|
},
|
|
@@ -7625,7 +8325,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7625
8325
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7626
8326
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7627
8327
|
await updateTaskSource(input.projectRoot, {
|
|
7628
|
-
taskId:
|
|
8328
|
+
taskId: taskId3,
|
|
7629
8329
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7630
8330
|
update: {
|
|
7631
8331
|
status: "needs_attention",
|
|
@@ -7637,8 +8337,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7637
8337
|
errorText: detail
|
|
7638
8338
|
})
|
|
7639
8339
|
}
|
|
7640
|
-
}).catch(() => {
|
|
7641
|
-
|
|
8340
|
+
}).catch((error) => {
|
|
8341
|
+
try {
|
|
8342
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
8343
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
8344
|
+
title: "Task source needs-attention update failed",
|
|
8345
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
8346
|
+
tone: "error",
|
|
8347
|
+
status: "needs_attention",
|
|
8348
|
+
createdAt: new Date().toISOString()
|
|
8349
|
+
});
|
|
8350
|
+
} catch {}
|
|
7642
8351
|
});
|
|
7643
8352
|
}
|
|
7644
8353
|
return { status: "needs_attention", pr };
|
|
@@ -7646,7 +8355,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7646
8355
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7647
8356
|
await closeIssueAfterMergedPr({
|
|
7648
8357
|
projectRoot: input.projectRoot,
|
|
7649
|
-
taskId:
|
|
8358
|
+
taskId: taskId3,
|
|
7650
8359
|
runId: input.runId,
|
|
7651
8360
|
prUrl: pr.prUrl,
|
|
7652
8361
|
sourceTask: input.sourceTask,
|
|
@@ -7656,12 +8365,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7656
8365
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7657
8366
|
return { status: "completed", pr };
|
|
7658
8367
|
}
|
|
7659
|
-
function summarizeValidationFailure(projectRoot,
|
|
7660
|
-
if (!
|
|
8368
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
8369
|
+
if (!taskId3) {
|
|
7661
8370
|
return null;
|
|
7662
8371
|
}
|
|
7663
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7664
|
-
const summary = readJsonFile3(
|
|
8372
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
8373
|
+
const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
|
|
7665
8374
|
if (!summary || summary.status !== "fail") {
|
|
7666
8375
|
continue;
|
|
7667
8376
|
}
|
|
@@ -7742,9 +8451,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7742
8451
|
if (!input.taskId || !input.workspaceDir) {
|
|
7743
8452
|
return { accepted: false, reason: null };
|
|
7744
8453
|
}
|
|
7745
|
-
const artifactDir =
|
|
7746
|
-
const reviewStatusPath =
|
|
7747
|
-
const taskResultPath =
|
|
8454
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8455
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8456
|
+
const taskResultPath = resolve21(artifactDir, "task-result.json");
|
|
7748
8457
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7749
8458
|
if (reviewStatus !== "APPROVED") {
|
|
7750
8459
|
return { accepted: false, reason: null };
|
|
@@ -7781,12 +8490,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7781
8490
|
if (!input.taskId || !input.workspaceDir) {
|
|
7782
8491
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7783
8492
|
}
|
|
7784
|
-
const artifactDir =
|
|
7785
|
-
const reviewStatePath =
|
|
7786
|
-
const reviewFeedbackPath =
|
|
7787
|
-
const reviewStatusPath =
|
|
7788
|
-
const failedApproachesPath =
|
|
7789
|
-
const validationSummaryPath =
|
|
8493
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8494
|
+
const reviewStatePath = resolve21(artifactDir, "review-state.json");
|
|
8495
|
+
const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
|
|
8496
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8497
|
+
const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
8498
|
+
const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
|
|
7790
8499
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7791
8500
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7792
8501
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7840,12 +8549,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7840
8549
|
}
|
|
7841
8550
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7842
8551
|
}
|
|
8552
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8553
|
+
let nextAssistantText = input.assistantText;
|
|
8554
|
+
if (input.record.type === "message_update") {
|
|
8555
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8556
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8557
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8558
|
+
}
|
|
8559
|
+
} else if (input.record.type === "stream_event") {
|
|
8560
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8561
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8562
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8563
|
+
nextAssistantText += delta.text;
|
|
8564
|
+
}
|
|
8565
|
+
} else if (input.record.type === "assistant") {
|
|
8566
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8567
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8568
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8569
|
+
if (fullText.length > nextAssistantText.length)
|
|
8570
|
+
nextAssistantText = fullText;
|
|
8571
|
+
}
|
|
8572
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8573
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8574
|
+
id: input.messageId,
|
|
8575
|
+
type: "assistant_message",
|
|
8576
|
+
text: nextAssistantText,
|
|
8577
|
+
state: "streaming",
|
|
8578
|
+
createdAt: new Date().toISOString()
|
|
8579
|
+
});
|
|
8580
|
+
}
|
|
8581
|
+
return nextAssistantText;
|
|
8582
|
+
}
|
|
8583
|
+
function appendPiToolTimelineFromRecord(input) {
|
|
8584
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
8585
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
8586
|
+
return false;
|
|
8587
|
+
const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
|
|
8588
|
+
const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
|
|
8589
|
+
const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
|
|
8590
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8591
|
+
id: `tool:${toolCallId}:${type}`,
|
|
8592
|
+
type,
|
|
8593
|
+
toolName,
|
|
8594
|
+
status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
|
|
8595
|
+
createdAt: new Date().toISOString()
|
|
8596
|
+
});
|
|
8597
|
+
return true;
|
|
8598
|
+
}
|
|
8599
|
+
function isNonRenderablePiProtocolRecord(record) {
|
|
8600
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
8601
|
+
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");
|
|
8602
|
+
}
|
|
8603
|
+
function appendToolTimelineFromLog(input) {
|
|
8604
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8605
|
+
if (title !== "Tool activity")
|
|
8606
|
+
return;
|
|
8607
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8608
|
+
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;
|
|
8609
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8610
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8611
|
+
id: `tool:${logId}`,
|
|
8612
|
+
type: "tool_execution_update",
|
|
8613
|
+
toolName,
|
|
8614
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8615
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8616
|
+
payload,
|
|
8617
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8618
|
+
});
|
|
8619
|
+
}
|
|
7843
8620
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7844
|
-
if (!
|
|
8621
|
+
if (!existsSync12(reviewStatusPath)) {
|
|
7845
8622
|
return null;
|
|
7846
8623
|
}
|
|
7847
8624
|
try {
|
|
7848
|
-
const status =
|
|
8625
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7849
8626
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7850
8627
|
} catch {
|
|
7851
8628
|
return null;
|
|
@@ -7863,14 +8640,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7863
8640
|
function runSourceTaskIdentity(sourceTask) {
|
|
7864
8641
|
return sourceTask;
|
|
7865
8642
|
}
|
|
7866
|
-
|
|
7867
|
-
if (!
|
|
8643
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8644
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8645
|
+
return null;
|
|
8646
|
+
const record = sourceTask;
|
|
8647
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8648
|
+
if (direct?.trim())
|
|
8649
|
+
return direct.trim();
|
|
8650
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8651
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8652
|
+
}
|
|
8653
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8654
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8655
|
+
id: input.taskId,
|
|
8656
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8657
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8658
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8659
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8660
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8661
|
+
});
|
|
8662
|
+
if (serverResult.ok === false) {
|
|
8663
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8664
|
+
}
|
|
8665
|
+
return {
|
|
8666
|
+
updated: serverResult.ok !== false,
|
|
8667
|
+
taskId: input.taskId,
|
|
8668
|
+
status: input.update.status,
|
|
8669
|
+
source: "server",
|
|
8670
|
+
sourceKind: "server",
|
|
8671
|
+
projectSync: serverResult.projectSync
|
|
8672
|
+
};
|
|
8673
|
+
};
|
|
8674
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8675
|
+
if (!taskId3)
|
|
7868
8676
|
return;
|
|
7869
8677
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7870
8678
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7871
8679
|
return;
|
|
7872
8680
|
const result = await updateTaskSource(projectRoot, {
|
|
7873
|
-
taskId:
|
|
8681
|
+
taskId: taskId3,
|
|
7874
8682
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7875
8683
|
update: {
|
|
7876
8684
|
status,
|
|
@@ -7889,14 +8697,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7889
8697
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7890
8698
|
}
|
|
7891
8699
|
}
|
|
7892
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8700
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7893
8701
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7894
8702
|
const raw = run?.sourceTask;
|
|
7895
8703
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7896
8704
|
return null;
|
|
7897
8705
|
}
|
|
7898
8706
|
const record = raw;
|
|
7899
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8707
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7900
8708
|
if (!id) {
|
|
7901
8709
|
return null;
|
|
7902
8710
|
}
|
|
@@ -7928,6 +8736,9 @@ function stringArrayField(record, key) {
|
|
|
7928
8736
|
}
|
|
7929
8737
|
async function executeRigOwnedTaskRun(context, input) {
|
|
7930
8738
|
const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
|
|
8739
|
+
const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
|
|
8740
|
+
const resumeMode = process.env.RIG_RUN_RESUME === "1";
|
|
8741
|
+
const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
|
|
7931
8742
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
7932
8743
|
let prompt = buildRunPrompt({
|
|
7933
8744
|
projectRoot: context.projectRoot,
|
|
@@ -7983,14 +8794,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7983
8794
|
taskId: runtimeTaskId,
|
|
7984
8795
|
createdAt: startedAt,
|
|
7985
8796
|
runtimeAdapter: input.runtimeAdapter,
|
|
7986
|
-
status: "created"
|
|
8797
|
+
status: resumeMode ? "preparing" : "created"
|
|
7987
8798
|
});
|
|
7988
8799
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
7989
8800
|
status: "preparing",
|
|
7990
8801
|
startedAt,
|
|
7991
8802
|
completedAt: null,
|
|
7992
8803
|
errorText: null,
|
|
7993
|
-
artifactRoot: null,
|
|
8804
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
7994
8805
|
runtimeAdapter: input.runtimeAdapter,
|
|
7995
8806
|
runtimeMode: input.runtimeMode,
|
|
7996
8807
|
interactionMode: input.interactionMode,
|
|
@@ -8006,9 +8817,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8006
8817
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
8007
8818
|
});
|
|
8008
8819
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8009
|
-
id: `log:${input.runId}:start`,
|
|
8010
|
-
title: "Rig task run started",
|
|
8011
|
-
detail: input.taskId ?? input.title ?? runtimeTaskId,
|
|
8820
|
+
id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
|
|
8821
|
+
title: resumeMode ? "Rig task run resumed" : "Rig task run started",
|
|
8822
|
+
detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
|
|
8012
8823
|
tone: "info",
|
|
8013
8824
|
status: "preparing",
|
|
8014
8825
|
createdAt: startedAt
|
|
@@ -8029,15 +8840,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8029
8840
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
8030
8841
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
8031
8842
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
8032
|
-
const planningArtifactPath =
|
|
8843
|
+
const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
8033
8844
|
const persistedPlanning = {
|
|
8034
8845
|
...planningClassification,
|
|
8035
8846
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
8036
8847
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
8037
8848
|
classifiedAt: new Date().toISOString()
|
|
8038
8849
|
};
|
|
8039
|
-
mkdirSync8(
|
|
8040
|
-
writeFileSync6(
|
|
8850
|
+
mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8851
|
+
writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
8041
8852
|
`, "utf8");
|
|
8042
8853
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
8043
8854
|
prompt = `${prompt}
|
|
@@ -8086,11 +8897,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8086
8897
|
let reviewAction;
|
|
8087
8898
|
let verificationStarted = false;
|
|
8088
8899
|
let reviewStarted = false;
|
|
8089
|
-
let latestRuntimeWorkspace = null;
|
|
8090
|
-
let latestSessionDir = null;
|
|
8091
|
-
let latestLogsDir = null;
|
|
8900
|
+
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
8901
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
|
|
8902
|
+
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
8092
8903
|
let latestProviderCommand = null;
|
|
8093
|
-
let latestRuntimeBranch = null;
|
|
8904
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
8094
8905
|
let snapshotSidecarPromise = null;
|
|
8095
8906
|
let dirtyBaselineApplied = false;
|
|
8096
8907
|
const childEnv = {
|
|
@@ -8108,7 +8919,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8108
8919
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
8109
8920
|
RIG_SERVER_RUN_ID: input.runId
|
|
8110
8921
|
},
|
|
8111
|
-
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
|
|
8922
|
+
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
|
|
8923
|
+
...resumeMode ? {
|
|
8924
|
+
RIG_RUN_RESUME: "1",
|
|
8925
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
8926
|
+
} : {}
|
|
8112
8927
|
};
|
|
8113
8928
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
8114
8929
|
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
@@ -8166,10 +8981,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8166
8981
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8167
8982
|
status: "running",
|
|
8168
8983
|
worktreePath: latestRuntimeWorkspace,
|
|
8169
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8984
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
8170
8985
|
logRoot: latestLogsDir,
|
|
8171
|
-
sessionPath: latestSessionDir ?
|
|
8172
|
-
sessionLogPath: latestLogsDir ?
|
|
8986
|
+
sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
|
|
8987
|
+
sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
|
|
8173
8988
|
branch: runtimeId
|
|
8174
8989
|
});
|
|
8175
8990
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -8177,7 +8992,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8177
8992
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
8178
8993
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
8179
8994
|
if (readyFile) {
|
|
8180
|
-
mkdirSync8(
|
|
8995
|
+
mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
|
|
8181
8996
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
8182
8997
|
`, "utf8");
|
|
8183
8998
|
}
|
|
@@ -8310,6 +9125,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8310
9125
|
try {
|
|
8311
9126
|
const record = JSON.parse(trimmed);
|
|
8312
9127
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
9128
|
+
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
9129
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9130
|
+
return;
|
|
9131
|
+
}
|
|
8313
9132
|
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
8314
9133
|
runId: input.runId,
|
|
8315
9134
|
record,
|
|
@@ -8326,7 +9145,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8326
9145
|
if (providerLogs.length > 0) {
|
|
8327
9146
|
for (const providerLog of providerLogs) {
|
|
8328
9147
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9148
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8329
9149
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9150
|
+
if (providerLog.title === "Tool activity")
|
|
9151
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8330
9152
|
}
|
|
8331
9153
|
}
|
|
8332
9154
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8378,6 +9200,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8378
9200
|
return;
|
|
8379
9201
|
}
|
|
8380
9202
|
}
|
|
9203
|
+
if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
8381
9206
|
if (record.type === "assistant") {
|
|
8382
9207
|
const message2 = record.message && typeof record.message === "object" ? record.message : record;
|
|
8383
9208
|
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
@@ -8414,9 +9239,38 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8414
9239
|
let reviewFailureDetail = null;
|
|
8415
9240
|
const stderrLines = [];
|
|
8416
9241
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
8417
|
-
|
|
9242
|
+
if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
|
|
9243
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9244
|
+
id: `log:${input.runId}:resume-closeout-phase`,
|
|
9245
|
+
title: "Resume continuing from closeout phase",
|
|
9246
|
+
detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
|
|
9247
|
+
tone: "info",
|
|
9248
|
+
status: resumePreviousStatus,
|
|
9249
|
+
createdAt: new Date().toISOString()
|
|
9250
|
+
});
|
|
9251
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
|
|
9252
|
+
exit = { code: 0, signal: null };
|
|
9253
|
+
} else if (resumeMode && latestRuntimeWorkspace) {
|
|
9254
|
+
const acceptedArtifactState = readTaskRunAcceptedArtifactState({
|
|
9255
|
+
taskId: input.taskId ?? runtimeTaskId,
|
|
9256
|
+
workspaceDir: latestRuntimeWorkspace
|
|
9257
|
+
});
|
|
9258
|
+
if (acceptedArtifactState.accepted) {
|
|
9259
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9260
|
+
id: `log:${input.runId}:resume-accepted-artifacts`,
|
|
9261
|
+
title: "Resume found accepted artifacts; continuing closeout",
|
|
9262
|
+
detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
|
|
9263
|
+
tone: "info",
|
|
9264
|
+
status: "validating",
|
|
9265
|
+
createdAt: new Date().toISOString()
|
|
9266
|
+
});
|
|
9267
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
|
|
9268
|
+
exit = { code: 0, signal: null };
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
|
|
8418
9272
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
8419
|
-
const child =
|
|
9273
|
+
const child = spawn3(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
8420
9274
|
cwd: context.projectRoot,
|
|
8421
9275
|
env: childEnv,
|
|
8422
9276
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8455,7 +9309,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8455
9309
|
let acceptedArtifactObservedAt = null;
|
|
8456
9310
|
let acceptedArtifactPollTimer = null;
|
|
8457
9311
|
let acceptedArtifactKillTimer = null;
|
|
8458
|
-
const attemptExit = await new Promise((
|
|
9312
|
+
const attemptExit = await new Promise((resolve22) => {
|
|
8459
9313
|
let settled = false;
|
|
8460
9314
|
const settle = (result) => {
|
|
8461
9315
|
if (settled)
|
|
@@ -8463,7 +9317,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8463
9317
|
settled = true;
|
|
8464
9318
|
if (acceptedArtifactPollTimer)
|
|
8465
9319
|
clearInterval(acceptedArtifactPollTimer);
|
|
8466
|
-
|
|
9320
|
+
resolve22(result);
|
|
8467
9321
|
};
|
|
8468
9322
|
const pollAcceptedArtifacts = () => {
|
|
8469
9323
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8536,7 +9390,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8536
9390
|
});
|
|
8537
9391
|
for (const pendingLog of pendingLogs) {
|
|
8538
9392
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9393
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8539
9394
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9395
|
+
if (pendingLog.title === "Tool activity")
|
|
9396
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8540
9397
|
}
|
|
8541
9398
|
process.off("SIGTERM", forwardSigterm);
|
|
8542
9399
|
if (attemptExit.error) {
|
|
@@ -8662,8 +9519,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8662
9519
|
}
|
|
8663
9520
|
if (planningClassification.planningRequired) {
|
|
8664
9521
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8665
|
-
const expectedPlanPath =
|
|
8666
|
-
if (!
|
|
9522
|
+
const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
|
|
9523
|
+
if (!existsSync12(expectedPlanPath)) {
|
|
8667
9524
|
const failedAt = new Date().toISOString();
|
|
8668
9525
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8669
9526
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8683,6 +9540,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8683
9540
|
throw new CliError2(failureDetail, 1);
|
|
8684
9541
|
}
|
|
8685
9542
|
}
|
|
9543
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
9544
|
+
appendPiStageLog({
|
|
9545
|
+
projectRoot: context.projectRoot,
|
|
9546
|
+
runId: input.runId,
|
|
9547
|
+
stage: "Validate",
|
|
9548
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
9549
|
+
status: "completed"
|
|
9550
|
+
});
|
|
9551
|
+
if (verificationAction && !reviewStarted) {
|
|
9552
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
9553
|
+
}
|
|
9554
|
+
if (!reviewAction) {
|
|
9555
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
9556
|
+
}
|
|
9557
|
+
if (reviewAction) {
|
|
9558
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
9559
|
+
}
|
|
9560
|
+
const requestedAt = new Date().toISOString();
|
|
9561
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
9562
|
+
status: "reviewing",
|
|
9563
|
+
completedAt: null,
|
|
9564
|
+
errorText: null,
|
|
9565
|
+
serverCloseout: {
|
|
9566
|
+
status: "pending",
|
|
9567
|
+
phase: "queued",
|
|
9568
|
+
requestedAt,
|
|
9569
|
+
updatedAt: requestedAt,
|
|
9570
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
9571
|
+
branch: latestRuntimeBranch,
|
|
9572
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
9573
|
+
}
|
|
9574
|
+
});
|
|
9575
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9576
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9577
|
+
title: "Server-owned closeout requested",
|
|
9578
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9579
|
+
tone: "info",
|
|
9580
|
+
status: "reviewing",
|
|
9581
|
+
createdAt: requestedAt,
|
|
9582
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9583
|
+
});
|
|
9584
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9585
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9586
|
+
await context.emitEvent("command.finished", {
|
|
9587
|
+
command: [
|
|
9588
|
+
"rig",
|
|
9589
|
+
"server",
|
|
9590
|
+
"task-run",
|
|
9591
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9592
|
+
...input.title ? ["--title", input.title] : []
|
|
9593
|
+
],
|
|
9594
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9595
|
+
exitCode: 0,
|
|
9596
|
+
durationMs: 0,
|
|
9597
|
+
startedAt,
|
|
9598
|
+
finishedAt: requestedAt
|
|
9599
|
+
});
|
|
9600
|
+
return;
|
|
9601
|
+
}
|
|
8686
9602
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8687
9603
|
appendPiStageLog({
|
|
8688
9604
|
projectRoot: context.projectRoot,
|
|
@@ -8701,7 +9617,7 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8701
9617
|
});
|
|
8702
9618
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
|
|
8703
9619
|
const feedbackCommand = buildHostAgentCommand(message2);
|
|
8704
|
-
const child =
|
|
9620
|
+
const child = spawn3(feedbackCommand[0], feedbackCommand.slice(1), {
|
|
8705
9621
|
cwd: latestRuntimeWorkspace ?? context.projectRoot,
|
|
8706
9622
|
env: childEnv,
|
|
8707
9623
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8710,11 +9626,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8710
9626
|
child.stdin.write(message2);
|
|
8711
9627
|
}
|
|
8712
9628
|
child.stdin.end();
|
|
9629
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9630
|
+
let feedbackAssistantText = "";
|
|
9631
|
+
const feedbackPendingToolUses = new Map;
|
|
8713
9632
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8714
9633
|
stdout.on("line", (line) => {
|
|
8715
9634
|
const trimmed = line.trim();
|
|
8716
9635
|
if (!trimmed)
|
|
8717
9636
|
return;
|
|
9637
|
+
try {
|
|
9638
|
+
const record = JSON.parse(trimmed);
|
|
9639
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9640
|
+
runId: input.runId,
|
|
9641
|
+
record,
|
|
9642
|
+
createdAtFallback: new Date().toISOString(),
|
|
9643
|
+
status: "reviewing",
|
|
9644
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9645
|
+
});
|
|
9646
|
+
for (const providerLog of providerLogs) {
|
|
9647
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9648
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9649
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9650
|
+
if (providerLog.title === "Tool activity")
|
|
9651
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9652
|
+
}
|
|
9653
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9654
|
+
projectRoot: context.projectRoot,
|
|
9655
|
+
runId: input.runId,
|
|
9656
|
+
messageId: feedbackAssistantMessageId,
|
|
9657
|
+
record,
|
|
9658
|
+
assistantText: feedbackAssistantText
|
|
9659
|
+
});
|
|
9660
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9661
|
+
if (hadAssistantDelta) {
|
|
9662
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9663
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9664
|
+
}
|
|
9665
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9666
|
+
return;
|
|
9667
|
+
} catch {}
|
|
8718
9668
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8719
9669
|
id: nextRunLogId(),
|
|
8720
9670
|
title: "Pi PR feedback fix output",
|
|
@@ -8740,10 +9690,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8740
9690
|
});
|
|
8741
9691
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8742
9692
|
});
|
|
8743
|
-
const exitCode = await new Promise((
|
|
8744
|
-
child.once("error", () =>
|
|
8745
|
-
child.once("close", (code) =>
|
|
9693
|
+
const exitCode = await new Promise((resolve22) => {
|
|
9694
|
+
child.once("error", () => resolve22(1));
|
|
9695
|
+
child.once("close", (code) => resolve22(code ?? 1));
|
|
8746
9696
|
});
|
|
9697
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9698
|
+
runId: input.runId,
|
|
9699
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9700
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9701
|
+
})) {
|
|
9702
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9703
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9704
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9705
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9706
|
+
}
|
|
9707
|
+
if (feedbackAssistantText.trim()) {
|
|
9708
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9709
|
+
id: feedbackAssistantMessageId,
|
|
9710
|
+
type: "assistant_message",
|
|
9711
|
+
text: feedbackAssistantText,
|
|
9712
|
+
state: "completed",
|
|
9713
|
+
createdAt: new Date().toISOString(),
|
|
9714
|
+
completedAt: new Date().toISOString()
|
|
9715
|
+
});
|
|
9716
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9717
|
+
}
|
|
8747
9718
|
if (exitCode !== 0) {
|
|
8748
9719
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8749
9720
|
}
|
|
@@ -8861,8 +9832,8 @@ async function executeTest(context, args) {
|
|
|
8861
9832
|
}
|
|
8862
9833
|
|
|
8863
9834
|
// packages/cli/src/commands/setup.ts
|
|
8864
|
-
import { existsSync as
|
|
8865
|
-
import { resolve as
|
|
9835
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9836
|
+
import { resolve as resolve22 } from "path";
|
|
8866
9837
|
import { createPluginHost } from "@rig/core";
|
|
8867
9838
|
import {
|
|
8868
9839
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -8925,8 +9896,8 @@ function runSetupInit(projectRoot) {
|
|
|
8925
9896
|
mkdirSync9(stateDir, { recursive: true });
|
|
8926
9897
|
mkdirSync9(logsDir, { recursive: true });
|
|
8927
9898
|
mkdirSync9(artifactsDir, { recursive: true });
|
|
8928
|
-
const failuresPath =
|
|
8929
|
-
if (!
|
|
9899
|
+
const failuresPath = resolve22(stateDir, "failed_approaches.md");
|
|
9900
|
+
if (!existsSync13(failuresPath)) {
|
|
8930
9901
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
8931
9902
|
|
|
8932
9903
|
`, "utf-8");
|
|
@@ -8944,18 +9915,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
8944
9915
|
}
|
|
8945
9916
|
async function runSetupPreflight(projectRoot) {
|
|
8946
9917
|
await runSetupCheck(projectRoot);
|
|
8947
|
-
const validationRoot =
|
|
8948
|
-
if (
|
|
9918
|
+
const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9919
|
+
if (existsSync13(validationRoot)) {
|
|
8949
9920
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
8950
9921
|
for (const validator of validators) {
|
|
8951
|
-
const script =
|
|
8952
|
-
if (
|
|
9922
|
+
const script = resolve22(validationRoot, validator.name, "validate.sh");
|
|
9923
|
+
if (existsSync13(script)) {
|
|
8953
9924
|
console.log(`OK: validator script ${script}`);
|
|
8954
9925
|
}
|
|
8955
9926
|
}
|
|
8956
9927
|
}
|
|
8957
|
-
const hooksRoot =
|
|
8958
|
-
if (
|
|
9928
|
+
const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9929
|
+
if (existsSync13(hooksRoot)) {
|
|
8959
9930
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
8960
9931
|
for (const hook of hooks) {
|
|
8961
9932
|
console.log(`OK: hook ${hook}`);
|
|
@@ -9332,8 +10303,8 @@ async function executeGroup(context, group, args) {
|
|
|
9332
10303
|
}
|
|
9333
10304
|
}
|
|
9334
10305
|
// packages/cli/src/launcher.ts
|
|
9335
|
-
import { existsSync as
|
|
9336
|
-
import { resolve as
|
|
10306
|
+
import { existsSync as existsSync14 } from "fs";
|
|
10307
|
+
import { basename as basename2, resolve as resolve23 } from "path";
|
|
9337
10308
|
import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
|
|
9338
10309
|
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
|
|
9339
10310
|
function parsePolicyMode(value) {
|
|
@@ -9346,7 +10317,7 @@ function parsePolicyMode(value) {
|
|
|
9346
10317
|
throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
|
|
9347
10318
|
}
|
|
9348
10319
|
function hasRigProjectMarker(candidate) {
|
|
9349
|
-
return
|
|
10320
|
+
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"));
|
|
9350
10321
|
}
|
|
9351
10322
|
function resolveProjectRoot({
|
|
9352
10323
|
envProjectRoot,
|
|
@@ -9355,17 +10326,19 @@ function resolveProjectRoot({
|
|
|
9355
10326
|
cwd = process.cwd()
|
|
9356
10327
|
}) {
|
|
9357
10328
|
if (envProjectRoot) {
|
|
9358
|
-
return
|
|
10329
|
+
return resolve23(cwd, envProjectRoot);
|
|
9359
10330
|
}
|
|
9360
10331
|
const fallbackImportDir = importDir ?? cwd;
|
|
9361
|
-
const
|
|
10332
|
+
const execName = basename2(execPath).toLowerCase();
|
|
10333
|
+
const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
|
|
10334
|
+
const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
|
|
9362
10335
|
for (const candidate of candidates) {
|
|
9363
10336
|
const nearest = resolveNearestRigProjectRoot(candidate);
|
|
9364
10337
|
if (hasRigProjectMarker(nearest)) {
|
|
9365
10338
|
return nearest;
|
|
9366
10339
|
}
|
|
9367
10340
|
}
|
|
9368
|
-
return
|
|
10341
|
+
return resolve23(cwd);
|
|
9369
10342
|
}
|
|
9370
10343
|
function normalizeCliErrorCode(message2, isCliError) {
|
|
9371
10344
|
if (message2.startsWith("Invalid --policy-mode value:")) {
|
|
@@ -9432,7 +10405,7 @@ async function runRigCli(module, options = {}) {
|
|
|
9432
10405
|
runId: context.runId,
|
|
9433
10406
|
outcome,
|
|
9434
10407
|
eventsFile: context.eventBus.getEventsFile(),
|
|
9435
|
-
policyFile:
|
|
10408
|
+
policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
|
|
9436
10409
|
policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
|
|
9437
10410
|
}, null, 2));
|
|
9438
10411
|
}
|