@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.15
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/bin/rig.js
CHANGED
|
@@ -2671,17 +2671,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2671
2671
|
}
|
|
2672
2672
|
|
|
2673
2673
|
// packages/cli/src/commands/_server-client.ts
|
|
2674
|
-
import { spawnSync } from "child_process";
|
|
2675
2674
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2676
2675
|
import { resolve as resolve9 } from "path";
|
|
2677
2676
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2678
|
-
var
|
|
2677
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2679
2678
|
function cleanToken(value) {
|
|
2680
2679
|
const trimmed = value?.trim();
|
|
2681
2680
|
return trimmed ? trimmed : null;
|
|
2682
2681
|
}
|
|
2683
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2684
|
-
|
|
2682
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2683
|
+
const scopedKey = resolve9(projectRoot ?? process.cwd());
|
|
2684
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2685
2685
|
}
|
|
2686
2686
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2687
2687
|
const path = resolve9(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2695,25 +2695,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2695
2695
|
}
|
|
2696
2696
|
}
|
|
2697
2697
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2698
|
-
|
|
2699
|
-
|
|
2698
|
+
const scopedKey = resolve9(projectRoot);
|
|
2699
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2700
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2700
2701
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2701
|
-
if (privateSession)
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
}
|
|
2705
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2706
|
-
if (envToken) {
|
|
2707
|
-
cachedGitHubBearerToken = envToken;
|
|
2708
|
-
return cachedGitHubBearerToken;
|
|
2709
|
-
}
|
|
2710
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2711
|
-
encoding: "utf8",
|
|
2712
|
-
timeout: 5000,
|
|
2713
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2714
|
-
});
|
|
2715
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2716
|
-
return cachedGitHubBearerToken;
|
|
2702
|
+
if (privateSession)
|
|
2703
|
+
return privateSession;
|
|
2704
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2717
2705
|
}
|
|
2718
2706
|
async function ensureServerForCli(projectRoot) {
|
|
2719
2707
|
try {
|
|
@@ -2911,6 +2899,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2911
2899
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2912
2900
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2913
2901
|
}
|
|
2902
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2903
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2904
|
+
if (options.limit !== undefined)
|
|
2905
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2906
|
+
if (options.cursor)
|
|
2907
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2908
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2909
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2910
|
+
}
|
|
2911
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2912
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2913
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2914
|
+
}
|
|
2915
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2916
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2917
|
+
url.searchParams.set("owner", owner);
|
|
2918
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2919
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2920
|
+
}
|
|
2921
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2922
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2923
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2924
|
+
}
|
|
2925
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2926
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2927
|
+
method: "POST",
|
|
2928
|
+
headers: { "content-type": "application/json" },
|
|
2929
|
+
body: JSON.stringify(input)
|
|
2930
|
+
});
|
|
2931
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2932
|
+
}
|
|
2914
2933
|
async function stopRunViaServer(context, runId) {
|
|
2915
2934
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2916
2935
|
method: "POST",
|
|
@@ -3190,6 +3209,9 @@ function permissionAllowsPr(payload) {
|
|
|
3190
3209
|
}
|
|
3191
3210
|
return null;
|
|
3192
3211
|
}
|
|
3212
|
+
function isNotFoundError(error) {
|
|
3213
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3214
|
+
}
|
|
3193
3215
|
function projectCheckoutReady(payload) {
|
|
3194
3216
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3195
3217
|
return null;
|
|
@@ -3222,19 +3244,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3222
3244
|
const checks = [];
|
|
3223
3245
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
3224
3246
|
const taskId = options.taskId?.trim() || null;
|
|
3247
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3248
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3249
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3250
|
+
let legacyServerCompatibility = false;
|
|
3225
3251
|
try {
|
|
3226
3252
|
await request("/api/server/status");
|
|
3227
3253
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3228
3254
|
} catch (error) {
|
|
3229
|
-
|
|
3255
|
+
if (isNotFoundError(error)) {
|
|
3256
|
+
try {
|
|
3257
|
+
await request("/health");
|
|
3258
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3259
|
+
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"));
|
|
3260
|
+
} catch (healthError) {
|
|
3261
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3262
|
+
}
|
|
3263
|
+
} else {
|
|
3264
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3265
|
+
}
|
|
3230
3266
|
}
|
|
3231
3267
|
const repo = readRepoConnection(context.projectRoot);
|
|
3232
|
-
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>`."));
|
|
3268
|
+
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>`."));
|
|
3233
3269
|
try {
|
|
3234
3270
|
const auth = await request("/api/github/auth/status");
|
|
3235
|
-
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>`."));
|
|
3271
|
+
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>`."));
|
|
3236
3272
|
} catch (error) {
|
|
3237
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3273
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3238
3274
|
}
|
|
3239
3275
|
try {
|
|
3240
3276
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3262,9 +3298,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3262
3298
|
try {
|
|
3263
3299
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3264
3300
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3265
|
-
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."));
|
|
3301
|
+
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."));
|
|
3266
3302
|
} catch (error) {
|
|
3267
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3303
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3268
3304
|
}
|
|
3269
3305
|
try {
|
|
3270
3306
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -4176,9 +4212,10 @@ async function executeInbox(context, args) {
|
|
|
4176
4212
|
|
|
4177
4213
|
// packages/cli/src/commands/init.ts
|
|
4178
4214
|
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4179
|
-
import { spawnSync
|
|
4215
|
+
import { spawnSync } from "child_process";
|
|
4180
4216
|
import { resolve as resolve17 } from "path";
|
|
4181
4217
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4218
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
4182
4219
|
|
|
4183
4220
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
4184
4221
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4502,7 +4539,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4502
4539
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4503
4540
|
}
|
|
4504
4541
|
function detectOriginRepoSlug(projectRoot) {
|
|
4505
|
-
const result =
|
|
4542
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4506
4543
|
if (result.status !== 0)
|
|
4507
4544
|
return null;
|
|
4508
4545
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4558,11 +4595,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4558
4595
|
return source;
|
|
4559
4596
|
const projectId = JSON.stringify(options.githubProject);
|
|
4560
4597
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4598
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4599
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4600
|
+
`)},` : "";
|
|
4561
4601
|
return source.replace(` projects: { enabled: false },`, [
|
|
4562
4602
|
` projects: {`,
|
|
4563
4603
|
` enabled: true,`,
|
|
4564
4604
|
` projectId: ${projectId},`,
|
|
4565
|
-
` statusFieldId: ${statusFieldId}
|
|
4605
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4566
4606
|
` },`
|
|
4567
4607
|
].join(`
|
|
4568
4608
|
`));
|
|
@@ -4583,11 +4623,11 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4583
4623
|
}
|
|
4584
4624
|
}
|
|
4585
4625
|
function detectGhLogin() {
|
|
4586
|
-
const result =
|
|
4626
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4587
4627
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4588
4628
|
}
|
|
4589
4629
|
function readGhAuthToken() {
|
|
4590
|
-
const result =
|
|
4630
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4591
4631
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4592
4632
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4593
4633
|
}
|
|
@@ -4596,8 +4636,15 @@ function readGhAuthToken() {
|
|
|
4596
4636
|
async function loadClackPrompts() {
|
|
4597
4637
|
return await import("@clack/prompts");
|
|
4598
4638
|
}
|
|
4639
|
+
function clackTextOptions(options) {
|
|
4640
|
+
return {
|
|
4641
|
+
message: options.message,
|
|
4642
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4643
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4644
|
+
};
|
|
4645
|
+
}
|
|
4599
4646
|
async function promptRequiredText(prompts, options) {
|
|
4600
|
-
const value = await prompts.text(options);
|
|
4647
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4601
4648
|
if (prompts.isCancel(value))
|
|
4602
4649
|
throw new CliError2("Init cancelled.", 1);
|
|
4603
4650
|
const text2 = String(value ?? "").trim();
|
|
@@ -4606,7 +4653,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4606
4653
|
return text2;
|
|
4607
4654
|
}
|
|
4608
4655
|
async function promptOptionalText(prompts, options) {
|
|
4609
|
-
const value = await prompts.text(options);
|
|
4656
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4610
4657
|
if (prompts.isCancel(value))
|
|
4611
4658
|
throw new CliError2("Init cancelled.", 1);
|
|
4612
4659
|
return String(value ?? "").trim();
|
|
@@ -4617,6 +4664,132 @@ async function promptSelect(prompts, options) {
|
|
|
4617
4664
|
throw new CliError2("Init cancelled.", 1);
|
|
4618
4665
|
return String(value);
|
|
4619
4666
|
}
|
|
4667
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4668
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4669
|
+
}
|
|
4670
|
+
function recordArray(value, key) {
|
|
4671
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4672
|
+
return [];
|
|
4673
|
+
const raw = value[key];
|
|
4674
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4675
|
+
}
|
|
4676
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4677
|
+
if (token?.trim()) {
|
|
4678
|
+
try {
|
|
4679
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4680
|
+
} catch (directError) {
|
|
4681
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4682
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4683
|
+
return serverPayload;
|
|
4684
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4688
|
+
}
|
|
4689
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4690
|
+
if (token?.trim()) {
|
|
4691
|
+
try {
|
|
4692
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4693
|
+
} catch (directError) {
|
|
4694
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4695
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4696
|
+
return serverPayload;
|
|
4697
|
+
}
|
|
4698
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4702
|
+
}
|
|
4703
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4704
|
+
running: "Running/In progress",
|
|
4705
|
+
prOpen: "PR open/review",
|
|
4706
|
+
ciFixing: "CI/review fixing",
|
|
4707
|
+
merging: "Merging",
|
|
4708
|
+
done: "Done",
|
|
4709
|
+
needsAttention: "Needs attention"
|
|
4710
|
+
};
|
|
4711
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4712
|
+
running: "In Progress",
|
|
4713
|
+
prOpen: "In Review",
|
|
4714
|
+
ciFixing: "In Review",
|
|
4715
|
+
merging: "Merging",
|
|
4716
|
+
done: "Done",
|
|
4717
|
+
needsAttention: "Needs Attention"
|
|
4718
|
+
};
|
|
4719
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4720
|
+
const statuses = {};
|
|
4721
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4722
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4723
|
+
const value = await promptOptionalText(prompts, {
|
|
4724
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4725
|
+
placeholder: defaultLabel
|
|
4726
|
+
});
|
|
4727
|
+
statuses[key] = value || defaultLabel;
|
|
4728
|
+
}
|
|
4729
|
+
return statuses;
|
|
4730
|
+
}
|
|
4731
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken) {
|
|
4732
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4733
|
+
message: "GitHub Projects status sync",
|
|
4734
|
+
options: [
|
|
4735
|
+
{ value: "off", label: "Off" },
|
|
4736
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4737
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4738
|
+
]
|
|
4739
|
+
});
|
|
4740
|
+
if (projectChoice === "off")
|
|
4741
|
+
return { githubProject: "off" };
|
|
4742
|
+
if (projectChoice === "manual") {
|
|
4743
|
+
return {
|
|
4744
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4745
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4746
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4747
|
+
};
|
|
4748
|
+
}
|
|
4749
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4750
|
+
if (!owner)
|
|
4751
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4752
|
+
const projectsPayload = await listGitHubProjectsForInit(context, owner, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4753
|
+
const projects = recordArray(projectsPayload, "projects");
|
|
4754
|
+
if (projects.length === 0) {
|
|
4755
|
+
const error = typeof projectsPayload.error === "string" ? ` (${projectsPayload.error})` : "";
|
|
4756
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; falling back to manual ProjectV2 ids.`);
|
|
4757
|
+
return {
|
|
4758
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4759
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4760
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4761
|
+
};
|
|
4762
|
+
}
|
|
4763
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4764
|
+
message: "GitHub ProjectV2 project",
|
|
4765
|
+
options: [
|
|
4766
|
+
...projects.map((project) => ({
|
|
4767
|
+
value: String(project.id),
|
|
4768
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4769
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4770
|
+
})),
|
|
4771
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4772
|
+
]
|
|
4773
|
+
});
|
|
4774
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4775
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4776
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4777
|
+
const rawField = fieldPayloadRecord.field;
|
|
4778
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4779
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4780
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4781
|
+
if (options.length === 0) {
|
|
4782
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: await promptManualProjectStatusMapping(prompts) };
|
|
4783
|
+
}
|
|
4784
|
+
const statuses = {};
|
|
4785
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4786
|
+
statuses[key] = await promptSelect(prompts, {
|
|
4787
|
+
message: `Project status option for ${label}`,
|
|
4788
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: String(option.name ?? option.id) }))
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined };
|
|
4792
|
+
}
|
|
4620
4793
|
function sleep2(ms) {
|
|
4621
4794
|
return new Promise((resolve18) => setTimeout(resolve18, ms));
|
|
4622
4795
|
}
|
|
@@ -4745,7 +4918,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4745
4918
|
if (token) {
|
|
4746
4919
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4747
4920
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4748
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4921
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4749
4922
|
if (serverKind === "remote") {
|
|
4750
4923
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4751
4924
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
@@ -4770,7 +4943,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4770
4943
|
if (completed) {
|
|
4771
4944
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4772
4945
|
if (apiSessionToken) {
|
|
4773
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
4946
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4774
4947
|
if (serverKind === "remote") {
|
|
4775
4948
|
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4776
4949
|
}
|
|
@@ -4794,7 +4967,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4794
4967
|
if (serverKind === "remote" && checkoutPath && token) {
|
|
4795
4968
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
4796
4969
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4797
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4970
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4798
4971
|
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
4799
4972
|
}
|
|
4800
4973
|
const registered = await registerProjectViaServer(context, {
|
|
@@ -4803,6 +4976,12 @@ async function runControlPlaneInit(context, options) {
|
|
|
4803
4976
|
});
|
|
4804
4977
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4805
4978
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
4979
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
4980
|
+
ok: false,
|
|
4981
|
+
ready: false,
|
|
4982
|
+
labelsReady: false,
|
|
4983
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4984
|
+
}));
|
|
4806
4985
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4807
4986
|
remote: true,
|
|
4808
4987
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4833,6 +5012,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4833
5012
|
githubAuth,
|
|
4834
5013
|
deviceAuth,
|
|
4835
5014
|
githubAuthWarning: remoteGhTokenWarning,
|
|
5015
|
+
labelSetup,
|
|
4836
5016
|
pi,
|
|
4837
5017
|
doctor
|
|
4838
5018
|
};
|
|
@@ -4987,24 +5167,17 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4987
5167
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4988
5168
|
}
|
|
4989
5169
|
}
|
|
4990
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4991
|
-
const
|
|
4992
|
-
message: "GitHub Projects status sync",
|
|
4993
|
-
options: [
|
|
4994
|
-
{ value: "off", label: "Off" },
|
|
4995
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4996
|
-
]
|
|
4997
|
-
});
|
|
4998
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4999
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
5170
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
5171
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken);
|
|
5000
5172
|
const result = await runControlPlaneInit(context, {
|
|
5001
5173
|
server: serverChoice,
|
|
5002
5174
|
remoteUrl,
|
|
5003
5175
|
repoSlug,
|
|
5004
5176
|
githubToken,
|
|
5005
5177
|
githubAuthMethod: authMethod,
|
|
5006
|
-
githubProject,
|
|
5007
|
-
githubProjectStatusField,
|
|
5178
|
+
githubProject: projectConfig.githubProject,
|
|
5179
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
5180
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
5008
5181
|
remoteCheckout,
|
|
5009
5182
|
repair,
|
|
5010
5183
|
privateStateOnly
|
|
@@ -5103,7 +5276,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
5103
5276
|
}
|
|
5104
5277
|
|
|
5105
5278
|
// packages/cli/src/commands/github.ts
|
|
5106
|
-
import { spawnSync as
|
|
5279
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5107
5280
|
function printPayload(context, payload, fallback) {
|
|
5108
5281
|
if (context.outputMode === "json")
|
|
5109
5282
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -5111,7 +5284,7 @@ function printPayload(context, payload, fallback) {
|
|
|
5111
5284
|
console.log(fallback);
|
|
5112
5285
|
}
|
|
5113
5286
|
function readGhToken() {
|
|
5114
|
-
const result =
|
|
5287
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
5115
5288
|
if (result.status !== 0) {
|
|
5116
5289
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
5117
5290
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -6143,14 +6316,10 @@ async function executeRemote(context, args) {
|
|
|
6143
6316
|
}
|
|
6144
6317
|
|
|
6145
6318
|
// packages/cli/src/commands/run.ts
|
|
6146
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
6147
|
-
import { resolve as resolve20 } from "path";
|
|
6148
6319
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6149
6320
|
import {
|
|
6150
6321
|
listAuthorityRuns as listAuthorityRuns3,
|
|
6151
|
-
readAuthorityRun as readAuthorityRun4
|
|
6152
|
-
readJsonlFile as readJsonlFile4,
|
|
6153
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6322
|
+
readAuthorityRun as readAuthorityRun4
|
|
6154
6323
|
} from "@rig/runtime/control-plane/authority-files";
|
|
6155
6324
|
import {
|
|
6156
6325
|
cleanupRunState,
|
|
@@ -6166,9 +6335,9 @@ import {
|
|
|
6166
6335
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
6167
6336
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
6168
6337
|
|
|
6169
|
-
// packages/cli/src/commands/_operator-
|
|
6338
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
6170
6339
|
import { createInterface } from "readline";
|
|
6171
|
-
|
|
6340
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
6172
6341
|
var CANONICAL_STAGES = [
|
|
6173
6342
|
"Connect",
|
|
6174
6343
|
"GitHub/task sync",
|
|
@@ -6183,18 +6352,141 @@ var CANONICAL_STAGES = [
|
|
|
6183
6352
|
"Merge",
|
|
6184
6353
|
"Complete"
|
|
6185
6354
|
];
|
|
6355
|
+
function logDetail(log3) {
|
|
6356
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6357
|
+
}
|
|
6358
|
+
function entryId(entry, fallback) {
|
|
6359
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6360
|
+
}
|
|
6186
6361
|
function renderOperatorSnapshot(snapshot) {
|
|
6187
6362
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
6188
6363
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
6189
6364
|
const status = String(run.status ?? "unknown");
|
|
6190
6365
|
const logs = snapshot.logs ?? [];
|
|
6366
|
+
const latestByStage = new Map;
|
|
6367
|
+
for (const log3 of logs) {
|
|
6368
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6369
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6370
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6371
|
+
if (stage)
|
|
6372
|
+
latestByStage.set(stage, log3);
|
|
6373
|
+
}
|
|
6191
6374
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
6192
|
-
const match =
|
|
6193
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6375
|
+
const match = latestByStage.get(stage);
|
|
6376
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
6194
6377
|
});
|
|
6195
6378
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
6196
6379
|
`);
|
|
6197
6380
|
}
|
|
6381
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6382
|
+
let lastSnapshot = "";
|
|
6383
|
+
const assistantTextById = new Map;
|
|
6384
|
+
const seenTimeline = new Set;
|
|
6385
|
+
const seenLogs = new Set;
|
|
6386
|
+
const writeLine = (line) => output.write(`${line}
|
|
6387
|
+
`);
|
|
6388
|
+
return {
|
|
6389
|
+
renderSnapshot(snapshot) {
|
|
6390
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6391
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6392
|
+
writeLine(rendered);
|
|
6393
|
+
lastSnapshot = rendered;
|
|
6394
|
+
}
|
|
6395
|
+
},
|
|
6396
|
+
renderTimeline(entries) {
|
|
6397
|
+
for (const [index, entry] of entries.entries()) {
|
|
6398
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6399
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6400
|
+
const text2 = entry.text;
|
|
6401
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6402
|
+
if (text2.startsWith(previousText)) {
|
|
6403
|
+
const delta = text2.slice(previousText.length);
|
|
6404
|
+
if (delta)
|
|
6405
|
+
output.write(delta);
|
|
6406
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6407
|
+
writeLine(`
|
|
6408
|
+
[Pi assistant]`);
|
|
6409
|
+
output.write(text2);
|
|
6410
|
+
}
|
|
6411
|
+
assistantTextById.set(id, text2);
|
|
6412
|
+
continue;
|
|
6413
|
+
}
|
|
6414
|
+
if (seenTimeline.has(id))
|
|
6415
|
+
continue;
|
|
6416
|
+
seenTimeline.add(id);
|
|
6417
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6418
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
if (entry.type === "timeline_warning") {
|
|
6422
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
},
|
|
6426
|
+
renderLogs(entries) {
|
|
6427
|
+
for (const [index, entry] of entries.entries()) {
|
|
6428
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6429
|
+
if (seenLogs.has(id))
|
|
6430
|
+
continue;
|
|
6431
|
+
seenLogs.add(id);
|
|
6432
|
+
const title = String(entry.title ?? "");
|
|
6433
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6434
|
+
continue;
|
|
6435
|
+
const detail = logDetail(entry);
|
|
6436
|
+
if (!detail)
|
|
6437
|
+
continue;
|
|
6438
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
};
|
|
6442
|
+
}
|
|
6443
|
+
function createOperatorSurface(options = {}) {
|
|
6444
|
+
const input = options.input ?? process.stdin;
|
|
6445
|
+
const output = options.output ?? process.stdout;
|
|
6446
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6447
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6448
|
+
const writeLine = (line) => output.write(`${line}
|
|
6449
|
+
`);
|
|
6450
|
+
return {
|
|
6451
|
+
mode: "pi-compatible-text",
|
|
6452
|
+
...renderer,
|
|
6453
|
+
info: writeLine,
|
|
6454
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6455
|
+
`),
|
|
6456
|
+
attachCommandInput(handler) {
|
|
6457
|
+
if (options.interactive === false || !input.isTTY)
|
|
6458
|
+
return null;
|
|
6459
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6460
|
+
rl.on("line", (line) => {
|
|
6461
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6462
|
+
});
|
|
6463
|
+
return { close: () => rl.close() };
|
|
6464
|
+
}
|
|
6465
|
+
};
|
|
6466
|
+
}
|
|
6467
|
+
function taskId(task) {
|
|
6468
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6469
|
+
}
|
|
6470
|
+
function taskTitle(task) {
|
|
6471
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6472
|
+
}
|
|
6473
|
+
function taskStatus(task) {
|
|
6474
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6475
|
+
}
|
|
6476
|
+
function renderTaskPickerRows(tasks) {
|
|
6477
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6478
|
+
}
|
|
6479
|
+
async function promptForTaskSelection(question) {
|
|
6480
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6481
|
+
try {
|
|
6482
|
+
return await rl.question(question);
|
|
6483
|
+
} finally {
|
|
6484
|
+
rl.close();
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6489
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
6198
6490
|
function runStatusFromPayload(payload) {
|
|
6199
6491
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
6200
6492
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -6216,11 +6508,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
6216
6508
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
6217
6509
|
return { action: "continue", message: "Steering message queued." };
|
|
6218
6510
|
}
|
|
6219
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6511
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
6220
6512
|
const run = await getRunDetailsViaServer(context, runId);
|
|
6221
6513
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
6222
|
-
const
|
|
6223
|
-
|
|
6514
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6515
|
+
entries: [{
|
|
6516
|
+
id: `timeline-unavailable:${runId}`,
|
|
6517
|
+
type: "timeline_warning",
|
|
6518
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6519
|
+
createdAt: new Date().toISOString()
|
|
6520
|
+
}],
|
|
6521
|
+
nextCursor: options.timelineCursor ?? null
|
|
6522
|
+
}));
|
|
6523
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6524
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6525
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6526
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
6224
6527
|
}
|
|
6225
6528
|
async function attachRunOperatorView(context, input) {
|
|
6226
6529
|
let steered = false;
|
|
@@ -6228,40 +6531,41 @@ async function attachRunOperatorView(context, input) {
|
|
|
6228
6531
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
6229
6532
|
steered = true;
|
|
6230
6533
|
}
|
|
6534
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
6231
6535
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6232
6536
|
if (context.outputMode === "text") {
|
|
6233
|
-
|
|
6537
|
+
surface.renderSnapshot(snapshot);
|
|
6538
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6539
|
+
surface.renderLogs(snapshot.logs);
|
|
6234
6540
|
if (steered)
|
|
6235
|
-
|
|
6541
|
+
surface.info("Steering message queued.");
|
|
6236
6542
|
}
|
|
6237
6543
|
let detached = false;
|
|
6238
|
-
let
|
|
6544
|
+
let commandInput = null;
|
|
6239
6545
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
6240
6546
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
}
|
|
6251
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6547
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6548
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6549
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6550
|
+
if (result.message)
|
|
6551
|
+
surface.info(result.message);
|
|
6552
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6553
|
+
detached = true;
|
|
6554
|
+
commandInput?.close();
|
|
6555
|
+
}
|
|
6252
6556
|
});
|
|
6253
6557
|
}
|
|
6254
|
-
let lastRendered = snapshot.rendered;
|
|
6255
6558
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6559
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6256
6560
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6257
6561
|
await Bun.sleep(pollMs);
|
|
6258
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6562
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6563
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6564
|
+
surface.renderSnapshot(snapshot);
|
|
6565
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6566
|
+
surface.renderLogs(snapshot.logs);
|
|
6263
6567
|
}
|
|
6264
|
-
|
|
6568
|
+
commandInput?.close();
|
|
6265
6569
|
}
|
|
6266
6570
|
return { ...snapshot, steered, detached };
|
|
6267
6571
|
}
|
|
@@ -6435,34 +6739,24 @@ async function executeRun(context, args) {
|
|
|
6435
6739
|
if (!run.value) {
|
|
6436
6740
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6437
6741
|
}
|
|
6438
|
-
const
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
return events2;
|
|
6447
|
-
};
|
|
6448
|
-
const events = printEvents();
|
|
6742
|
+
const renderer = createPiRunStreamRenderer();
|
|
6743
|
+
let cursor = null;
|
|
6744
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
6745
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6746
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
6747
|
+
if (context.outputMode === "text") {
|
|
6748
|
+
renderer.renderTimeline(events);
|
|
6749
|
+
}
|
|
6449
6750
|
if (follow.value && context.outputMode === "text") {
|
|
6450
|
-
let lastLength = existsSync12(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6451
6751
|
while (true) {
|
|
6452
6752
|
await Bun.sleep(1000);
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
continue;
|
|
6458
|
-
const delta = next.slice(lastLength);
|
|
6459
|
-
lastLength = next.length;
|
|
6460
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6461
|
-
console.log(line);
|
|
6462
|
-
}
|
|
6753
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
6754
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6755
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
6756
|
+
renderer.renderTimeline(nextEvents);
|
|
6463
6757
|
}
|
|
6464
6758
|
}
|
|
6465
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
6759
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6466
6760
|
}
|
|
6467
6761
|
case "attach": {
|
|
6468
6762
|
let pending = rest;
|
|
@@ -6752,10 +7046,10 @@ async function executeServer(context, args, options) {
|
|
|
6752
7046
|
}
|
|
6753
7047
|
|
|
6754
7048
|
// packages/cli/src/commands/task.ts
|
|
6755
|
-
import { readFileSync as
|
|
6756
|
-
import { spawnSync as
|
|
6757
|
-
import { createInterface as
|
|
6758
|
-
import { resolve as
|
|
7049
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7050
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7051
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
7052
|
+
import { resolve as resolve20 } from "path";
|
|
6759
7053
|
import {
|
|
6760
7054
|
taskArtifactDir,
|
|
6761
7055
|
taskArtifacts,
|
|
@@ -6773,19 +7067,9 @@ import {
|
|
|
6773
7067
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6774
7068
|
|
|
6775
7069
|
// packages/cli/src/commands/_task-picker.ts
|
|
6776
|
-
|
|
6777
|
-
function taskId(task) {
|
|
7070
|
+
function taskId2(task) {
|
|
6778
7071
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6779
7072
|
}
|
|
6780
|
-
function taskTitle(task) {
|
|
6781
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6782
|
-
}
|
|
6783
|
-
function taskStatus(task) {
|
|
6784
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6785
|
-
}
|
|
6786
|
-
function renderTaskPickerRows(tasks) {
|
|
6787
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6788
|
-
}
|
|
6789
7073
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6790
7074
|
if (tasks.length === 0)
|
|
6791
7075
|
return null;
|
|
@@ -6795,17 +7079,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6795
7079
|
if (!isTty) {
|
|
6796
7080
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6797
7081
|
}
|
|
6798
|
-
const prompt = io.prompt ??
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
} finally {
|
|
6803
|
-
rl.close();
|
|
6804
|
-
}
|
|
6805
|
-
});
|
|
6806
|
-
console.log("Select Rig task:");
|
|
7082
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
7083
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
7084
|
+
`) };
|
|
7085
|
+
renderer.writeLine("Select Rig task:");
|
|
6807
7086
|
for (const row of renderTaskPickerRows(tasks))
|
|
6808
|
-
|
|
7087
|
+
renderer.writeLine(` ${row}`);
|
|
6809
7088
|
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6810
7089
|
if (!answer)
|
|
6811
7090
|
return null;
|
|
@@ -6813,7 +7092,7 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6813
7092
|
const index = Number.parseInt(answer, 10) - 1;
|
|
6814
7093
|
return tasks[index] ?? null;
|
|
6815
7094
|
}
|
|
6816
|
-
return tasks.find((task) =>
|
|
7095
|
+
return tasks.find((task) => taskId2(task) === answer) ?? null;
|
|
6817
7096
|
}
|
|
6818
7097
|
|
|
6819
7098
|
// packages/cli/src/commands/task.ts
|
|
@@ -6891,7 +7170,7 @@ function normalizePrMode(value) {
|
|
|
6891
7170
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6892
7171
|
}
|
|
6893
7172
|
function detectLocalDirtyState(projectRoot) {
|
|
6894
|
-
const result =
|
|
7173
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6895
7174
|
if (result.status !== 0)
|
|
6896
7175
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6897
7176
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6925,7 +7204,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6925
7204
|
if (explicit)
|
|
6926
7205
|
return { mode: explicit, state };
|
|
6927
7206
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6928
|
-
const rl =
|
|
7207
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6929
7208
|
try {
|
|
6930
7209
|
const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
|
|
6931
7210
|
return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
|
|
@@ -7010,12 +7289,12 @@ async function executeTask(context, args, options) {
|
|
|
7010
7289
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
7011
7290
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
7012
7291
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
7013
|
-
const
|
|
7014
|
-
if (!
|
|
7292
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7293
|
+
if (!taskId3)
|
|
7015
7294
|
throw new CliError2("task show requires a task id.", 2);
|
|
7016
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7295
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
7017
7296
|
if (!task)
|
|
7018
|
-
throw new CliError2(`Task not found: ${
|
|
7297
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
7019
7298
|
const summary = summarizeTask(task, { raw: true });
|
|
7020
7299
|
if (context.outputMode === "text")
|
|
7021
7300
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -7086,7 +7365,7 @@ async function executeTask(context, args, options) {
|
|
|
7086
7365
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
7087
7366
|
let content;
|
|
7088
7367
|
if (fileFlag.value) {
|
|
7089
|
-
content =
|
|
7368
|
+
content = readFileSync9(resolve20(context.projectRoot, fileFlag.value), "utf-8");
|
|
7090
7369
|
} else {
|
|
7091
7370
|
content = await readStdin();
|
|
7092
7371
|
}
|
|
@@ -7307,9 +7586,9 @@ async function executeTask(context, args, options) {
|
|
|
7307
7586
|
}
|
|
7308
7587
|
|
|
7309
7588
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7310
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7311
|
-
import { resolve as
|
|
7312
|
-
import { spawn as spawn2, spawnSync as
|
|
7589
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7590
|
+
import { resolve as resolve21 } from "path";
|
|
7591
|
+
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
7313
7592
|
import { createInterface as createLineInterface } from "readline";
|
|
7314
7593
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7315
7594
|
import {
|
|
@@ -7334,8 +7613,7 @@ import {
|
|
|
7334
7613
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7335
7614
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7336
7615
|
import {
|
|
7337
|
-
buildTaskRunLifecycleComment
|
|
7338
|
-
updateConfiguredTaskSourceTask
|
|
7616
|
+
buildTaskRunLifecycleComment
|
|
7339
7617
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7340
7618
|
import {
|
|
7341
7619
|
closeIssueAfterMergedPr,
|
|
@@ -7373,7 +7651,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7373
7651
|
};
|
|
7374
7652
|
}
|
|
7375
7653
|
function runGitSync(cwd, args, input) {
|
|
7376
|
-
const result =
|
|
7654
|
+
const result = spawnSync4("git", [...args], {
|
|
7377
7655
|
cwd,
|
|
7378
7656
|
input,
|
|
7379
7657
|
encoding: "utf8",
|
|
@@ -7391,12 +7669,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7391
7669
|
return 0;
|
|
7392
7670
|
let copied = 0;
|
|
7393
7671
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7394
|
-
const sourcePath =
|
|
7395
|
-
const targetPath =
|
|
7672
|
+
const sourcePath = resolve21(sourceRoot, relativePath);
|
|
7673
|
+
const targetPath = resolve21(targetRoot, relativePath);
|
|
7396
7674
|
try {
|
|
7397
7675
|
if (!statSync2(sourcePath).isFile())
|
|
7398
7676
|
continue;
|
|
7399
|
-
mkdirSync8(
|
|
7677
|
+
mkdirSync8(resolve21(targetPath, ".."), { recursive: true });
|
|
7400
7678
|
copyFileSync3(sourcePath, targetPath);
|
|
7401
7679
|
copied += 1;
|
|
7402
7680
|
} catch {}
|
|
@@ -7435,7 +7713,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7435
7713
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7436
7714
|
return {
|
|
7437
7715
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7438
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
7716
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve21(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7439
7717
|
};
|
|
7440
7718
|
}
|
|
7441
7719
|
function positiveInt(value, fallback) {
|
|
@@ -7546,9 +7824,9 @@ function createCommandRunner(binary) {
|
|
|
7546
7824
|
const stderrChunks = [];
|
|
7547
7825
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7548
7826
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7549
|
-
return await new Promise((
|
|
7550
|
-
child.once("error", (error) =>
|
|
7551
|
-
child.once("close", (code) =>
|
|
7827
|
+
return await new Promise((resolve22) => {
|
|
7828
|
+
child.once("error", (error) => resolve22({ exitCode: 1, stderr: error.message }));
|
|
7829
|
+
child.once("close", (code) => resolve22({
|
|
7552
7830
|
exitCode: code ?? 1,
|
|
7553
7831
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7554
7832
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7568,12 +7846,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7568
7846
|
return input.configuredBranch;
|
|
7569
7847
|
}
|
|
7570
7848
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7571
|
-
const
|
|
7572
|
-
if (!
|
|
7849
|
+
const taskId3 = input.taskId?.trim();
|
|
7850
|
+
if (!taskId3) {
|
|
7573
7851
|
return { status: "skipped" };
|
|
7574
7852
|
}
|
|
7575
|
-
const
|
|
7576
|
-
const
|
|
7853
|
+
const configInput = input.config ?? null;
|
|
7854
|
+
const config = configInput ?? {};
|
|
7855
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7577
7856
|
if (prMode === "off" || prMode === "ask") {
|
|
7578
7857
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7579
7858
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7589,7 +7868,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7589
7868
|
gitCommand
|
|
7590
7869
|
});
|
|
7591
7870
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7592
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
7871
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7593
7872
|
const stage = input.appendStage ?? (() => {
|
|
7594
7873
|
return;
|
|
7595
7874
|
});
|
|
@@ -7607,7 +7886,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7607
7886
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7608
7887
|
const commit = await commitRunChanges({
|
|
7609
7888
|
cwd: workspace,
|
|
7610
|
-
message: `rig: complete task ${
|
|
7889
|
+
message: `rig: complete task ${taskId3}`,
|
|
7611
7890
|
command: gitCommand
|
|
7612
7891
|
});
|
|
7613
7892
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7615,14 +7894,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7615
7894
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7616
7895
|
const pr = await prAutomation({
|
|
7617
7896
|
projectRoot: workspace,
|
|
7618
|
-
taskId:
|
|
7897
|
+
taskId: taskId3,
|
|
7619
7898
|
runId: input.runId,
|
|
7620
7899
|
branch,
|
|
7621
7900
|
config,
|
|
7622
7901
|
sourceTask: input.sourceTask,
|
|
7623
7902
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
7624
|
-
artifactRoot:
|
|
7903
|
+
artifactRoot: resolve21(input.projectRoot, "artifacts", taskId3),
|
|
7625
7904
|
command: ghCommand,
|
|
7905
|
+
gitCommand,
|
|
7626
7906
|
steerPi,
|
|
7627
7907
|
lifecycle: {
|
|
7628
7908
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7630,7 +7910,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7630
7910
|
try {
|
|
7631
7911
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7632
7912
|
await updateTaskSource(input.projectRoot, {
|
|
7633
|
-
taskId:
|
|
7913
|
+
taskId: taskId3,
|
|
7634
7914
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7635
7915
|
update: {
|
|
7636
7916
|
status: "under_review",
|
|
@@ -7660,7 +7940,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7660
7940
|
`), "reviewing", "error");
|
|
7661
7941
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7662
7942
|
await updateTaskSource(input.projectRoot, {
|
|
7663
|
-
taskId:
|
|
7943
|
+
taskId: taskId3,
|
|
7664
7944
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7665
7945
|
update: {
|
|
7666
7946
|
status: "ci_fixing",
|
|
@@ -7671,8 +7951,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7671
7951
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7672
7952
|
})
|
|
7673
7953
|
}
|
|
7674
|
-
}).catch(() => {
|
|
7675
|
-
return;
|
|
7676
7954
|
});
|
|
7677
7955
|
}
|
|
7678
7956
|
},
|
|
@@ -7680,7 +7958,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7680
7958
|
stage("Merge", prUrl, "running", "tool");
|
|
7681
7959
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7682
7960
|
await updateTaskSource(input.projectRoot, {
|
|
7683
|
-
taskId:
|
|
7961
|
+
taskId: taskId3,
|
|
7684
7962
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7685
7963
|
update: {
|
|
7686
7964
|
status: "merging",
|
|
@@ -7691,8 +7969,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7691
7969
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7692
7970
|
})
|
|
7693
7971
|
}
|
|
7694
|
-
}).catch(() => {
|
|
7695
|
-
return;
|
|
7696
7972
|
});
|
|
7697
7973
|
}
|
|
7698
7974
|
},
|
|
@@ -7709,7 +7985,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7709
7985
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7710
7986
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7711
7987
|
await updateTaskSource(input.projectRoot, {
|
|
7712
|
-
taskId:
|
|
7988
|
+
taskId: taskId3,
|
|
7713
7989
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7714
7990
|
update: {
|
|
7715
7991
|
status: "needs_attention",
|
|
@@ -7721,8 +7997,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7721
7997
|
errorText: detail
|
|
7722
7998
|
})
|
|
7723
7999
|
}
|
|
7724
|
-
}).catch(() => {
|
|
7725
|
-
|
|
8000
|
+
}).catch((error) => {
|
|
8001
|
+
try {
|
|
8002
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
8003
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
8004
|
+
title: "Task source needs-attention update failed",
|
|
8005
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
8006
|
+
tone: "error",
|
|
8007
|
+
status: "needs_attention",
|
|
8008
|
+
createdAt: new Date().toISOString()
|
|
8009
|
+
});
|
|
8010
|
+
} catch {}
|
|
7726
8011
|
});
|
|
7727
8012
|
}
|
|
7728
8013
|
return { status: "needs_attention", pr };
|
|
@@ -7730,7 +8015,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7730
8015
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7731
8016
|
await closeIssueAfterMergedPr({
|
|
7732
8017
|
projectRoot: input.projectRoot,
|
|
7733
|
-
taskId:
|
|
8018
|
+
taskId: taskId3,
|
|
7734
8019
|
runId: input.runId,
|
|
7735
8020
|
prUrl: pr.prUrl,
|
|
7736
8021
|
sourceTask: input.sourceTask,
|
|
@@ -7740,12 +8025,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7740
8025
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7741
8026
|
return { status: "completed", pr };
|
|
7742
8027
|
}
|
|
7743
|
-
function summarizeValidationFailure(projectRoot,
|
|
7744
|
-
if (!
|
|
8028
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
8029
|
+
if (!taskId3) {
|
|
7745
8030
|
return null;
|
|
7746
8031
|
}
|
|
7747
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7748
|
-
const summary = readJsonFile3(
|
|
8032
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
8033
|
+
const summary = readJsonFile3(resolve21(artifactDir, "validation-summary.json"), null);
|
|
7749
8034
|
if (!summary || summary.status !== "fail") {
|
|
7750
8035
|
continue;
|
|
7751
8036
|
}
|
|
@@ -7826,9 +8111,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7826
8111
|
if (!input.taskId || !input.workspaceDir) {
|
|
7827
8112
|
return { accepted: false, reason: null };
|
|
7828
8113
|
}
|
|
7829
|
-
const artifactDir =
|
|
7830
|
-
const reviewStatusPath =
|
|
7831
|
-
const taskResultPath =
|
|
8114
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8115
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8116
|
+
const taskResultPath = resolve21(artifactDir, "task-result.json");
|
|
7832
8117
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7833
8118
|
if (reviewStatus !== "APPROVED") {
|
|
7834
8119
|
return { accepted: false, reason: null };
|
|
@@ -7865,12 +8150,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7865
8150
|
if (!input.taskId || !input.workspaceDir) {
|
|
7866
8151
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7867
8152
|
}
|
|
7868
|
-
const artifactDir =
|
|
7869
|
-
const reviewStatePath =
|
|
7870
|
-
const reviewFeedbackPath =
|
|
7871
|
-
const reviewStatusPath =
|
|
7872
|
-
const failedApproachesPath =
|
|
7873
|
-
const validationSummaryPath =
|
|
8153
|
+
const artifactDir = resolve21(input.workspaceDir, "artifacts", input.taskId);
|
|
8154
|
+
const reviewStatePath = resolve21(artifactDir, "review-state.json");
|
|
8155
|
+
const reviewFeedbackPath = resolve21(artifactDir, "review-feedback.md");
|
|
8156
|
+
const reviewStatusPath = resolve21(artifactDir, "review-status.txt");
|
|
8157
|
+
const failedApproachesPath = resolve21(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
8158
|
+
const validationSummaryPath = resolve21(artifactDir, "validation-summary.json");
|
|
7874
8159
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7875
8160
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7876
8161
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7924,12 +8209,60 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7924
8209
|
}
|
|
7925
8210
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7926
8211
|
}
|
|
8212
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8213
|
+
let nextAssistantText = input.assistantText;
|
|
8214
|
+
if (input.record.type === "message_update") {
|
|
8215
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8216
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8217
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8218
|
+
}
|
|
8219
|
+
} else if (input.record.type === "stream_event") {
|
|
8220
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8221
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8222
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8223
|
+
nextAssistantText += delta.text;
|
|
8224
|
+
}
|
|
8225
|
+
} else if (input.record.type === "assistant") {
|
|
8226
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8227
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8228
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8229
|
+
if (fullText.length > nextAssistantText.length)
|
|
8230
|
+
nextAssistantText = fullText;
|
|
8231
|
+
}
|
|
8232
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8233
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8234
|
+
id: input.messageId,
|
|
8235
|
+
type: "assistant_message",
|
|
8236
|
+
text: nextAssistantText,
|
|
8237
|
+
state: "streaming",
|
|
8238
|
+
createdAt: new Date().toISOString()
|
|
8239
|
+
});
|
|
8240
|
+
}
|
|
8241
|
+
return nextAssistantText;
|
|
8242
|
+
}
|
|
8243
|
+
function appendToolTimelineFromLog(input) {
|
|
8244
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8245
|
+
if (title !== "Tool activity")
|
|
8246
|
+
return;
|
|
8247
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8248
|
+
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;
|
|
8249
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8250
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8251
|
+
id: `tool:${logId}`,
|
|
8252
|
+
type: "tool_execution_update",
|
|
8253
|
+
toolName,
|
|
8254
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8255
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8256
|
+
payload,
|
|
8257
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8258
|
+
});
|
|
8259
|
+
}
|
|
7927
8260
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7928
|
-
if (!
|
|
8261
|
+
if (!existsSync12(reviewStatusPath)) {
|
|
7929
8262
|
return null;
|
|
7930
8263
|
}
|
|
7931
8264
|
try {
|
|
7932
|
-
const status =
|
|
8265
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7933
8266
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7934
8267
|
} catch {
|
|
7935
8268
|
return null;
|
|
@@ -7947,14 +8280,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7947
8280
|
function runSourceTaskIdentity(sourceTask) {
|
|
7948
8281
|
return sourceTask;
|
|
7949
8282
|
}
|
|
7950
|
-
|
|
7951
|
-
if (!
|
|
8283
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8284
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8285
|
+
return null;
|
|
8286
|
+
const record = sourceTask;
|
|
8287
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8288
|
+
if (direct?.trim())
|
|
8289
|
+
return direct.trim();
|
|
8290
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8291
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8292
|
+
}
|
|
8293
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8294
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8295
|
+
id: input.taskId,
|
|
8296
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8297
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8298
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8299
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8300
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8301
|
+
});
|
|
8302
|
+
if (serverResult.ok === false) {
|
|
8303
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8304
|
+
}
|
|
8305
|
+
return {
|
|
8306
|
+
updated: serverResult.ok !== false,
|
|
8307
|
+
taskId: input.taskId,
|
|
8308
|
+
status: input.update.status,
|
|
8309
|
+
source: "server",
|
|
8310
|
+
sourceKind: "server",
|
|
8311
|
+
projectSync: serverResult.projectSync
|
|
8312
|
+
};
|
|
8313
|
+
};
|
|
8314
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8315
|
+
if (!taskId3)
|
|
7952
8316
|
return;
|
|
7953
8317
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7954
8318
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7955
8319
|
return;
|
|
7956
8320
|
const result = await updateTaskSource(projectRoot, {
|
|
7957
|
-
taskId:
|
|
8321
|
+
taskId: taskId3,
|
|
7958
8322
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7959
8323
|
update: {
|
|
7960
8324
|
status,
|
|
@@ -7973,14 +8337,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7973
8337
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7974
8338
|
}
|
|
7975
8339
|
}
|
|
7976
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8340
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7977
8341
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7978
8342
|
const raw = run?.sourceTask;
|
|
7979
8343
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7980
8344
|
return null;
|
|
7981
8345
|
}
|
|
7982
8346
|
const record = raw;
|
|
7983
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8347
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7984
8348
|
if (!id) {
|
|
7985
8349
|
return null;
|
|
7986
8350
|
}
|
|
@@ -8116,15 +8480,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
8116
8480
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
8117
8481
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
8118
8482
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
8119
|
-
const planningArtifactPath =
|
|
8483
|
+
const planningArtifactPath = resolve21("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
8120
8484
|
const persistedPlanning = {
|
|
8121
8485
|
...planningClassification,
|
|
8122
8486
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
8123
8487
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
8124
8488
|
classifiedAt: new Date().toISOString()
|
|
8125
8489
|
};
|
|
8126
|
-
mkdirSync8(
|
|
8127
|
-
writeFileSync6(
|
|
8490
|
+
mkdirSync8(resolve21(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8491
|
+
writeFileSync6(resolve21(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
8128
8492
|
`, "utf8");
|
|
8129
8493
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
8130
8494
|
prompt = `${prompt}
|
|
@@ -8174,7 +8538,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8174
8538
|
let verificationStarted = false;
|
|
8175
8539
|
let reviewStarted = false;
|
|
8176
8540
|
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
8177
|
-
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ?
|
|
8541
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
|
|
8178
8542
|
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
8179
8543
|
let latestProviderCommand = null;
|
|
8180
8544
|
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
@@ -8257,10 +8621,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8257
8621
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8258
8622
|
status: "running",
|
|
8259
8623
|
worktreePath: latestRuntimeWorkspace,
|
|
8260
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8624
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve21(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
8261
8625
|
logRoot: latestLogsDir,
|
|
8262
|
-
sessionPath: latestSessionDir ?
|
|
8263
|
-
sessionLogPath: latestLogsDir ?
|
|
8626
|
+
sessionPath: latestSessionDir ? resolve21(latestSessionDir, "session.json") : null,
|
|
8627
|
+
sessionLogPath: latestLogsDir ? resolve21(latestLogsDir, "agent-stdout.log") : null,
|
|
8264
8628
|
branch: runtimeId
|
|
8265
8629
|
});
|
|
8266
8630
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -8268,7 +8632,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8268
8632
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
8269
8633
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
8270
8634
|
if (readyFile) {
|
|
8271
|
-
mkdirSync8(
|
|
8635
|
+
mkdirSync8(resolve21(readyFile, ".."), { recursive: true });
|
|
8272
8636
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
8273
8637
|
`, "utf8");
|
|
8274
8638
|
}
|
|
@@ -8417,7 +8781,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8417
8781
|
if (providerLogs.length > 0) {
|
|
8418
8782
|
for (const providerLog of providerLogs) {
|
|
8419
8783
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
8784
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8420
8785
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
8786
|
+
if (providerLog.title === "Tool activity")
|
|
8787
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8421
8788
|
}
|
|
8422
8789
|
}
|
|
8423
8790
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8575,7 +8942,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8575
8942
|
let acceptedArtifactObservedAt = null;
|
|
8576
8943
|
let acceptedArtifactPollTimer = null;
|
|
8577
8944
|
let acceptedArtifactKillTimer = null;
|
|
8578
|
-
const attemptExit = await new Promise((
|
|
8945
|
+
const attemptExit = await new Promise((resolve22) => {
|
|
8579
8946
|
let settled = false;
|
|
8580
8947
|
const settle = (result) => {
|
|
8581
8948
|
if (settled)
|
|
@@ -8583,7 +8950,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8583
8950
|
settled = true;
|
|
8584
8951
|
if (acceptedArtifactPollTimer)
|
|
8585
8952
|
clearInterval(acceptedArtifactPollTimer);
|
|
8586
|
-
|
|
8953
|
+
resolve22(result);
|
|
8587
8954
|
};
|
|
8588
8955
|
const pollAcceptedArtifacts = () => {
|
|
8589
8956
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8656,7 +9023,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8656
9023
|
});
|
|
8657
9024
|
for (const pendingLog of pendingLogs) {
|
|
8658
9025
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9026
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8659
9027
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9028
|
+
if (pendingLog.title === "Tool activity")
|
|
9029
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8660
9030
|
}
|
|
8661
9031
|
process.off("SIGTERM", forwardSigterm);
|
|
8662
9032
|
if (attemptExit.error) {
|
|
@@ -8782,8 +9152,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8782
9152
|
}
|
|
8783
9153
|
if (planningClassification.planningRequired) {
|
|
8784
9154
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8785
|
-
const expectedPlanPath =
|
|
8786
|
-
if (!
|
|
9155
|
+
const expectedPlanPath = resolve21(planWorkspace, planningArtifactPath);
|
|
9156
|
+
if (!existsSync12(expectedPlanPath)) {
|
|
8787
9157
|
const failedAt = new Date().toISOString();
|
|
8788
9158
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8789
9159
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8803,6 +9173,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8803
9173
|
throw new CliError2(failureDetail, 1);
|
|
8804
9174
|
}
|
|
8805
9175
|
}
|
|
9176
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
9177
|
+
appendPiStageLog({
|
|
9178
|
+
projectRoot: context.projectRoot,
|
|
9179
|
+
runId: input.runId,
|
|
9180
|
+
stage: "Validate",
|
|
9181
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
9182
|
+
status: "completed"
|
|
9183
|
+
});
|
|
9184
|
+
if (verificationAction && !reviewStarted) {
|
|
9185
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
9186
|
+
}
|
|
9187
|
+
if (!reviewAction) {
|
|
9188
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
9189
|
+
}
|
|
9190
|
+
if (reviewAction) {
|
|
9191
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
9192
|
+
}
|
|
9193
|
+
const requestedAt = new Date().toISOString();
|
|
9194
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
9195
|
+
status: "reviewing",
|
|
9196
|
+
completedAt: null,
|
|
9197
|
+
errorText: null,
|
|
9198
|
+
serverCloseout: {
|
|
9199
|
+
status: "pending",
|
|
9200
|
+
phase: "queued",
|
|
9201
|
+
requestedAt,
|
|
9202
|
+
updatedAt: requestedAt,
|
|
9203
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
9204
|
+
branch: latestRuntimeBranch,
|
|
9205
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
9206
|
+
}
|
|
9207
|
+
});
|
|
9208
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9209
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9210
|
+
title: "Server-owned closeout requested",
|
|
9211
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9212
|
+
tone: "info",
|
|
9213
|
+
status: "reviewing",
|
|
9214
|
+
createdAt: requestedAt,
|
|
9215
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9216
|
+
});
|
|
9217
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9218
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9219
|
+
await context.emitEvent("command.finished", {
|
|
9220
|
+
command: [
|
|
9221
|
+
"rig",
|
|
9222
|
+
"server",
|
|
9223
|
+
"task-run",
|
|
9224
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9225
|
+
...input.title ? ["--title", input.title] : []
|
|
9226
|
+
],
|
|
9227
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9228
|
+
exitCode: 0,
|
|
9229
|
+
durationMs: 0,
|
|
9230
|
+
startedAt,
|
|
9231
|
+
finishedAt: requestedAt
|
|
9232
|
+
});
|
|
9233
|
+
return;
|
|
9234
|
+
}
|
|
8806
9235
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8807
9236
|
appendPiStageLog({
|
|
8808
9237
|
projectRoot: context.projectRoot,
|
|
@@ -8830,11 +9259,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8830
9259
|
child.stdin.write(message2);
|
|
8831
9260
|
}
|
|
8832
9261
|
child.stdin.end();
|
|
9262
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9263
|
+
let feedbackAssistantText = "";
|
|
9264
|
+
const feedbackPendingToolUses = new Map;
|
|
8833
9265
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8834
9266
|
stdout.on("line", (line) => {
|
|
8835
9267
|
const trimmed = line.trim();
|
|
8836
9268
|
if (!trimmed)
|
|
8837
9269
|
return;
|
|
9270
|
+
try {
|
|
9271
|
+
const record = JSON.parse(trimmed);
|
|
9272
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9273
|
+
runId: input.runId,
|
|
9274
|
+
record,
|
|
9275
|
+
createdAtFallback: new Date().toISOString(),
|
|
9276
|
+
status: "reviewing",
|
|
9277
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9278
|
+
});
|
|
9279
|
+
for (const providerLog of providerLogs) {
|
|
9280
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9281
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9282
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9283
|
+
if (providerLog.title === "Tool activity")
|
|
9284
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9285
|
+
}
|
|
9286
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9287
|
+
projectRoot: context.projectRoot,
|
|
9288
|
+
runId: input.runId,
|
|
9289
|
+
messageId: feedbackAssistantMessageId,
|
|
9290
|
+
record,
|
|
9291
|
+
assistantText: feedbackAssistantText
|
|
9292
|
+
});
|
|
9293
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9294
|
+
if (hadAssistantDelta) {
|
|
9295
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9296
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9297
|
+
}
|
|
9298
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9299
|
+
return;
|
|
9300
|
+
} catch {}
|
|
8838
9301
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8839
9302
|
id: nextRunLogId(),
|
|
8840
9303
|
title: "Pi PR feedback fix output",
|
|
@@ -8860,10 +9323,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8860
9323
|
});
|
|
8861
9324
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8862
9325
|
});
|
|
8863
|
-
const exitCode = await new Promise((
|
|
8864
|
-
child.once("error", () =>
|
|
8865
|
-
child.once("close", (code) =>
|
|
9326
|
+
const exitCode = await new Promise((resolve22) => {
|
|
9327
|
+
child.once("error", () => resolve22(1));
|
|
9328
|
+
child.once("close", (code) => resolve22(code ?? 1));
|
|
8866
9329
|
});
|
|
9330
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9331
|
+
runId: input.runId,
|
|
9332
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9333
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9334
|
+
})) {
|
|
9335
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9336
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9337
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9338
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9339
|
+
}
|
|
9340
|
+
if (feedbackAssistantText.trim()) {
|
|
9341
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9342
|
+
id: feedbackAssistantMessageId,
|
|
9343
|
+
type: "assistant_message",
|
|
9344
|
+
text: feedbackAssistantText,
|
|
9345
|
+
state: "completed",
|
|
9346
|
+
createdAt: new Date().toISOString(),
|
|
9347
|
+
completedAt: new Date().toISOString()
|
|
9348
|
+
});
|
|
9349
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9350
|
+
}
|
|
8867
9351
|
if (exitCode !== 0) {
|
|
8868
9352
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8869
9353
|
}
|
|
@@ -8981,8 +9465,8 @@ async function executeTest(context, args) {
|
|
|
8981
9465
|
}
|
|
8982
9466
|
|
|
8983
9467
|
// packages/cli/src/commands/setup.ts
|
|
8984
|
-
import { existsSync as
|
|
8985
|
-
import { resolve as
|
|
9468
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9469
|
+
import { resolve as resolve22 } from "path";
|
|
8986
9470
|
import { createPluginHost } from "@rig/core";
|
|
8987
9471
|
import {
|
|
8988
9472
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -9045,8 +9529,8 @@ function runSetupInit(projectRoot) {
|
|
|
9045
9529
|
mkdirSync9(stateDir, { recursive: true });
|
|
9046
9530
|
mkdirSync9(logsDir, { recursive: true });
|
|
9047
9531
|
mkdirSync9(artifactsDir, { recursive: true });
|
|
9048
|
-
const failuresPath =
|
|
9049
|
-
if (!
|
|
9532
|
+
const failuresPath = resolve22(stateDir, "failed_approaches.md");
|
|
9533
|
+
if (!existsSync13(failuresPath)) {
|
|
9050
9534
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
9051
9535
|
|
|
9052
9536
|
`, "utf-8");
|
|
@@ -9064,18 +9548,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
9064
9548
|
}
|
|
9065
9549
|
async function runSetupPreflight(projectRoot) {
|
|
9066
9550
|
await runSetupCheck(projectRoot);
|
|
9067
|
-
const validationRoot =
|
|
9068
|
-
if (
|
|
9551
|
+
const validationRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9552
|
+
if (existsSync13(validationRoot)) {
|
|
9069
9553
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
9070
9554
|
for (const validator of validators) {
|
|
9071
|
-
const script =
|
|
9072
|
-
if (
|
|
9555
|
+
const script = resolve22(validationRoot, validator.name, "validate.sh");
|
|
9556
|
+
if (existsSync13(script)) {
|
|
9073
9557
|
console.log(`OK: validator script ${script}`);
|
|
9074
9558
|
}
|
|
9075
9559
|
}
|
|
9076
9560
|
}
|
|
9077
|
-
const hooksRoot =
|
|
9078
|
-
if (
|
|
9561
|
+
const hooksRoot = resolve22(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9562
|
+
if (existsSync13(hooksRoot)) {
|
|
9079
9563
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
9080
9564
|
for (const hook of hooks) {
|
|
9081
9565
|
console.log(`OK: hook ${hook}`);
|
|
@@ -9452,8 +9936,8 @@ async function executeGroup(context, group, args) {
|
|
|
9452
9936
|
}
|
|
9453
9937
|
}
|
|
9454
9938
|
// packages/cli/src/launcher.ts
|
|
9455
|
-
import { existsSync as
|
|
9456
|
-
import { basename as basename2, resolve as
|
|
9939
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9940
|
+
import { basename as basename2, resolve as resolve23 } from "path";
|
|
9457
9941
|
import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
|
|
9458
9942
|
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
|
|
9459
9943
|
function parsePolicyMode(value) {
|
|
@@ -9466,7 +9950,7 @@ function parsePolicyMode(value) {
|
|
|
9466
9950
|
throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
|
|
9467
9951
|
}
|
|
9468
9952
|
function hasRigProjectMarker(candidate) {
|
|
9469
|
-
return
|
|
9953
|
+
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"));
|
|
9470
9954
|
}
|
|
9471
9955
|
function resolveProjectRoot({
|
|
9472
9956
|
envProjectRoot,
|
|
@@ -9475,19 +9959,19 @@ function resolveProjectRoot({
|
|
|
9475
9959
|
cwd = process.cwd()
|
|
9476
9960
|
}) {
|
|
9477
9961
|
if (envProjectRoot) {
|
|
9478
|
-
return
|
|
9962
|
+
return resolve23(cwd, envProjectRoot);
|
|
9479
9963
|
}
|
|
9480
9964
|
const fallbackImportDir = importDir ?? cwd;
|
|
9481
9965
|
const execName = basename2(execPath).toLowerCase();
|
|
9482
|
-
const execCandidates = execName === "rig" || execName === "rig.exe" ? [
|
|
9483
|
-
const candidates = [cwd, ...execCandidates,
|
|
9966
|
+
const execCandidates = execName === "rig" || execName === "rig.exe" ? [resolve23(execPath, "..", "..")] : [];
|
|
9967
|
+
const candidates = [cwd, ...execCandidates, resolve23(fallbackImportDir, "..")];
|
|
9484
9968
|
for (const candidate of candidates) {
|
|
9485
9969
|
const nearest = resolveNearestRigProjectRoot(candidate);
|
|
9486
9970
|
if (hasRigProjectMarker(nearest)) {
|
|
9487
9971
|
return nearest;
|
|
9488
9972
|
}
|
|
9489
9973
|
}
|
|
9490
|
-
return
|
|
9974
|
+
return resolve23(cwd);
|
|
9491
9975
|
}
|
|
9492
9976
|
function normalizeCliErrorCode(message2, isCliError) {
|
|
9493
9977
|
if (message2.startsWith("Invalid --policy-mode value:")) {
|
|
@@ -9554,7 +10038,7 @@ async function runRigCli(module, options = {}) {
|
|
|
9554
10038
|
runId: context.runId,
|
|
9555
10039
|
outcome,
|
|
9556
10040
|
eventsFile: context.eventBus.getEventsFile(),
|
|
9557
|
-
policyFile:
|
|
10041
|
+
policyFile: resolve23(projectRoot, "rig", "policy", "policy.json"),
|
|
9558
10042
|
policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
|
|
9559
10043
|
}, null, 2));
|
|
9560
10044
|
}
|