@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +710 -226
- package/dist/src/commands/_doctor-checks.js +7 -20
- package/dist/src/commands/_operator-surface.js +157 -0
- package/dist/src/commands/_operator-view.js +160 -51
- package/dist/src/commands/_preflight.js +30 -26
- package/dist/src/commands/_server-client.js +46 -22
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +21 -13
- package/dist/src/commands/agent.js +1 -0
- package/dist/src/commands/doctor.js +7 -20
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +183 -44
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +172 -76
- package/dist/src/commands/server.js +7 -20
- package/dist/src/commands/setup.js +7 -20
- package/dist/src/commands/task-run-driver.js +446 -53
- package/dist/src/commands/task.js +231 -98
- package/dist/src/commands.js +702 -218
- package/dist/src/index.js +710 -226
- package/package.json +5 -5
package/dist/src/index.js
CHANGED
|
@@ -2667,17 +2667,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2667
2667
|
}
|
|
2668
2668
|
|
|
2669
2669
|
// packages/cli/src/commands/_server-client.ts
|
|
2670
|
-
import { spawnSync } from "child_process";
|
|
2671
2670
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2672
2671
|
import { resolve as resolve9 } from "path";
|
|
2673
2672
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2674
|
-
var
|
|
2673
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2675
2674
|
function cleanToken(value) {
|
|
2676
2675
|
const trimmed = value?.trim();
|
|
2677
2676
|
return trimmed ? trimmed : null;
|
|
2678
2677
|
}
|
|
2679
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2680
|
-
|
|
2678
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2679
|
+
const scopedKey = resolve9(projectRoot ?? process.cwd());
|
|
2680
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2681
2681
|
}
|
|
2682
2682
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2683
2683
|
const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2691,25 +2691,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2691
2691
|
}
|
|
2692
2692
|
}
|
|
2693
2693
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2694
|
-
|
|
2695
|
-
|
|
2694
|
+
const scopedKey = resolve9(projectRoot);
|
|
2695
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2696
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2696
2697
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2697
|
-
if (privateSession)
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2701
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2702
|
-
if (envToken) {
|
|
2703
|
-
cachedGitHubBearerToken = envToken;
|
|
2704
|
-
return cachedGitHubBearerToken;
|
|
2705
|
-
}
|
|
2706
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2707
|
-
encoding: "utf8",
|
|
2708
|
-
timeout: 5000,
|
|
2709
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2710
|
-
});
|
|
2711
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2712
|
-
return cachedGitHubBearerToken;
|
|
2698
|
+
if (privateSession)
|
|
2699
|
+
return privateSession;
|
|
2700
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2713
2701
|
}
|
|
2714
2702
|
async function ensureServerForCli(projectRoot) {
|
|
2715
2703
|
try {
|
|
@@ -2907,6 +2895,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2907
2895
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2908
2896
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2909
2897
|
}
|
|
2898
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2899
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2900
|
+
if (options.limit !== undefined)
|
|
2901
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2902
|
+
if (options.cursor)
|
|
2903
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2904
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2905
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2906
|
+
}
|
|
2907
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2908
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2909
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2910
|
+
}
|
|
2911
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2912
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2913
|
+
url.searchParams.set("owner", owner);
|
|
2914
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2915
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2916
|
+
}
|
|
2917
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2918
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2919
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2920
|
+
}
|
|
2921
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2922
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2923
|
+
method: "POST",
|
|
2924
|
+
headers: { "content-type": "application/json" },
|
|
2925
|
+
body: JSON.stringify(input)
|
|
2926
|
+
});
|
|
2927
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2928
|
+
}
|
|
2910
2929
|
async function stopRunViaServer(context, runId) {
|
|
2911
2930
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2912
2931
|
method: "POST",
|
|
@@ -3186,6 +3205,9 @@ function permissionAllowsPr(payload) {
|
|
|
3186
3205
|
}
|
|
3187
3206
|
return null;
|
|
3188
3207
|
}
|
|
3208
|
+
function isNotFoundError(error) {
|
|
3209
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3210
|
+
}
|
|
3189
3211
|
function projectCheckoutReady(payload) {
|
|
3190
3212
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3191
3213
|
return null;
|
|
@@ -3218,19 +3240,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3218
3240
|
const checks = [];
|
|
3219
3241
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
3220
3242
|
const taskId = options.taskId?.trim() || null;
|
|
3243
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3244
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3245
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3246
|
+
let legacyServerCompatibility = false;
|
|
3221
3247
|
try {
|
|
3222
3248
|
await request("/api/server/status");
|
|
3223
3249
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3224
3250
|
} catch (error) {
|
|
3225
|
-
|
|
3251
|
+
if (isNotFoundError(error)) {
|
|
3252
|
+
try {
|
|
3253
|
+
await request("/health");
|
|
3254
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3255
|
+
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"));
|
|
3256
|
+
} catch (healthError) {
|
|
3257
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3258
|
+
}
|
|
3259
|
+
} else {
|
|
3260
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3261
|
+
}
|
|
3226
3262
|
}
|
|
3227
3263
|
const repo = readRepoConnection(context.projectRoot);
|
|
3228
|
-
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>`."));
|
|
3264
|
+
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>`."));
|
|
3229
3265
|
try {
|
|
3230
3266
|
const auth = await request("/api/github/auth/status");
|
|
3231
|
-
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>`."));
|
|
3267
|
+
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>`."));
|
|
3232
3268
|
} catch (error) {
|
|
3233
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3269
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3234
3270
|
}
|
|
3235
3271
|
try {
|
|
3236
3272
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3258,9 +3294,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3258
3294
|
try {
|
|
3259
3295
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3260
3296
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3261
|
-
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."));
|
|
3297
|
+
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."));
|
|
3262
3298
|
} catch (error) {
|
|
3263
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3299
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3264
3300
|
}
|
|
3265
3301
|
try {
|
|
3266
3302
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -4172,9 +4208,10 @@ async function executeInbox(context, args) {
|
|
|
4172
4208
|
|
|
4173
4209
|
// packages/cli/src/commands/init.ts
|
|
4174
4210
|
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4175
|
-
import { spawnSync
|
|
4211
|
+
import { spawnSync } from "child_process";
|
|
4176
4212
|
import { resolve as resolve17 } from "path";
|
|
4177
4213
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4214
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
4178
4215
|
|
|
4179
4216
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
4180
4217
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4498,7 +4535,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4498
4535
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4499
4536
|
}
|
|
4500
4537
|
function detectOriginRepoSlug(projectRoot) {
|
|
4501
|
-
const result =
|
|
4538
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4502
4539
|
if (result.status !== 0)
|
|
4503
4540
|
return null;
|
|
4504
4541
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4554,11 +4591,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4554
4591
|
return source;
|
|
4555
4592
|
const projectId = JSON.stringify(options.githubProject);
|
|
4556
4593
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4594
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4595
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4596
|
+
`)},` : "";
|
|
4557
4597
|
return source.replace(` projects: { enabled: false },`, [
|
|
4558
4598
|
` projects: {`,
|
|
4559
4599
|
` enabled: true,`,
|
|
4560
4600
|
` projectId: ${projectId},`,
|
|
4561
|
-
` statusFieldId: ${statusFieldId}
|
|
4601
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4562
4602
|
` },`
|
|
4563
4603
|
].join(`
|
|
4564
4604
|
`));
|
|
@@ -4579,11 +4619,11 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4579
4619
|
}
|
|
4580
4620
|
}
|
|
4581
4621
|
function detectGhLogin() {
|
|
4582
|
-
const result =
|
|
4622
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4583
4623
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4584
4624
|
}
|
|
4585
4625
|
function readGhAuthToken() {
|
|
4586
|
-
const result =
|
|
4626
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4587
4627
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4588
4628
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4589
4629
|
}
|
|
@@ -4592,8 +4632,15 @@ function readGhAuthToken() {
|
|
|
4592
4632
|
async function loadClackPrompts() {
|
|
4593
4633
|
return await import("@clack/prompts");
|
|
4594
4634
|
}
|
|
4635
|
+
function clackTextOptions(options) {
|
|
4636
|
+
return {
|
|
4637
|
+
message: options.message,
|
|
4638
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4639
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4640
|
+
};
|
|
4641
|
+
}
|
|
4595
4642
|
async function promptRequiredText(prompts, options) {
|
|
4596
|
-
const value = await prompts.text(options);
|
|
4643
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4597
4644
|
if (prompts.isCancel(value))
|
|
4598
4645
|
throw new CliError2("Init cancelled.", 1);
|
|
4599
4646
|
const text2 = String(value ?? "").trim();
|
|
@@ -4602,7 +4649,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4602
4649
|
return text2;
|
|
4603
4650
|
}
|
|
4604
4651
|
async function promptOptionalText(prompts, options) {
|
|
4605
|
-
const value = await prompts.text(options);
|
|
4652
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4606
4653
|
if (prompts.isCancel(value))
|
|
4607
4654
|
throw new CliError2("Init cancelled.", 1);
|
|
4608
4655
|
return String(value ?? "").trim();
|
|
@@ -4613,6 +4660,132 @@ async function promptSelect(prompts, options) {
|
|
|
4613
4660
|
throw new CliError2("Init cancelled.", 1);
|
|
4614
4661
|
return String(value);
|
|
4615
4662
|
}
|
|
4663
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4664
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4665
|
+
}
|
|
4666
|
+
function recordArray(value, key) {
|
|
4667
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4668
|
+
return [];
|
|
4669
|
+
const raw = value[key];
|
|
4670
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4671
|
+
}
|
|
4672
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4673
|
+
if (token?.trim()) {
|
|
4674
|
+
try {
|
|
4675
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4676
|
+
} catch (directError) {
|
|
4677
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4678
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4679
|
+
return serverPayload;
|
|
4680
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4684
|
+
}
|
|
4685
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4686
|
+
if (token?.trim()) {
|
|
4687
|
+
try {
|
|
4688
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4689
|
+
} catch (directError) {
|
|
4690
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4691
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4692
|
+
return serverPayload;
|
|
4693
|
+
}
|
|
4694
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4698
|
+
}
|
|
4699
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4700
|
+
running: "Running/In progress",
|
|
4701
|
+
prOpen: "PR open/review",
|
|
4702
|
+
ciFixing: "CI/review fixing",
|
|
4703
|
+
merging: "Merging",
|
|
4704
|
+
done: "Done",
|
|
4705
|
+
needsAttention: "Needs attention"
|
|
4706
|
+
};
|
|
4707
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4708
|
+
running: "In Progress",
|
|
4709
|
+
prOpen: "In Review",
|
|
4710
|
+
ciFixing: "In Review",
|
|
4711
|
+
merging: "Merging",
|
|
4712
|
+
done: "Done",
|
|
4713
|
+
needsAttention: "Needs Attention"
|
|
4714
|
+
};
|
|
4715
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4716
|
+
const statuses = {};
|
|
4717
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4718
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4719
|
+
const value = await promptOptionalText(prompts, {
|
|
4720
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4721
|
+
placeholder: defaultLabel
|
|
4722
|
+
});
|
|
4723
|
+
statuses[key] = value || defaultLabel;
|
|
4724
|
+
}
|
|
4725
|
+
return statuses;
|
|
4726
|
+
}
|
|
4727
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken) {
|
|
4728
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4729
|
+
message: "GitHub Projects status sync",
|
|
4730
|
+
options: [
|
|
4731
|
+
{ value: "off", label: "Off" },
|
|
4732
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4733
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4734
|
+
]
|
|
4735
|
+
});
|
|
4736
|
+
if (projectChoice === "off")
|
|
4737
|
+
return { githubProject: "off" };
|
|
4738
|
+
if (projectChoice === "manual") {
|
|
4739
|
+
return {
|
|
4740
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4741
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4742
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4743
|
+
};
|
|
4744
|
+
}
|
|
4745
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4746
|
+
if (!owner)
|
|
4747
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4748
|
+
const projectsPayload = await listGitHubProjectsForInit(context, owner, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4749
|
+
const projects = recordArray(projectsPayload, "projects");
|
|
4750
|
+
if (projects.length === 0) {
|
|
4751
|
+
const error = typeof projectsPayload.error === "string" ? ` (${projectsPayload.error})` : "";
|
|
4752
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; falling back to manual ProjectV2 ids.`);
|
|
4753
|
+
return {
|
|
4754
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4755
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4756
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4760
|
+
message: "GitHub ProjectV2 project",
|
|
4761
|
+
options: [
|
|
4762
|
+
...projects.map((project) => ({
|
|
4763
|
+
value: String(project.id),
|
|
4764
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4765
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4766
|
+
})),
|
|
4767
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4768
|
+
]
|
|
4769
|
+
});
|
|
4770
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4771
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4772
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4773
|
+
const rawField = fieldPayloadRecord.field;
|
|
4774
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4775
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4776
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4777
|
+
if (options.length === 0) {
|
|
4778
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: await promptManualProjectStatusMapping(prompts) };
|
|
4779
|
+
}
|
|
4780
|
+
const statuses = {};
|
|
4781
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4782
|
+
statuses[key] = await promptSelect(prompts, {
|
|
4783
|
+
message: `Project status option for ${label}`,
|
|
4784
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: String(option.name ?? option.id) }))
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4787
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined };
|
|
4788
|
+
}
|
|
4616
4789
|
function sleep2(ms) {
|
|
4617
4790
|
return new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
4618
4791
|
}
|
|
@@ -4741,7 +4914,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4741
4914
|
if (token) {
|
|
4742
4915
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4743
4916
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4744
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4917
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4745
4918
|
if (serverKind === "remote") {
|
|
4746
4919
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4747
4920
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
@@ -4766,7 +4939,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4766
4939
|
if (completed) {
|
|
4767
4940
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4768
4941
|
if (apiSessionToken) {
|
|
4769
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
4942
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4770
4943
|
if (serverKind === "remote") {
|
|
4771
4944
|
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4772
4945
|
}
|
|
@@ -4790,7 +4963,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4790
4963
|
if (serverKind === "remote" && checkoutPath && token) {
|
|
4791
4964
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
4792
4965
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4793
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4966
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4794
4967
|
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
4795
4968
|
}
|
|
4796
4969
|
const registered = await registerProjectViaServer(context, {
|
|
@@ -4799,6 +4972,12 @@ async function runControlPlaneInit(context, options) {
|
|
|
4799
4972
|
});
|
|
4800
4973
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4801
4974
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
4975
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
4976
|
+
ok: false,
|
|
4977
|
+
ready: false,
|
|
4978
|
+
labelsReady: false,
|
|
4979
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4980
|
+
}));
|
|
4802
4981
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4803
4982
|
remote: true,
|
|
4804
4983
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4829,6 +5008,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4829
5008
|
githubAuth,
|
|
4830
5009
|
deviceAuth,
|
|
4831
5010
|
githubAuthWarning: remoteGhTokenWarning,
|
|
5011
|
+
labelSetup,
|
|
4832
5012
|
pi,
|
|
4833
5013
|
doctor
|
|
4834
5014
|
};
|
|
@@ -4983,24 +5163,17 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4983
5163
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4984
5164
|
}
|
|
4985
5165
|
}
|
|
4986
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4987
|
-
const
|
|
4988
|
-
message: "GitHub Projects status sync",
|
|
4989
|
-
options: [
|
|
4990
|
-
{ value: "off", label: "Off" },
|
|
4991
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4992
|
-
]
|
|
4993
|
-
});
|
|
4994
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4995
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
5166
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
5167
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken);
|
|
4996
5168
|
const result = await runControlPlaneInit(context, {
|
|
4997
5169
|
server: serverChoice,
|
|
4998
5170
|
remoteUrl,
|
|
4999
5171
|
repoSlug,
|
|
5000
5172
|
githubToken,
|
|
5001
5173
|
githubAuthMethod: authMethod,
|
|
5002
|
-
githubProject,
|
|
5003
|
-
githubProjectStatusField,
|
|
5174
|
+
githubProject: projectConfig.githubProject,
|
|
5175
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
5176
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
5004
5177
|
remoteCheckout,
|
|
5005
5178
|
repair,
|
|
5006
5179
|
privateStateOnly
|
|
@@ -5099,7 +5272,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
5099
5272
|
}
|
|
5100
5273
|
|
|
5101
5274
|
// packages/cli/src/commands/github.ts
|
|
5102
|
-
import { spawnSync as
|
|
5275
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5103
5276
|
function printPayload(context, payload, fallback) {
|
|
5104
5277
|
if (context.outputMode === "json")
|
|
5105
5278
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -5107,7 +5280,7 @@ function printPayload(context, payload, fallback) {
|
|
|
5107
5280
|
console.log(fallback);
|
|
5108
5281
|
}
|
|
5109
5282
|
function readGhToken() {
|
|
5110
|
-
const result =
|
|
5283
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
5111
5284
|
if (result.status !== 0) {
|
|
5112
5285
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
5113
5286
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -6139,14 +6312,10 @@ async function executeRemote(context, args) {
|
|
|
6139
6312
|
}
|
|
6140
6313
|
|
|
6141
6314
|
// packages/cli/src/commands/run.ts
|
|
6142
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6143
|
-
import { resolve as resolve20 } from "path";
|
|
6144
6315
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6145
6316
|
import {
|
|
6146
6317
|
listAuthorityRuns as listAuthorityRuns3,
|
|
6147
|
-
readAuthorityRun as readAuthorityRun4
|
|
6148
|
-
readJsonlFile as readJsonlFile4,
|
|
6149
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6318
|
+
readAuthorityRun as readAuthorityRun4
|
|
6150
6319
|
} from "@rig/runtime/control-plane/authority-files";
|
|
6151
6320
|
import {
|
|
6152
6321
|
cleanupRunState,
|
|
@@ -6162,9 +6331,9 @@ import {
|
|
|
6162
6331
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
6163
6332
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
6164
6333
|
|
|
6165
|
-
// packages/cli/src/commands/_operator-
|
|
6334
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
6166
6335
|
import { createInterface } from "readline";
|
|
6167
|
-
|
|
6336
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
6168
6337
|
var CANONICAL_STAGES = [
|
|
6169
6338
|
"Connect",
|
|
6170
6339
|
"GitHub/task sync",
|
|
@@ -6179,18 +6348,141 @@ var CANONICAL_STAGES = [
|
|
|
6179
6348
|
"Merge",
|
|
6180
6349
|
"Complete"
|
|
6181
6350
|
];
|
|
6351
|
+
function logDetail(log3) {
|
|
6352
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6353
|
+
}
|
|
6354
|
+
function entryId(entry, fallback) {
|
|
6355
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6356
|
+
}
|
|
6182
6357
|
function renderOperatorSnapshot(snapshot) {
|
|
6183
6358
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
6184
6359
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
6185
6360
|
const status = String(run.status ?? "unknown");
|
|
6186
6361
|
const logs = snapshot.logs ?? [];
|
|
6362
|
+
const latestByStage = new Map;
|
|
6363
|
+
for (const log3 of logs) {
|
|
6364
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6365
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6366
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6367
|
+
if (stage)
|
|
6368
|
+
latestByStage.set(stage, log3);
|
|
6369
|
+
}
|
|
6187
6370
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
6188
|
-
const match =
|
|
6189
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6371
|
+
const match = latestByStage.get(stage);
|
|
6372
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
6190
6373
|
});
|
|
6191
6374
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
6192
6375
|
`);
|
|
6193
6376
|
}
|
|
6377
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6378
|
+
let lastSnapshot = "";
|
|
6379
|
+
const assistantTextById = new Map;
|
|
6380
|
+
const seenTimeline = new Set;
|
|
6381
|
+
const seenLogs = new Set;
|
|
6382
|
+
const writeLine = (line) => output.write(`${line}
|
|
6383
|
+
`);
|
|
6384
|
+
return {
|
|
6385
|
+
renderSnapshot(snapshot) {
|
|
6386
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6387
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6388
|
+
writeLine(rendered);
|
|
6389
|
+
lastSnapshot = rendered;
|
|
6390
|
+
}
|
|
6391
|
+
},
|
|
6392
|
+
renderTimeline(entries) {
|
|
6393
|
+
for (const [index, entry] of entries.entries()) {
|
|
6394
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6395
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6396
|
+
const text2 = entry.text;
|
|
6397
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6398
|
+
if (text2.startsWith(previousText)) {
|
|
6399
|
+
const delta = text2.slice(previousText.length);
|
|
6400
|
+
if (delta)
|
|
6401
|
+
output.write(delta);
|
|
6402
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6403
|
+
writeLine(`
|
|
6404
|
+
[Pi assistant]`);
|
|
6405
|
+
output.write(text2);
|
|
6406
|
+
}
|
|
6407
|
+
assistantTextById.set(id, text2);
|
|
6408
|
+
continue;
|
|
6409
|
+
}
|
|
6410
|
+
if (seenTimeline.has(id))
|
|
6411
|
+
continue;
|
|
6412
|
+
seenTimeline.add(id);
|
|
6413
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6414
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6415
|
+
continue;
|
|
6416
|
+
}
|
|
6417
|
+
if (entry.type === "timeline_warning") {
|
|
6418
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
},
|
|
6422
|
+
renderLogs(entries) {
|
|
6423
|
+
for (const [index, entry] of entries.entries()) {
|
|
6424
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6425
|
+
if (seenLogs.has(id))
|
|
6426
|
+
continue;
|
|
6427
|
+
seenLogs.add(id);
|
|
6428
|
+
const title = String(entry.title ?? "");
|
|
6429
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6430
|
+
continue;
|
|
6431
|
+
const detail = logDetail(entry);
|
|
6432
|
+
if (!detail)
|
|
6433
|
+
continue;
|
|
6434
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
function createOperatorSurface(options = {}) {
|
|
6440
|
+
const input = options.input ?? process.stdin;
|
|
6441
|
+
const output = options.output ?? process.stdout;
|
|
6442
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6443
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6444
|
+
const writeLine = (line) => output.write(`${line}
|
|
6445
|
+
`);
|
|
6446
|
+
return {
|
|
6447
|
+
mode: "pi-compatible-text",
|
|
6448
|
+
...renderer,
|
|
6449
|
+
info: writeLine,
|
|
6450
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6451
|
+
`),
|
|
6452
|
+
attachCommandInput(handler) {
|
|
6453
|
+
if (options.interactive === false || !input.isTTY)
|
|
6454
|
+
return null;
|
|
6455
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6456
|
+
rl.on("line", (line) => {
|
|
6457
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6458
|
+
});
|
|
6459
|
+
return { close: () => rl.close() };
|
|
6460
|
+
}
|
|
6461
|
+
};
|
|
6462
|
+
}
|
|
6463
|
+
function taskId(task) {
|
|
6464
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6465
|
+
}
|
|
6466
|
+
function taskTitle(task) {
|
|
6467
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6468
|
+
}
|
|
6469
|
+
function taskStatus(task) {
|
|
6470
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6471
|
+
}
|
|
6472
|
+
function renderTaskPickerRows(tasks) {
|
|
6473
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6474
|
+
}
|
|
6475
|
+
async function promptForTaskSelection(question) {
|
|
6476
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6477
|
+
try {
|
|
6478
|
+
return await rl.question(question);
|
|
6479
|
+
} finally {
|
|
6480
|
+
rl.close();
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6485
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
6194
6486
|
function runStatusFromPayload(payload) {
|
|
6195
6487
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
6196
6488
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -6212,11 +6504,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
6212
6504
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
6213
6505
|
return { action: "continue", message: "Steering message queued." };
|
|
6214
6506
|
}
|
|
6215
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6507
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
6216
6508
|
const run = await getRunDetailsViaServer(context, runId);
|
|
6217
6509
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
6218
|
-
const
|
|
6219
|
-
|
|
6510
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6511
|
+
entries: [{
|
|
6512
|
+
id: `timeline-unavailable:${runId}`,
|
|
6513
|
+
type: "timeline_warning",
|
|
6514
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6515
|
+
createdAt: new Date().toISOString()
|
|
6516
|
+
}],
|
|
6517
|
+
nextCursor: options.timelineCursor ?? null
|
|
6518
|
+
}));
|
|
6519
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6520
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6521
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6522
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
6220
6523
|
}
|
|
6221
6524
|
async function attachRunOperatorView(context, input) {
|
|
6222
6525
|
let steered = false;
|
|
@@ -6224,40 +6527,41 @@ async function attachRunOperatorView(context, input) {
|
|
|
6224
6527
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
6225
6528
|
steered = true;
|
|
6226
6529
|
}
|
|
6530
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
6227
6531
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6228
6532
|
if (context.outputMode === "text") {
|
|
6229
|
-
|
|
6533
|
+
surface.renderSnapshot(snapshot);
|
|
6534
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6535
|
+
surface.renderLogs(snapshot.logs);
|
|
6230
6536
|
if (steered)
|
|
6231
|
-
|
|
6537
|
+
surface.info("Steering message queued.");
|
|
6232
6538
|
}
|
|
6233
6539
|
let detached = false;
|
|
6234
|
-
let
|
|
6540
|
+
let commandInput = null;
|
|
6235
6541
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
6236
6542
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
}
|
|
6247
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6543
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6544
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6545
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6546
|
+
if (result.message)
|
|
6547
|
+
surface.info(result.message);
|
|
6548
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6549
|
+
detached = true;
|
|
6550
|
+
commandInput?.close();
|
|
6551
|
+
}
|
|
6248
6552
|
});
|
|
6249
6553
|
}
|
|
6250
|
-
let lastRendered = snapshot.rendered;
|
|
6251
6554
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6555
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6252
6556
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6253
6557
|
await Bun.sleep(pollMs);
|
|
6254
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6558
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6559
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6560
|
+
surface.renderSnapshot(snapshot);
|
|
6561
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6562
|
+
surface.renderLogs(snapshot.logs);
|
|
6259
6563
|
}
|
|
6260
|
-
|
|
6564
|
+
commandInput?.close();
|
|
6261
6565
|
}
|
|
6262
6566
|
return { ...snapshot, steered, detached };
|
|
6263
6567
|
}
|
|
@@ -6431,34 +6735,24 @@ async function executeRun(context, args) {
|
|
|
6431
6735
|
if (!run.value) {
|
|
6432
6736
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6433
6737
|
}
|
|
6434
|
-
const
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
return events2;
|
|
6443
|
-
};
|
|
6444
|
-
const events = printEvents();
|
|
6738
|
+
const renderer = createPiRunStreamRenderer();
|
|
6739
|
+
let cursor = null;
|
|
6740
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
6741
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6742
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
6743
|
+
if (context.outputMode === "text") {
|
|
6744
|
+
renderer.renderTimeline(events);
|
|
6745
|
+
}
|
|
6445
6746
|
if (follow.value && context.outputMode === "text") {
|
|
6446
|
-
let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6447
6747
|
while (true) {
|
|
6448
6748
|
await Bun.sleep(1000);
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
continue;
|
|
6454
|
-
const delta = next.slice(lastLength);
|
|
6455
|
-
lastLength = next.length;
|
|
6456
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6457
|
-
console.log(line);
|
|
6458
|
-
}
|
|
6749
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
6750
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6751
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
6752
|
+
renderer.renderTimeline(nextEvents);
|
|
6459
6753
|
}
|
|
6460
6754
|
}
|
|
6461
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
6755
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6462
6756
|
}
|
|
6463
6757
|
case "attach": {
|
|
6464
6758
|
let pending = rest;
|
|
@@ -6748,10 +7042,10 @@ async function executeServer(context, args, options) {
|
|
|
6748
7042
|
}
|
|
6749
7043
|
|
|
6750
7044
|
// packages/cli/src/commands/task.ts
|
|
6751
|
-
import { readFileSync as
|
|
6752
|
-
import { spawnSync as
|
|
6753
|
-
import { createInterface as
|
|
6754
|
-
import { resolve as
|
|
7045
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7046
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7047
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
7048
|
+
import { resolve as resolve20 } from "path";
|
|
6755
7049
|
import {
|
|
6756
7050
|
taskArtifactDir,
|
|
6757
7051
|
taskArtifacts,
|
|
@@ -6769,19 +7063,9 @@ import {
|
|
|
6769
7063
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6770
7064
|
|
|
6771
7065
|
// packages/cli/src/commands/_task-picker.ts
|
|
6772
|
-
|
|
6773
|
-
function taskId(task) {
|
|
7066
|
+
function taskId2(task) {
|
|
6774
7067
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6775
7068
|
}
|
|
6776
|
-
function taskTitle(task) {
|
|
6777
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6778
|
-
}
|
|
6779
|
-
function taskStatus(task) {
|
|
6780
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6781
|
-
}
|
|
6782
|
-
function renderTaskPickerRows(tasks) {
|
|
6783
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6784
|
-
}
|
|
6785
7069
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6786
7070
|
if (tasks.length === 0)
|
|
6787
7071
|
return null;
|
|
@@ -6791,17 +7075,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6791
7075
|
if (!isTty) {
|
|
6792
7076
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6793
7077
|
}
|
|
6794
|
-
const prompt = io.prompt ??
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
} finally {
|
|
6799
|
-
rl.close();
|
|
6800
|
-
}
|
|
6801
|
-
});
|
|
6802
|
-
console.log("Select Rig task:");
|
|
7078
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
7079
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
7080
|
+
`) };
|
|
7081
|
+
renderer.writeLine("Select Rig task:");
|
|
6803
7082
|
for (const row of renderTaskPickerRows(tasks))
|
|
6804
|
-
|
|
7083
|
+
renderer.writeLine(` ${row}`);
|
|
6805
7084
|
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6806
7085
|
if (!answer)
|
|
6807
7086
|
return null;
|
|
@@ -6809,7 +7088,7 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6809
7088
|
const index = Number.parseInt(answer, 10) - 1;
|
|
6810
7089
|
return tasks[index] ?? null;
|
|
6811
7090
|
}
|
|
6812
|
-
return tasks.find((task) =>
|
|
7091
|
+
return tasks.find((task) => taskId2(task) === answer) ?? null;
|
|
6813
7092
|
}
|
|
6814
7093
|
|
|
6815
7094
|
// packages/cli/src/commands/task.ts
|
|
@@ -6887,7 +7166,7 @@ function normalizePrMode(value) {
|
|
|
6887
7166
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6888
7167
|
}
|
|
6889
7168
|
function detectLocalDirtyState(projectRoot) {
|
|
6890
|
-
const result =
|
|
7169
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6891
7170
|
if (result.status !== 0)
|
|
6892
7171
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6893
7172
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6921,7 +7200,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6921
7200
|
if (explicit)
|
|
6922
7201
|
return { mode: explicit, state };
|
|
6923
7202
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6924
|
-
const rl =
|
|
7203
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6925
7204
|
try {
|
|
6926
7205
|
const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
|
|
6927
7206
|
return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
|
|
@@ -7006,12 +7285,12 @@ async function executeTask(context, args, options) {
|
|
|
7006
7285
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
7007
7286
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
7008
7287
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
7009
|
-
const
|
|
7010
|
-
if (!
|
|
7288
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7289
|
+
if (!taskId3)
|
|
7011
7290
|
throw new CliError2("task show requires a task id.", 2);
|
|
7012
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7291
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
7013
7292
|
if (!task)
|
|
7014
|
-
throw new CliError2(`Task not found: ${
|
|
7293
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
7015
7294
|
const summary = summarizeTask(task, { raw: true });
|
|
7016
7295
|
if (context.outputMode === "text")
|
|
7017
7296
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -7082,7 +7361,7 @@ async function executeTask(context, args, options) {
|
|
|
7082
7361
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
7083
7362
|
let content;
|
|
7084
7363
|
if (fileFlag.value) {
|
|
7085
|
-
content =
|
|
7364
|
+
content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
|
|
7086
7365
|
} else {
|
|
7087
7366
|
content = await readStdin();
|
|
7088
7367
|
}
|
|
@@ -7303,9 +7582,9 @@ async function executeTask(context, args, options) {
|
|
|
7303
7582
|
}
|
|
7304
7583
|
|
|
7305
7584
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7306
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7307
|
-
import { resolve as
|
|
7308
|
-
import { spawn as spawn2, spawnSync as
|
|
7585
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7586
|
+
import { resolve as resolve21 } from "path";
|
|
7587
|
+
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
7309
7588
|
import { createInterface as createLineInterface } from "readline";
|
|
7310
7589
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7311
7590
|
import {
|
|
@@ -7330,8 +7609,7 @@ import {
|
|
|
7330
7609
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7331
7610
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7332
7611
|
import {
|
|
7333
|
-
buildTaskRunLifecycleComment
|
|
7334
|
-
updateConfiguredTaskSourceTask
|
|
7612
|
+
buildTaskRunLifecycleComment
|
|
7335
7613
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7336
7614
|
import {
|
|
7337
7615
|
closeIssueAfterMergedPr,
|
|
@@ -7369,7 +7647,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7369
7647
|
};
|
|
7370
7648
|
}
|
|
7371
7649
|
function runGitSync(cwd, args, input) {
|
|
7372
|
-
const result =
|
|
7650
|
+
const result = spawnSync4("git", [...args], {
|
|
7373
7651
|
cwd,
|
|
7374
7652
|
input,
|
|
7375
7653
|
encoding: "utf8",
|
|
@@ -7387,12 +7665,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7387
7665
|
return 0;
|
|
7388
7666
|
let copied = 0;
|
|
7389
7667
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7390
|
-
const sourcePath =
|
|
7391
|
-
const targetPath =
|
|
7668
|
+
const sourcePath = resolve21(sourceRoot, relativePath);
|
|
7669
|
+
const targetPath = resolve21(targetRoot, relativePath);
|
|
7392
7670
|
try {
|
|
7393
7671
|
if (!statSync2(sourcePath).isFile())
|
|
7394
7672
|
continue;
|
|
7395
|
-
mkdirSync8(
|
|
7673
|
+
mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
|
|
7396
7674
|
copyFileSync3(sourcePath, targetPath);
|
|
7397
7675
|
copied += 1;
|
|
7398
7676
|
} catch {}
|
|
@@ -7431,7 +7709,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7431
7709
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7432
7710
|
return {
|
|
7433
7711
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7434
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
7712
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7435
7713
|
};
|
|
7436
7714
|
}
|
|
7437
7715
|
function positiveInt(value, fallback) {
|
|
@@ -7542,9 +7820,9 @@ function createCommandRunner(binary) {
|
|
|
7542
7820
|
const stderrChunks = [];
|
|
7543
7821
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7544
7822
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7545
|
-
return await new Promise((
|
|
7546
|
-
child.once("error", (error) =>
|
|
7547
|
-
child.once("close", (code) =>
|
|
7823
|
+
return await new Promise((resolve22) => {
|
|
7824
|
+
child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
|
|
7825
|
+
child.once("close", (code) => resolve22({
|
|
7548
7826
|
exitCode: code ?? 1,
|
|
7549
7827
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7550
7828
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7564,12 +7842,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7564
7842
|
return input.configuredBranch;
|
|
7565
7843
|
}
|
|
7566
7844
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7567
|
-
const
|
|
7568
|
-
if (!
|
|
7845
|
+
const taskId3 = input.taskId?.trim();
|
|
7846
|
+
if (!taskId3) {
|
|
7569
7847
|
return { status: "skipped" };
|
|
7570
7848
|
}
|
|
7571
|
-
const
|
|
7572
|
-
const
|
|
7849
|
+
const configInput = input.config ?? null;
|
|
7850
|
+
const config = configInput ?? {};
|
|
7851
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7573
7852
|
if (prMode === "off" || prMode === "ask") {
|
|
7574
7853
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7575
7854
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7585,7 +7864,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7585
7864
|
gitCommand
|
|
7586
7865
|
});
|
|
7587
7866
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7588
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
7867
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7589
7868
|
const stage = input.appendStage ?? (() => {
|
|
7590
7869
|
return;
|
|
7591
7870
|
});
|
|
@@ -7603,7 +7882,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7603
7882
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7604
7883
|
const commit = await commitRunChanges({
|
|
7605
7884
|
cwd: workspace,
|
|
7606
|
-
message: `rig: complete task ${
|
|
7885
|
+
message: `rig: complete task ${taskId3}`,
|
|
7607
7886
|
command: gitCommand
|
|
7608
7887
|
});
|
|
7609
7888
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7611,14 +7890,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7611
7890
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7612
7891
|
const pr = await prAutomation({
|
|
7613
7892
|
projectRoot: workspace,
|
|
7614
|
-
taskId:
|
|
7893
|
+
taskId: taskId3,
|
|
7615
7894
|
runId: input.runId,
|
|
7616
7895
|
branch,
|
|
7617
7896
|
config,
|
|
7618
7897
|
sourceTask: input.sourceTask,
|
|
7619
7898
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
7620
|
-
artifactRoot:
|
|
7899
|
+
artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
|
|
7621
7900
|
command: ghCommand,
|
|
7901
|
+
gitCommand,
|
|
7622
7902
|
steerPi,
|
|
7623
7903
|
lifecycle: {
|
|
7624
7904
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7626,7 +7906,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7626
7906
|
try {
|
|
7627
7907
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7628
7908
|
await updateTaskSource(input.projectRoot, {
|
|
7629
|
-
taskId:
|
|
7909
|
+
taskId: taskId3,
|
|
7630
7910
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7631
7911
|
update: {
|
|
7632
7912
|
status: "under_review",
|
|
@@ -7656,7 +7936,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7656
7936
|
`), "reviewing", "error");
|
|
7657
7937
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7658
7938
|
await updateTaskSource(input.projectRoot, {
|
|
7659
|
-
taskId:
|
|
7939
|
+
taskId: taskId3,
|
|
7660
7940
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7661
7941
|
update: {
|
|
7662
7942
|
status: "ci_fixing",
|
|
@@ -7667,8 +7947,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7667
7947
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7668
7948
|
})
|
|
7669
7949
|
}
|
|
7670
|
-
}).catch(() => {
|
|
7671
|
-
return;
|
|
7672
7950
|
});
|
|
7673
7951
|
}
|
|
7674
7952
|
},
|
|
@@ -7676,7 +7954,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7676
7954
|
stage("Merge", prUrl, "running", "tool");
|
|
7677
7955
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7678
7956
|
await updateTaskSource(input.projectRoot, {
|
|
7679
|
-
taskId:
|
|
7957
|
+
taskId: taskId3,
|
|
7680
7958
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7681
7959
|
update: {
|
|
7682
7960
|
status: "merging",
|
|
@@ -7687,8 +7965,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7687
7965
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7688
7966
|
})
|
|
7689
7967
|
}
|
|
7690
|
-
}).catch(() => {
|
|
7691
|
-
return;
|
|
7692
7968
|
});
|
|
7693
7969
|
}
|
|
7694
7970
|
},
|
|
@@ -7705,7 +7981,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7705
7981
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7706
7982
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7707
7983
|
await updateTaskSource(input.projectRoot, {
|
|
7708
|
-
taskId:
|
|
7984
|
+
taskId: taskId3,
|
|
7709
7985
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7710
7986
|
update: {
|
|
7711
7987
|
status: "needs_attention",
|
|
@@ -7717,8 +7993,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7717
7993
|
errorText: detail
|
|
7718
7994
|
})
|
|
7719
7995
|
}
|
|
7720
|
-
}).catch(() => {
|
|
7721
|
-
|
|
7996
|
+
}).catch((error) => {
|
|
7997
|
+
try {
|
|
7998
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
7999
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
8000
|
+
title: "Task source needs-attention update failed",
|
|
8001
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
8002
|
+
tone: "error",
|
|
8003
|
+
status: "needs_attention",
|
|
8004
|
+
createdAt: new Date().toISOString()
|
|
8005
|
+
});
|
|
8006
|
+
} catch {}
|
|
7722
8007
|
});
|
|
7723
8008
|
}
|
|
7724
8009
|
return { status: "needs_attention", pr };
|
|
@@ -7726,7 +8011,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7726
8011
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7727
8012
|
await closeIssueAfterMergedPr({
|
|
7728
8013
|
projectRoot: input.projectRoot,
|
|
7729
|
-
taskId:
|
|
8014
|
+
taskId: taskId3,
|
|
7730
8015
|
runId: input.runId,
|
|
7731
8016
|
prUrl: pr.prUrl,
|
|
7732
8017
|
sourceTask: input.sourceTask,
|
|
@@ -7736,12 +8021,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7736
8021
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7737
8022
|
return { status: "completed", pr };
|
|
7738
8023
|
}
|
|
7739
|
-
function summarizeValidationFailure(projectRoot,
|
|
7740
|
-
if (!
|
|
8024
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
8025
|
+
if (!taskId3) {
|
|
7741
8026
|
return null;
|
|
7742
8027
|
}
|
|
7743
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7744
|
-
const summary = readJsonFile3(
|
|
8028
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
8029
|
+
const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
|
|
7745
8030
|
if (!summary || summary.status !== "fail") {
|
|
7746
8031
|
continue;
|
|
7747
8032
|
}
|
|
@@ -7822,9 +8107,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7822
8107
|
if (!input.taskId || !input.workspaceDir) {
|
|
7823
8108
|
return { accepted: false, reason: null };
|
|
7824
8109
|
}
|
|
7825
|
-
const artifactDir =
|
|
7826
|
-
const reviewStatusPath =
|
|
7827
|
-
const taskResultPath =
|
|
8110
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8111
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8112
|
+
const taskResultPath = resolve21(artifactDir, "task-result.json");
|
|
7828
8113
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7829
8114
|
if (reviewStatus !== "APPROVED") {
|
|
7830
8115
|
return { accepted: false, reason: null };
|
|
@@ -7861,12 +8146,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7861
8146
|
if (!input.taskId || !input.workspaceDir) {
|
|
7862
8147
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7863
8148
|
}
|
|
7864
|
-
const artifactDir =
|
|
7865
|
-
const reviewStatePath =
|
|
7866
|
-
const reviewFeedbackPath =
|
|
7867
|
-
const reviewStatusPath =
|
|
7868
|
-
const failedApproachesPath =
|
|
7869
|
-
const validationSummaryPath =
|
|
8149
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8150
|
+
const reviewStatePath = resolve21(artifactDir, "review-state.json");
|
|
8151
|
+
const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
|
|
8152
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8153
|
+
const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
8154
|
+
const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
|
|
7870
8155
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7871
8156
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7872
8157
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7920,12 +8205,60 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7920
8205
|
}
|
|
7921
8206
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7922
8207
|
}
|
|
8208
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8209
|
+
let nextAssistantText = input.assistantText;
|
|
8210
|
+
if (input.record.type === "message_update") {
|
|
8211
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8212
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8213
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8214
|
+
}
|
|
8215
|
+
} else if (input.record.type === "stream_event") {
|
|
8216
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8217
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8218
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8219
|
+
nextAssistantText += delta.text;
|
|
8220
|
+
}
|
|
8221
|
+
} else if (input.record.type === "assistant") {
|
|
8222
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8223
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8224
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8225
|
+
if (fullText.length > nextAssistantText.length)
|
|
8226
|
+
nextAssistantText = fullText;
|
|
8227
|
+
}
|
|
8228
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8229
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8230
|
+
id: input.messageId,
|
|
8231
|
+
type: "assistant_message",
|
|
8232
|
+
text: nextAssistantText,
|
|
8233
|
+
state: "streaming",
|
|
8234
|
+
createdAt: new Date().toISOString()
|
|
8235
|
+
});
|
|
8236
|
+
}
|
|
8237
|
+
return nextAssistantText;
|
|
8238
|
+
}
|
|
8239
|
+
function appendToolTimelineFromLog(input) {
|
|
8240
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8241
|
+
if (title !== "Tool activity")
|
|
8242
|
+
return;
|
|
8243
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8244
|
+
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;
|
|
8245
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8246
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8247
|
+
id: `tool:${logId}`,
|
|
8248
|
+
type: "tool_execution_update",
|
|
8249
|
+
toolName,
|
|
8250
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8251
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8252
|
+
payload,
|
|
8253
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8254
|
+
});
|
|
8255
|
+
}
|
|
7923
8256
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7924
|
-
if (!
|
|
8257
|
+
if (!existsSync12(reviewStatusPath)) {
|
|
7925
8258
|
return null;
|
|
7926
8259
|
}
|
|
7927
8260
|
try {
|
|
7928
|
-
const status =
|
|
8261
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7929
8262
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7930
8263
|
} catch {
|
|
7931
8264
|
return null;
|
|
@@ -7943,14 +8276,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7943
8276
|
function runSourceTaskIdentity(sourceTask) {
|
|
7944
8277
|
return sourceTask;
|
|
7945
8278
|
}
|
|
7946
|
-
|
|
7947
|
-
if (!
|
|
8279
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8280
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8281
|
+
return null;
|
|
8282
|
+
const record = sourceTask;
|
|
8283
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8284
|
+
if (direct?.trim())
|
|
8285
|
+
return direct.trim();
|
|
8286
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8287
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8288
|
+
}
|
|
8289
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8290
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8291
|
+
id: input.taskId,
|
|
8292
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8293
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8294
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8295
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8296
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8297
|
+
});
|
|
8298
|
+
if (serverResult.ok === false) {
|
|
8299
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8300
|
+
}
|
|
8301
|
+
return {
|
|
8302
|
+
updated: serverResult.ok !== false,
|
|
8303
|
+
taskId: input.taskId,
|
|
8304
|
+
status: input.update.status,
|
|
8305
|
+
source: "server",
|
|
8306
|
+
sourceKind: "server",
|
|
8307
|
+
projectSync: serverResult.projectSync
|
|
8308
|
+
};
|
|
8309
|
+
};
|
|
8310
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8311
|
+
if (!taskId3)
|
|
7948
8312
|
return;
|
|
7949
8313
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7950
8314
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7951
8315
|
return;
|
|
7952
8316
|
const result = await updateTaskSource(projectRoot, {
|
|
7953
|
-
taskId:
|
|
8317
|
+
taskId: taskId3,
|
|
7954
8318
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7955
8319
|
update: {
|
|
7956
8320
|
status,
|
|
@@ -7969,14 +8333,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7969
8333
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7970
8334
|
}
|
|
7971
8335
|
}
|
|
7972
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8336
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7973
8337
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7974
8338
|
const raw = run?.sourceTask;
|
|
7975
8339
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7976
8340
|
return null;
|
|
7977
8341
|
}
|
|
7978
8342
|
const record = raw;
|
|
7979
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8343
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7980
8344
|
if (!id) {
|
|
7981
8345
|
return null;
|
|
7982
8346
|
}
|
|
@@ -8112,15 +8476,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8112
8476
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
8113
8477
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
8114
8478
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
8115
|
-
const planningArtifactPath =
|
|
8479
|
+
const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
8116
8480
|
const persistedPlanning = {
|
|
8117
8481
|
...planningClassification,
|
|
8118
8482
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
8119
8483
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
8120
8484
|
classifiedAt: new Date().toISOString()
|
|
8121
8485
|
};
|
|
8122
|
-
mkdirSync8(
|
|
8123
|
-
writeFileSync6(
|
|
8486
|
+
mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8487
|
+
writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
8124
8488
|
`, "utf8");
|
|
8125
8489
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
8126
8490
|
prompt = `${prompt}
|
|
@@ -8170,7 +8534,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8170
8534
|
let verificationStarted = false;
|
|
8171
8535
|
let reviewStarted = false;
|
|
8172
8536
|
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
8173
|
-
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ?
|
|
8537
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
|
|
8174
8538
|
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
8175
8539
|
let latestProviderCommand = null;
|
|
8176
8540
|
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
@@ -8253,10 +8617,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8253
8617
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8254
8618
|
status: "running",
|
|
8255
8619
|
worktreePath: latestRuntimeWorkspace,
|
|
8256
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8620
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
8257
8621
|
logRoot: latestLogsDir,
|
|
8258
|
-
sessionPath: latestSessionDir ?
|
|
8259
|
-
sessionLogPath: latestLogsDir ?
|
|
8622
|
+
sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
|
|
8623
|
+
sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
|
|
8260
8624
|
branch: runtimeId
|
|
8261
8625
|
});
|
|
8262
8626
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -8264,7 +8628,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8264
8628
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
8265
8629
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
8266
8630
|
if (readyFile) {
|
|
8267
|
-
mkdirSync8(
|
|
8631
|
+
mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
|
|
8268
8632
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
8269
8633
|
`, "utf8");
|
|
8270
8634
|
}
|
|
@@ -8413,7 +8777,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8413
8777
|
if (providerLogs.length > 0) {
|
|
8414
8778
|
for (const providerLog of providerLogs) {
|
|
8415
8779
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
8780
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8416
8781
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
8782
|
+
if (providerLog.title === "Tool activity")
|
|
8783
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8417
8784
|
}
|
|
8418
8785
|
}
|
|
8419
8786
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8571,7 +8938,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8571
8938
|
let acceptedArtifactObservedAt = null;
|
|
8572
8939
|
let acceptedArtifactPollTimer = null;
|
|
8573
8940
|
let acceptedArtifactKillTimer = null;
|
|
8574
|
-
const attemptExit = await new Promise((
|
|
8941
|
+
const attemptExit = await new Promise((resolve22) => {
|
|
8575
8942
|
let settled = false;
|
|
8576
8943
|
const settle = (result) => {
|
|
8577
8944
|
if (settled)
|
|
@@ -8579,7 +8946,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8579
8946
|
settled = true;
|
|
8580
8947
|
if (acceptedArtifactPollTimer)
|
|
8581
8948
|
clearInterval(acceptedArtifactPollTimer);
|
|
8582
|
-
|
|
8949
|
+
resolve22(result);
|
|
8583
8950
|
};
|
|
8584
8951
|
const pollAcceptedArtifacts = () => {
|
|
8585
8952
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8652,7 +9019,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8652
9019
|
});
|
|
8653
9020
|
for (const pendingLog of pendingLogs) {
|
|
8654
9021
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9022
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8655
9023
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9024
|
+
if (pendingLog.title === "Tool activity")
|
|
9025
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8656
9026
|
}
|
|
8657
9027
|
process.off("SIGTERM", forwardSigterm);
|
|
8658
9028
|
if (attemptExit.error) {
|
|
@@ -8778,8 +9148,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8778
9148
|
}
|
|
8779
9149
|
if (planningClassification.planningRequired) {
|
|
8780
9150
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8781
|
-
const expectedPlanPath =
|
|
8782
|
-
if (!
|
|
9151
|
+
const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
|
|
9152
|
+
if (!existsSync12(expectedPlanPath)) {
|
|
8783
9153
|
const failedAt = new Date().toISOString();
|
|
8784
9154
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8785
9155
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8799,6 +9169,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8799
9169
|
throw new CliError2(failureDetail, 1);
|
|
8800
9170
|
}
|
|
8801
9171
|
}
|
|
9172
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
9173
|
+
appendPiStageLog({
|
|
9174
|
+
projectRoot: context.projectRoot,
|
|
9175
|
+
runId: input.runId,
|
|
9176
|
+
stage: "Validate",
|
|
9177
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
9178
|
+
status: "completed"
|
|
9179
|
+
});
|
|
9180
|
+
if (verificationAction && !reviewStarted) {
|
|
9181
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
9182
|
+
}
|
|
9183
|
+
if (!reviewAction) {
|
|
9184
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
9185
|
+
}
|
|
9186
|
+
if (reviewAction) {
|
|
9187
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
9188
|
+
}
|
|
9189
|
+
const requestedAt = new Date().toISOString();
|
|
9190
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
9191
|
+
status: "reviewing",
|
|
9192
|
+
completedAt: null,
|
|
9193
|
+
errorText: null,
|
|
9194
|
+
serverCloseout: {
|
|
9195
|
+
status: "pending",
|
|
9196
|
+
phase: "queued",
|
|
9197
|
+
requestedAt,
|
|
9198
|
+
updatedAt: requestedAt,
|
|
9199
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
9200
|
+
branch: latestRuntimeBranch,
|
|
9201
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
9202
|
+
}
|
|
9203
|
+
});
|
|
9204
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9205
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9206
|
+
title: "Server-owned closeout requested",
|
|
9207
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9208
|
+
tone: "info",
|
|
9209
|
+
status: "reviewing",
|
|
9210
|
+
createdAt: requestedAt,
|
|
9211
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9212
|
+
});
|
|
9213
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9214
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9215
|
+
await context.emitEvent("command.finished", {
|
|
9216
|
+
command: [
|
|
9217
|
+
"rig",
|
|
9218
|
+
"server",
|
|
9219
|
+
"task-run",
|
|
9220
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9221
|
+
...input.title ? ["--title", input.title] : []
|
|
9222
|
+
],
|
|
9223
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9224
|
+
exitCode: 0,
|
|
9225
|
+
durationMs: 0,
|
|
9226
|
+
startedAt,
|
|
9227
|
+
finishedAt: requestedAt
|
|
9228
|
+
});
|
|
9229
|
+
return;
|
|
9230
|
+
}
|
|
8802
9231
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8803
9232
|
appendPiStageLog({
|
|
8804
9233
|
projectRoot: context.projectRoot,
|
|
@@ -8826,11 +9255,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8826
9255
|
child.stdin.write(message2);
|
|
8827
9256
|
}
|
|
8828
9257
|
child.stdin.end();
|
|
9258
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9259
|
+
let feedbackAssistantText = "";
|
|
9260
|
+
const feedbackPendingToolUses = new Map;
|
|
8829
9261
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8830
9262
|
stdout.on("line", (line) => {
|
|
8831
9263
|
const trimmed = line.trim();
|
|
8832
9264
|
if (!trimmed)
|
|
8833
9265
|
return;
|
|
9266
|
+
try {
|
|
9267
|
+
const record = JSON.parse(trimmed);
|
|
9268
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9269
|
+
runId: input.runId,
|
|
9270
|
+
record,
|
|
9271
|
+
createdAtFallback: new Date().toISOString(),
|
|
9272
|
+
status: "reviewing",
|
|
9273
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9274
|
+
});
|
|
9275
|
+
for (const providerLog of providerLogs) {
|
|
9276
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9277
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9278
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9279
|
+
if (providerLog.title === "Tool activity")
|
|
9280
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9281
|
+
}
|
|
9282
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9283
|
+
projectRoot: context.projectRoot,
|
|
9284
|
+
runId: input.runId,
|
|
9285
|
+
messageId: feedbackAssistantMessageId,
|
|
9286
|
+
record,
|
|
9287
|
+
assistantText: feedbackAssistantText
|
|
9288
|
+
});
|
|
9289
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9290
|
+
if (hadAssistantDelta) {
|
|
9291
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9292
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9293
|
+
}
|
|
9294
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9295
|
+
return;
|
|
9296
|
+
} catch {}
|
|
8834
9297
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8835
9298
|
id: nextRunLogId(),
|
|
8836
9299
|
title: "Pi PR feedback fix output",
|
|
@@ -8856,10 +9319,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8856
9319
|
});
|
|
8857
9320
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8858
9321
|
});
|
|
8859
|
-
const exitCode = await new Promise((
|
|
8860
|
-
child.once("error", () =>
|
|
8861
|
-
child.once("close", (code) =>
|
|
9322
|
+
const exitCode = await new Promise((resolve22) => {
|
|
9323
|
+
child.once("error", () => resolve22(1));
|
|
9324
|
+
child.once("close", (code) => resolve22(code ?? 1));
|
|
8862
9325
|
});
|
|
9326
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9327
|
+
runId: input.runId,
|
|
9328
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9329
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9330
|
+
})) {
|
|
9331
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9332
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9333
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9334
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9335
|
+
}
|
|
9336
|
+
if (feedbackAssistantText.trim()) {
|
|
9337
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9338
|
+
id: feedbackAssistantMessageId,
|
|
9339
|
+
type: "assistant_message",
|
|
9340
|
+
text: feedbackAssistantText,
|
|
9341
|
+
state: "completed",
|
|
9342
|
+
createdAt: new Date().toISOString(),
|
|
9343
|
+
completedAt: new Date().toISOString()
|
|
9344
|
+
});
|
|
9345
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9346
|
+
}
|
|
8863
9347
|
if (exitCode !== 0) {
|
|
8864
9348
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8865
9349
|
}
|
|
@@ -8977,8 +9461,8 @@ async function executeTest(context, args) {
|
|
|
8977
9461
|
}
|
|
8978
9462
|
|
|
8979
9463
|
// packages/cli/src/commands/setup.ts
|
|
8980
|
-
import { existsSync as
|
|
8981
|
-
import { resolve as
|
|
9464
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9465
|
+
import { resolve as resolve22 } from "path";
|
|
8982
9466
|
import { createPluginHost } from "@rig/core";
|
|
8983
9467
|
import {
|
|
8984
9468
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -9041,8 +9525,8 @@ function runSetupInit(projectRoot) {
|
|
|
9041
9525
|
mkdirSync9(stateDir, { recursive: true });
|
|
9042
9526
|
mkdirSync9(logsDir, { recursive: true });
|
|
9043
9527
|
mkdirSync9(artifactsDir, { recursive: true });
|
|
9044
|
-
const failuresPath =
|
|
9045
|
-
if (!
|
|
9528
|
+
const failuresPath = resolve22(stateDir, "failed_approaches.md");
|
|
9529
|
+
if (!existsSync13(failuresPath)) {
|
|
9046
9530
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
9047
9531
|
|
|
9048
9532
|
`, "utf-8");
|
|
@@ -9060,18 +9544,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
9060
9544
|
}
|
|
9061
9545
|
async function runSetupPreflight(projectRoot) {
|
|
9062
9546
|
await runSetupCheck(projectRoot);
|
|
9063
|
-
const validationRoot =
|
|
9064
|
-
if (
|
|
9547
|
+
const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9548
|
+
if (existsSync13(validationRoot)) {
|
|
9065
9549
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
9066
9550
|
for (const validator of validators) {
|
|
9067
|
-
const script =
|
|
9068
|
-
if (
|
|
9551
|
+
const script = resolve22(validationRoot, validator.name, "validate.sh");
|
|
9552
|
+
if (existsSync13(script)) {
|
|
9069
9553
|
console.log(`OK: validator script ${script}`);
|
|
9070
9554
|
}
|
|
9071
9555
|
}
|
|
9072
9556
|
}
|
|
9073
|
-
const hooksRoot =
|
|
9074
|
-
if (
|
|
9557
|
+
const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9558
|
+
if (existsSync13(hooksRoot)) {
|
|
9075
9559
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
9076
9560
|
for (const hook of hooks) {
|
|
9077
9561
|
console.log(`OK: hook ${hook}`);
|
|
@@ -9465,8 +9949,8 @@ var __testOnly = {
|
|
|
9465
9949
|
validateRequiredBugPromptValue
|
|
9466
9950
|
};
|
|
9467
9951
|
// packages/cli/src/launcher.ts
|
|
9468
|
-
import { existsSync as
|
|
9469
|
-
import { basename as basename2, resolve as
|
|
9952
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9953
|
+
import { basename as basename2, resolve as resolve23 } from "path";
|
|
9470
9954
|
import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
|
|
9471
9955
|
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
|
|
9472
9956
|
function parsePolicyMode(value) {
|
|
@@ -9479,7 +9963,7 @@ function parsePolicyMode(value) {
|
|
|
9479
9963
|
throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
|
|
9480
9964
|
}
|
|
9481
9965
|
function hasRigProjectMarker(candidate) {
|
|
9482
|
-
return
|
|
9966
|
+
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"));
|
|
9483
9967
|
}
|
|
9484
9968
|
function resolveProjectRoot({
|
|
9485
9969
|
envProjectRoot,
|
|
@@ -9488,19 +9972,19 @@ function resolveProjectRoot({
|
|
|
9488
9972
|
cwd = process.cwd()
|
|
9489
9973
|
}) {
|
|
9490
9974
|
if (envProjectRoot) {
|
|
9491
|
-
return
|
|
9975
|
+
return resolve23(cwd, envProjectRoot);
|
|
9492
9976
|
}
|
|
9493
9977
|
const fallbackImportDir = importDir ?? cwd;
|
|
9494
9978
|
const execName = basename2(execPath).toLowerCase();
|
|
9495
|
-
const execCandidates = execName === "rig" || execName === "rig.exe" ? [
|
|
9496
|
-
const candidates = [cwd, ...execCandidates,
|
|
9979
|
+
const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
|
|
9980
|
+
const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
|
|
9497
9981
|
for (const candidate of candidates) {
|
|
9498
9982
|
const nearest = resolveNearestRigProjectRoot(candidate);
|
|
9499
9983
|
if (hasRigProjectMarker(nearest)) {
|
|
9500
9984
|
return nearest;
|
|
9501
9985
|
}
|
|
9502
9986
|
}
|
|
9503
|
-
return
|
|
9987
|
+
return resolve23(cwd);
|
|
9504
9988
|
}
|
|
9505
9989
|
function normalizeCliErrorCode(message2, isCliError) {
|
|
9506
9990
|
if (message2.startsWith("Invalid --policy-mode value:")) {
|
|
@@ -9567,7 +10051,7 @@ async function runRigCli(module, options = {}) {
|
|
|
9567
10051
|
runId: context.runId,
|
|
9568
10052
|
outcome,
|
|
9569
10053
|
eventsFile: context.eventBus.getEventsFile(),
|
|
9570
|
-
policyFile:
|
|
10054
|
+
policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
|
|
9571
10055
|
policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
|
|
9572
10056
|
}, null, 2));
|
|
9573
10057
|
}
|