@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +710 -226
- package/dist/src/commands/_doctor-checks.js +7 -20
- package/dist/src/commands/_operator-surface.js +157 -0
- package/dist/src/commands/_operator-view.js +160 -51
- package/dist/src/commands/_preflight.js +30 -26
- package/dist/src/commands/_server-client.js +46 -22
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +21 -13
- package/dist/src/commands/agent.js +1 -0
- package/dist/src/commands/doctor.js +7 -20
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +183 -44
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +172 -76
- package/dist/src/commands/server.js +7 -20
- package/dist/src/commands/setup.js +7 -20
- package/dist/src/commands/task-run-driver.js +446 -53
- package/dist/src/commands/task.js +231 -98
- package/dist/src/commands.js +702 -218
- package/dist/src/index.js +710 -226
- package/package.json +5 -5
package/dist/src/commands.js
CHANGED
|
@@ -2464,17 +2464,17 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
2464
2464
|
}
|
|
2465
2465
|
|
|
2466
2466
|
// packages/cli/src/commands/_server-client.ts
|
|
2467
|
-
import { spawnSync } from "child_process";
|
|
2468
2467
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2469
2468
|
import { resolve as resolve8 } from "path";
|
|
2470
2469
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
2471
|
-
var
|
|
2470
|
+
var scopedGitHubBearerTokens = new Map;
|
|
2472
2471
|
function cleanToken(value) {
|
|
2473
2472
|
const trimmed = value?.trim();
|
|
2474
2473
|
return trimmed ? trimmed : null;
|
|
2475
2474
|
}
|
|
2476
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
2477
|
-
|
|
2475
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
2476
|
+
const scopedKey = resolve8(projectRoot ?? process.cwd());
|
|
2477
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
2478
2478
|
}
|
|
2479
2479
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
2480
2480
|
const path = resolve8(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -2488,25 +2488,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
2488
2488
|
}
|
|
2489
2489
|
}
|
|
2490
2490
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
2491
|
-
|
|
2492
|
-
|
|
2491
|
+
const scopedKey = resolve8(projectRoot);
|
|
2492
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
2493
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
2493
2494
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
2494
|
-
if (privateSession)
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
}
|
|
2498
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
2499
|
-
if (envToken) {
|
|
2500
|
-
cachedGitHubBearerToken = envToken;
|
|
2501
|
-
return cachedGitHubBearerToken;
|
|
2502
|
-
}
|
|
2503
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
2504
|
-
encoding: "utf8",
|
|
2505
|
-
timeout: 5000,
|
|
2506
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2507
|
-
});
|
|
2508
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
2509
|
-
return cachedGitHubBearerToken;
|
|
2495
|
+
if (privateSession)
|
|
2496
|
+
return privateSession;
|
|
2497
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
2510
2498
|
}
|
|
2511
2499
|
async function ensureServerForCli(projectRoot) {
|
|
2512
2500
|
try {
|
|
@@ -2704,6 +2692,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
2704
2692
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2705
2693
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2706
2694
|
}
|
|
2695
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
2696
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
2697
|
+
if (options.limit !== undefined)
|
|
2698
|
+
url.searchParams.set("limit", String(options.limit));
|
|
2699
|
+
if (options.cursor)
|
|
2700
|
+
url.searchParams.set("cursor", options.cursor);
|
|
2701
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2702
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
2703
|
+
}
|
|
2704
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
2705
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
2706
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2707
|
+
}
|
|
2708
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
2709
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
2710
|
+
url.searchParams.set("owner", owner);
|
|
2711
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
2712
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
2713
|
+
}
|
|
2714
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
2715
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
2716
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2717
|
+
}
|
|
2718
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
2719
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
2720
|
+
method: "POST",
|
|
2721
|
+
headers: { "content-type": "application/json" },
|
|
2722
|
+
body: JSON.stringify(input)
|
|
2723
|
+
});
|
|
2724
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
2725
|
+
}
|
|
2707
2726
|
async function stopRunViaServer(context, runId) {
|
|
2708
2727
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
2709
2728
|
method: "POST",
|
|
@@ -2983,6 +3002,9 @@ function permissionAllowsPr(payload) {
|
|
|
2983
3002
|
}
|
|
2984
3003
|
return null;
|
|
2985
3004
|
}
|
|
3005
|
+
function isNotFoundError(error) {
|
|
3006
|
+
return /\b(404|not found)\b/i.test(message(error));
|
|
3007
|
+
}
|
|
2986
3008
|
function projectCheckoutReady(payload) {
|
|
2987
3009
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
2988
3010
|
return null;
|
|
@@ -3015,19 +3037,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3015
3037
|
const checks = [];
|
|
3016
3038
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
|
|
3017
3039
|
const taskId = options.taskId?.trim() || null;
|
|
3040
|
+
const requiresCurrentRunApi = Boolean(taskId);
|
|
3041
|
+
const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
|
|
3042
|
+
const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
|
|
3043
|
+
let legacyServerCompatibility = false;
|
|
3018
3044
|
try {
|
|
3019
3045
|
await request("/api/server/status");
|
|
3020
3046
|
checks.push(preflightCheck("server", "Rig server reachable", "pass"));
|
|
3021
3047
|
} catch (error) {
|
|
3022
|
-
|
|
3048
|
+
if (isNotFoundError(error)) {
|
|
3049
|
+
try {
|
|
3050
|
+
await request("/health");
|
|
3051
|
+
legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
|
|
3052
|
+
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"));
|
|
3053
|
+
} catch (healthError) {
|
|
3054
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
|
|
3055
|
+
}
|
|
3056
|
+
} else {
|
|
3057
|
+
checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
|
|
3058
|
+
}
|
|
3023
3059
|
}
|
|
3024
3060
|
const repo = readRepoConnection(context.projectRoot);
|
|
3025
|
-
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>`."));
|
|
3061
|
+
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>`."));
|
|
3026
3062
|
try {
|
|
3027
3063
|
const auth = await request("/api/github/auth/status");
|
|
3028
|
-
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>`."));
|
|
3064
|
+
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>`."));
|
|
3029
3065
|
} catch (error) {
|
|
3030
|
-
checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3066
|
+
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
3031
3067
|
}
|
|
3032
3068
|
try {
|
|
3033
3069
|
const projection = await request("/api/workspace/task-projection");
|
|
@@ -3055,9 +3091,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
3055
3091
|
try {
|
|
3056
3092
|
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
3057
3093
|
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
3058
|
-
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."));
|
|
3094
|
+
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."));
|
|
3059
3095
|
} catch (error) {
|
|
3060
|
-
checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
|
|
3096
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
3061
3097
|
}
|
|
3062
3098
|
try {
|
|
3063
3099
|
const runs = await request("/api/runs?limit=200");
|
|
@@ -3969,9 +4005,10 @@ async function executeInbox(context, args) {
|
|
|
3969
4005
|
|
|
3970
4006
|
// packages/cli/src/commands/init.ts
|
|
3971
4007
|
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
3972
|
-
import { spawnSync
|
|
4008
|
+
import { spawnSync } from "child_process";
|
|
3973
4009
|
import { resolve as resolve16 } from "path";
|
|
3974
4010
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
4011
|
+
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
3975
4012
|
|
|
3976
4013
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
3977
4014
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -4295,7 +4332,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
|
|
|
4295
4332
|
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
4296
4333
|
}
|
|
4297
4334
|
function detectOriginRepoSlug(projectRoot) {
|
|
4298
|
-
const result =
|
|
4335
|
+
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
4299
4336
|
if (result.status !== 0)
|
|
4300
4337
|
return null;
|
|
4301
4338
|
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
@@ -4351,11 +4388,14 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
4351
4388
|
return source;
|
|
4352
4389
|
const projectId = JSON.stringify(options.githubProject);
|
|
4353
4390
|
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
4391
|
+
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
4392
|
+
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
4393
|
+
`)},` : "";
|
|
4354
4394
|
return source.replace(` projects: { enabled: false },`, [
|
|
4355
4395
|
` projects: {`,
|
|
4356
4396
|
` enabled: true,`,
|
|
4357
4397
|
` projectId: ${projectId},`,
|
|
4358
|
-
` statusFieldId: ${statusFieldId}
|
|
4398
|
+
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
4359
4399
|
` },`
|
|
4360
4400
|
].join(`
|
|
4361
4401
|
`));
|
|
@@ -4376,11 +4416,11 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
4376
4416
|
}
|
|
4377
4417
|
}
|
|
4378
4418
|
function detectGhLogin() {
|
|
4379
|
-
const result =
|
|
4419
|
+
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
4380
4420
|
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
4381
4421
|
}
|
|
4382
4422
|
function readGhAuthToken() {
|
|
4383
|
-
const result =
|
|
4423
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4384
4424
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4385
4425
|
throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
|
|
4386
4426
|
}
|
|
@@ -4389,8 +4429,15 @@ function readGhAuthToken() {
|
|
|
4389
4429
|
async function loadClackPrompts() {
|
|
4390
4430
|
return await import("@clack/prompts");
|
|
4391
4431
|
}
|
|
4432
|
+
function clackTextOptions(options) {
|
|
4433
|
+
return {
|
|
4434
|
+
message: options.message,
|
|
4435
|
+
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
4436
|
+
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4392
4439
|
async function promptRequiredText(prompts, options) {
|
|
4393
|
-
const value = await prompts.text(options);
|
|
4440
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4394
4441
|
if (prompts.isCancel(value))
|
|
4395
4442
|
throw new CliError2("Init cancelled.", 1);
|
|
4396
4443
|
const text2 = String(value ?? "").trim();
|
|
@@ -4399,7 +4446,7 @@ async function promptRequiredText(prompts, options) {
|
|
|
4399
4446
|
return text2;
|
|
4400
4447
|
}
|
|
4401
4448
|
async function promptOptionalText(prompts, options) {
|
|
4402
|
-
const value = await prompts.text(options);
|
|
4449
|
+
const value = await prompts.text(clackTextOptions(options));
|
|
4403
4450
|
if (prompts.isCancel(value))
|
|
4404
4451
|
throw new CliError2("Init cancelled.", 1);
|
|
4405
4452
|
return String(value ?? "").trim();
|
|
@@ -4410,6 +4457,132 @@ async function promptSelect(prompts, options) {
|
|
|
4410
4457
|
throw new CliError2("Init cancelled.", 1);
|
|
4411
4458
|
return String(value);
|
|
4412
4459
|
}
|
|
4460
|
+
function repoOwnerFromSlug(repoSlug) {
|
|
4461
|
+
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
4462
|
+
}
|
|
4463
|
+
function recordArray(value, key) {
|
|
4464
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4465
|
+
return [];
|
|
4466
|
+
const raw = value[key];
|
|
4467
|
+
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4468
|
+
}
|
|
4469
|
+
async function listGitHubProjectsForInit(context, owner, token) {
|
|
4470
|
+
if (token?.trim()) {
|
|
4471
|
+
try {
|
|
4472
|
+
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
4473
|
+
} catch (directError) {
|
|
4474
|
+
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
4475
|
+
if (recordArray(serverPayload, "projects").length > 0)
|
|
4476
|
+
return serverPayload;
|
|
4477
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
return listGitHubProjectsViaServer(context, owner);
|
|
4481
|
+
}
|
|
4482
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
4483
|
+
if (token?.trim()) {
|
|
4484
|
+
try {
|
|
4485
|
+
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
4486
|
+
} catch (directError) {
|
|
4487
|
+
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
4488
|
+
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
4489
|
+
return serverPayload;
|
|
4490
|
+
}
|
|
4491
|
+
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
4495
|
+
}
|
|
4496
|
+
var PROJECT_STATUS_PROMPTS = {
|
|
4497
|
+
running: "Running/In progress",
|
|
4498
|
+
prOpen: "PR open/review",
|
|
4499
|
+
ciFixing: "CI/review fixing",
|
|
4500
|
+
merging: "Merging",
|
|
4501
|
+
done: "Done",
|
|
4502
|
+
needsAttention: "Needs attention"
|
|
4503
|
+
};
|
|
4504
|
+
var DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
4505
|
+
running: "In Progress",
|
|
4506
|
+
prOpen: "In Review",
|
|
4507
|
+
ciFixing: "In Review",
|
|
4508
|
+
merging: "Merging",
|
|
4509
|
+
done: "Done",
|
|
4510
|
+
needsAttention: "Needs Attention"
|
|
4511
|
+
};
|
|
4512
|
+
async function promptManualProjectStatusMapping(prompts) {
|
|
4513
|
+
const statuses = {};
|
|
4514
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4515
|
+
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
4516
|
+
const value = await promptOptionalText(prompts, {
|
|
4517
|
+
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
4518
|
+
placeholder: defaultLabel
|
|
4519
|
+
});
|
|
4520
|
+
statuses[key] = value || defaultLabel;
|
|
4521
|
+
}
|
|
4522
|
+
return statuses;
|
|
4523
|
+
}
|
|
4524
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken) {
|
|
4525
|
+
const projectChoice = await promptSelect(prompts, {
|
|
4526
|
+
message: "GitHub Projects status sync",
|
|
4527
|
+
options: [
|
|
4528
|
+
{ value: "off", label: "Off" },
|
|
4529
|
+
{ value: "select", label: "Select accessible ProjectV2" },
|
|
4530
|
+
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
4531
|
+
]
|
|
4532
|
+
});
|
|
4533
|
+
if (projectChoice === "off")
|
|
4534
|
+
return { githubProject: "off" };
|
|
4535
|
+
if (projectChoice === "manual") {
|
|
4536
|
+
return {
|
|
4537
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4538
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4539
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
const owner = repoOwnerFromSlug(repoSlug);
|
|
4543
|
+
if (!owner)
|
|
4544
|
+
throw new CliError2(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1);
|
|
4545
|
+
const projectsPayload = await listGitHubProjectsForInit(context, owner, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
4546
|
+
const projects = recordArray(projectsPayload, "projects");
|
|
4547
|
+
if (projects.length === 0) {
|
|
4548
|
+
const error = typeof projectsPayload.error === "string" ? ` (${projectsPayload.error})` : "";
|
|
4549
|
+
prompts.outro?.(`No accessible GitHub Projects were returned${error}; falling back to manual ProjectV2 ids.`);
|
|
4550
|
+
return {
|
|
4551
|
+
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
4552
|
+
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
4553
|
+
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
4554
|
+
};
|
|
4555
|
+
}
|
|
4556
|
+
const selectedProjectId = await promptSelect(prompts, {
|
|
4557
|
+
message: "GitHub ProjectV2 project",
|
|
4558
|
+
options: [
|
|
4559
|
+
...projects.map((project) => ({
|
|
4560
|
+
value: String(project.id),
|
|
4561
|
+
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
4562
|
+
hint: typeof project.url === "string" ? project.url : undefined
|
|
4563
|
+
})),
|
|
4564
|
+
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
4565
|
+
]
|
|
4566
|
+
});
|
|
4567
|
+
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
4568
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, githubToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
4569
|
+
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
4570
|
+
const rawField = fieldPayloadRecord.field;
|
|
4571
|
+
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
4572
|
+
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
4573
|
+
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
4574
|
+
if (options.length === 0) {
|
|
4575
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: await promptManualProjectStatusMapping(prompts) };
|
|
4576
|
+
}
|
|
4577
|
+
const statuses = {};
|
|
4578
|
+
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
4579
|
+
statuses[key] = await promptSelect(prompts, {
|
|
4580
|
+
message: `Project status option for ${label}`,
|
|
4581
|
+
options: options.map((option) => ({ value: String(option.id ?? option.name), label: String(option.name ?? option.id) }))
|
|
4582
|
+
});
|
|
4583
|
+
}
|
|
4584
|
+
return { githubProject: projectId, githubProjectStatusField: fieldId, githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined };
|
|
4585
|
+
}
|
|
4413
4586
|
function sleep2(ms) {
|
|
4414
4587
|
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
4415
4588
|
}
|
|
@@ -4538,7 +4711,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4538
4711
|
if (token) {
|
|
4539
4712
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
4540
4713
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4541
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4714
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4542
4715
|
if (serverKind === "remote") {
|
|
4543
4716
|
writeRemoteGitHubAuthState(projectRoot, {
|
|
4544
4717
|
source: authMethod === "gh" ? "gh" : "init-token",
|
|
@@ -4563,7 +4736,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4563
4736
|
if (completed) {
|
|
4564
4737
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
4565
4738
|
if (apiSessionToken) {
|
|
4566
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
4739
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
4567
4740
|
if (serverKind === "remote") {
|
|
4568
4741
|
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
4569
4742
|
}
|
|
@@ -4587,7 +4760,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4587
4760
|
if (serverKind === "remote" && checkoutPath && token) {
|
|
4588
4761
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
4589
4762
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
4590
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
4763
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
4591
4764
|
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
4592
4765
|
}
|
|
4593
4766
|
const registered = await registerProjectViaServer(context, {
|
|
@@ -4596,6 +4769,12 @@ async function runControlPlaneInit(context, options) {
|
|
|
4596
4769
|
});
|
|
4597
4770
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
4598
4771
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
4772
|
+
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
4773
|
+
ok: false,
|
|
4774
|
+
ready: false,
|
|
4775
|
+
labelsReady: false,
|
|
4776
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4777
|
+
}));
|
|
4599
4778
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
4600
4779
|
remote: true,
|
|
4601
4780
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
@@ -4626,6 +4805,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
4626
4805
|
githubAuth,
|
|
4627
4806
|
deviceAuth,
|
|
4628
4807
|
githubAuthWarning: remoteGhTokenWarning,
|
|
4808
|
+
labelSetup,
|
|
4629
4809
|
pi,
|
|
4630
4810
|
doctor
|
|
4631
4811
|
};
|
|
@@ -4780,24 +4960,17 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
4780
4960
|
throw new CliError2("Remote gh-token import cancelled.", 1);
|
|
4781
4961
|
}
|
|
4782
4962
|
}
|
|
4783
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
|
|
4784
|
-
const
|
|
4785
|
-
message: "GitHub Projects status sync",
|
|
4786
|
-
options: [
|
|
4787
|
-
{ value: "off", label: "Off" },
|
|
4788
|
-
{ value: "configure", label: "Configure ProjectV2 status field" }
|
|
4789
|
-
]
|
|
4790
|
-
});
|
|
4791
|
-
const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
|
|
4792
|
-
const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
|
|
4963
|
+
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
4964
|
+
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken);
|
|
4793
4965
|
const result = await runControlPlaneInit(context, {
|
|
4794
4966
|
server: serverChoice,
|
|
4795
4967
|
remoteUrl,
|
|
4796
4968
|
repoSlug,
|
|
4797
4969
|
githubToken,
|
|
4798
4970
|
githubAuthMethod: authMethod,
|
|
4799
|
-
githubProject,
|
|
4800
|
-
githubProjectStatusField,
|
|
4971
|
+
githubProject: projectConfig.githubProject,
|
|
4972
|
+
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
4973
|
+
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
4801
4974
|
remoteCheckout,
|
|
4802
4975
|
repair,
|
|
4803
4976
|
privateStateOnly
|
|
@@ -4896,7 +5069,7 @@ Usage: rig connect <list|add|use|status>`, 1);
|
|
|
4896
5069
|
}
|
|
4897
5070
|
|
|
4898
5071
|
// packages/cli/src/commands/github.ts
|
|
4899
|
-
import { spawnSync as
|
|
5072
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
4900
5073
|
function printPayload(context, payload, fallback) {
|
|
4901
5074
|
if (context.outputMode === "json")
|
|
4902
5075
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -4904,7 +5077,7 @@ function printPayload(context, payload, fallback) {
|
|
|
4904
5077
|
console.log(fallback);
|
|
4905
5078
|
}
|
|
4906
5079
|
function readGhToken() {
|
|
4907
|
-
const result =
|
|
5080
|
+
const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
|
|
4908
5081
|
if (result.status !== 0) {
|
|
4909
5082
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
4910
5083
|
throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);
|
|
@@ -5936,14 +6109,10 @@ async function executeRemote(context, args) {
|
|
|
5936
6109
|
}
|
|
5937
6110
|
|
|
5938
6111
|
// packages/cli/src/commands/run.ts
|
|
5939
|
-
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
5940
|
-
import { resolve as resolve19 } from "path";
|
|
5941
6112
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
5942
6113
|
import {
|
|
5943
6114
|
listAuthorityRuns as listAuthorityRuns3,
|
|
5944
|
-
readAuthorityRun as readAuthorityRun4
|
|
5945
|
-
readJsonlFile as readJsonlFile4,
|
|
5946
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir5
|
|
6115
|
+
readAuthorityRun as readAuthorityRun4
|
|
5947
6116
|
} from "@rig/runtime/control-plane/authority-files";
|
|
5948
6117
|
import {
|
|
5949
6118
|
cleanupRunState,
|
|
@@ -5959,9 +6128,9 @@ import {
|
|
|
5959
6128
|
} from "@rig/runtime/control-plane/native/run-ops";
|
|
5960
6129
|
import { loadRuntimeContextFromEnv as loadRuntimeContextFromEnv2 } from "@rig/runtime/control-plane/runtime/context";
|
|
5961
6130
|
|
|
5962
|
-
// packages/cli/src/commands/_operator-
|
|
6131
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
5963
6132
|
import { createInterface } from "readline";
|
|
5964
|
-
|
|
6133
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
5965
6134
|
var CANONICAL_STAGES = [
|
|
5966
6135
|
"Connect",
|
|
5967
6136
|
"GitHub/task sync",
|
|
@@ -5976,18 +6145,141 @@ var CANONICAL_STAGES = [
|
|
|
5976
6145
|
"Merge",
|
|
5977
6146
|
"Complete"
|
|
5978
6147
|
];
|
|
6148
|
+
function logDetail(log3) {
|
|
6149
|
+
return typeof log3.detail === "string" ? log3.detail.trim() : "";
|
|
6150
|
+
}
|
|
6151
|
+
function entryId(entry, fallback) {
|
|
6152
|
+
return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
|
|
6153
|
+
}
|
|
5979
6154
|
function renderOperatorSnapshot(snapshot) {
|
|
5980
6155
|
const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
|
|
5981
6156
|
const runId = String(run.runId ?? run.id ?? "run");
|
|
5982
6157
|
const status = String(run.status ?? "unknown");
|
|
5983
6158
|
const logs = snapshot.logs ?? [];
|
|
6159
|
+
const latestByStage = new Map;
|
|
6160
|
+
for (const log3 of logs) {
|
|
6161
|
+
const title = String(log3.title ?? "").toLowerCase();
|
|
6162
|
+
const stageName = String(log3.stage ?? "").toLowerCase();
|
|
6163
|
+
const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
|
|
6164
|
+
if (stage)
|
|
6165
|
+
latestByStage.set(stage, log3);
|
|
6166
|
+
}
|
|
5984
6167
|
const stageLines = CANONICAL_STAGES.flatMap((stage) => {
|
|
5985
|
-
const match =
|
|
5986
|
-
return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
|
|
6168
|
+
const match = latestByStage.get(stage);
|
|
6169
|
+
return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
|
|
5987
6170
|
});
|
|
5988
6171
|
return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
|
|
5989
6172
|
`);
|
|
5990
6173
|
}
|
|
6174
|
+
function createPiRunStreamRenderer(output = process.stdout) {
|
|
6175
|
+
let lastSnapshot = "";
|
|
6176
|
+
const assistantTextById = new Map;
|
|
6177
|
+
const seenTimeline = new Set;
|
|
6178
|
+
const seenLogs = new Set;
|
|
6179
|
+
const writeLine = (line) => output.write(`${line}
|
|
6180
|
+
`);
|
|
6181
|
+
return {
|
|
6182
|
+
renderSnapshot(snapshot) {
|
|
6183
|
+
const rendered = renderOperatorSnapshot(snapshot);
|
|
6184
|
+
if (rendered && rendered !== lastSnapshot) {
|
|
6185
|
+
writeLine(rendered);
|
|
6186
|
+
lastSnapshot = rendered;
|
|
6187
|
+
}
|
|
6188
|
+
},
|
|
6189
|
+
renderTimeline(entries) {
|
|
6190
|
+
for (const [index, entry] of entries.entries()) {
|
|
6191
|
+
const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
|
|
6192
|
+
if (entry.type === "assistant_message" && typeof entry.text === "string") {
|
|
6193
|
+
const text2 = entry.text;
|
|
6194
|
+
const previousText = assistantTextById.get(id) ?? "";
|
|
6195
|
+
if (text2.startsWith(previousText)) {
|
|
6196
|
+
const delta = text2.slice(previousText.length);
|
|
6197
|
+
if (delta)
|
|
6198
|
+
output.write(delta);
|
|
6199
|
+
} else if (text2.trim() && text2 !== previousText) {
|
|
6200
|
+
writeLine(`
|
|
6201
|
+
[Pi assistant]`);
|
|
6202
|
+
output.write(text2);
|
|
6203
|
+
}
|
|
6204
|
+
assistantTextById.set(id, text2);
|
|
6205
|
+
continue;
|
|
6206
|
+
}
|
|
6207
|
+
if (seenTimeline.has(id))
|
|
6208
|
+
continue;
|
|
6209
|
+
seenTimeline.add(id);
|
|
6210
|
+
if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
|
|
6211
|
+
writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
|
|
6212
|
+
continue;
|
|
6213
|
+
}
|
|
6214
|
+
if (entry.type === "timeline_warning") {
|
|
6215
|
+
writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
},
|
|
6219
|
+
renderLogs(entries) {
|
|
6220
|
+
for (const [index, entry] of entries.entries()) {
|
|
6221
|
+
const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
|
|
6222
|
+
if (seenLogs.has(id))
|
|
6223
|
+
continue;
|
|
6224
|
+
seenLogs.add(id);
|
|
6225
|
+
const title = String(entry.title ?? "");
|
|
6226
|
+
if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
|
|
6227
|
+
continue;
|
|
6228
|
+
const detail = logDetail(entry);
|
|
6229
|
+
if (!detail)
|
|
6230
|
+
continue;
|
|
6231
|
+
writeLine(`[${title || "Rig log"}] ${detail}`);
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
};
|
|
6235
|
+
}
|
|
6236
|
+
function createOperatorSurface(options = {}) {
|
|
6237
|
+
const input = options.input ?? process.stdin;
|
|
6238
|
+
const output = options.output ?? process.stdout;
|
|
6239
|
+
const errorOutput = options.errorOutput ?? process.stderr;
|
|
6240
|
+
const renderer = createPiRunStreamRenderer(output);
|
|
6241
|
+
const writeLine = (line) => output.write(`${line}
|
|
6242
|
+
`);
|
|
6243
|
+
return {
|
|
6244
|
+
mode: "pi-compatible-text",
|
|
6245
|
+
...renderer,
|
|
6246
|
+
info: writeLine,
|
|
6247
|
+
error: (message2) => errorOutput.write(`${message2}
|
|
6248
|
+
`),
|
|
6249
|
+
attachCommandInput(handler) {
|
|
6250
|
+
if (options.interactive === false || !input.isTTY)
|
|
6251
|
+
return null;
|
|
6252
|
+
const rl = createInterface({ input, output: process.stdout, terminal: false });
|
|
6253
|
+
rl.on("line", (line) => {
|
|
6254
|
+
Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6255
|
+
});
|
|
6256
|
+
return { close: () => rl.close() };
|
|
6257
|
+
}
|
|
6258
|
+
};
|
|
6259
|
+
}
|
|
6260
|
+
function taskId(task) {
|
|
6261
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6262
|
+
}
|
|
6263
|
+
function taskTitle(task) {
|
|
6264
|
+
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6265
|
+
}
|
|
6266
|
+
function taskStatus(task) {
|
|
6267
|
+
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6268
|
+
}
|
|
6269
|
+
function renderTaskPickerRows(tasks) {
|
|
6270
|
+
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6271
|
+
}
|
|
6272
|
+
async function promptForTaskSelection(question) {
|
|
6273
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
6274
|
+
try {
|
|
6275
|
+
return await rl.question(question);
|
|
6276
|
+
} finally {
|
|
6277
|
+
rl.close();
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
// packages/cli/src/commands/_operator-view.ts
|
|
6282
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
5991
6283
|
function runStatusFromPayload(payload) {
|
|
5992
6284
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
5993
6285
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -6009,11 +6301,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
|
|
|
6009
6301
|
await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
|
|
6010
6302
|
return { action: "continue", message: "Steering message queued." };
|
|
6011
6303
|
}
|
|
6012
|
-
async function readOperatorSnapshot(context, runId) {
|
|
6304
|
+
async function readOperatorSnapshot(context, runId, options = {}) {
|
|
6013
6305
|
const run = await getRunDetailsViaServer(context, runId);
|
|
6014
6306
|
const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
|
|
6015
|
-
const
|
|
6016
|
-
|
|
6307
|
+
const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
|
|
6308
|
+
entries: [{
|
|
6309
|
+
id: `timeline-unavailable:${runId}`,
|
|
6310
|
+
type: "timeline_warning",
|
|
6311
|
+
detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
|
|
6312
|
+
createdAt: new Date().toISOString()
|
|
6313
|
+
}],
|
|
6314
|
+
nextCursor: options.timelineCursor ?? null
|
|
6315
|
+
}));
|
|
6316
|
+
const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
|
|
6317
|
+
const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6318
|
+
const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
|
|
6319
|
+
return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
|
|
6017
6320
|
}
|
|
6018
6321
|
async function attachRunOperatorView(context, input) {
|
|
6019
6322
|
let steered = false;
|
|
@@ -6021,40 +6324,41 @@ async function attachRunOperatorView(context, input) {
|
|
|
6021
6324
|
await steerRunViaServer(context, input.runId, input.message.trim());
|
|
6022
6325
|
steered = true;
|
|
6023
6326
|
}
|
|
6327
|
+
const surface = createOperatorSurface({ interactive: input.interactive !== false });
|
|
6024
6328
|
let snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6025
6329
|
if (context.outputMode === "text") {
|
|
6026
|
-
|
|
6330
|
+
surface.renderSnapshot(snapshot);
|
|
6331
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6332
|
+
surface.renderLogs(snapshot.logs);
|
|
6027
6333
|
if (steered)
|
|
6028
|
-
|
|
6334
|
+
surface.info("Steering message queued.");
|
|
6029
6335
|
}
|
|
6030
6336
|
let detached = false;
|
|
6031
|
-
let
|
|
6337
|
+
let commandInput = null;
|
|
6032
6338
|
if (input.follow && !input.once && context.outputMode === "text") {
|
|
6033
6339
|
if (input.interactive !== false && process.stdin.isTTY) {
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
}
|
|
6044
|
-
}).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
6340
|
+
surface.info("Controls: /user <message>, /stop, /detach");
|
|
6341
|
+
commandInput = surface.attachCommandInput(async (line) => {
|
|
6342
|
+
const result = await applyOperatorCommand(context, { runId: input.runId, line });
|
|
6343
|
+
if (result.message)
|
|
6344
|
+
surface.info(result.message);
|
|
6345
|
+
if (result.action === "detach" || result.action === "stopped") {
|
|
6346
|
+
detached = true;
|
|
6347
|
+
commandInput?.close();
|
|
6348
|
+
}
|
|
6045
6349
|
});
|
|
6046
6350
|
}
|
|
6047
|
-
let lastRendered = snapshot.rendered;
|
|
6048
6351
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
6352
|
+
let timelineCursor = snapshot.timelineCursor;
|
|
6049
6353
|
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
6050
6354
|
await Bun.sleep(pollMs);
|
|
6051
|
-
snapshot = await readOperatorSnapshot(context, input.runId);
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6355
|
+
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
6356
|
+
timelineCursor = snapshot.timelineCursor;
|
|
6357
|
+
surface.renderSnapshot(snapshot);
|
|
6358
|
+
surface.renderTimeline(snapshot.timeline);
|
|
6359
|
+
surface.renderLogs(snapshot.logs);
|
|
6056
6360
|
}
|
|
6057
|
-
|
|
6361
|
+
commandInput?.close();
|
|
6058
6362
|
}
|
|
6059
6363
|
return { ...snapshot, steered, detached };
|
|
6060
6364
|
}
|
|
@@ -6228,34 +6532,24 @@ async function executeRun(context, args) {
|
|
|
6228
6532
|
if (!run.value) {
|
|
6229
6533
|
throw new CliError2("run timeline requires --run <id>.");
|
|
6230
6534
|
}
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
return events2;
|
|
6240
|
-
};
|
|
6241
|
-
const events = printEvents();
|
|
6535
|
+
const renderer = createPiRunStreamRenderer();
|
|
6536
|
+
let cursor = null;
|
|
6537
|
+
const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
|
|
6538
|
+
const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6539
|
+
cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
|
|
6540
|
+
if (context.outputMode === "text") {
|
|
6541
|
+
renderer.renderTimeline(events);
|
|
6542
|
+
}
|
|
6242
6543
|
if (follow.value && context.outputMode === "text") {
|
|
6243
|
-
let lastLength = existsSync11(timelinePath) ? readFileSync9(timelinePath, "utf8").length : 0;
|
|
6244
6544
|
while (true) {
|
|
6245
6545
|
await Bun.sleep(1000);
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
continue;
|
|
6251
|
-
const delta = next.slice(lastLength);
|
|
6252
|
-
lastLength = next.length;
|
|
6253
|
-
for (const line of delta.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
|
|
6254
|
-
console.log(line);
|
|
6255
|
-
}
|
|
6546
|
+
const nextPage = await getRunTimelineViaServer(context, run.value, { limit: 500, ...cursor ? { cursor } : {} });
|
|
6547
|
+
const nextEvents = Array.isArray(nextPage.entries) ? nextPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
6548
|
+
cursor = typeof nextPage.nextCursor === "string" ? nextPage.nextCursor : cursor;
|
|
6549
|
+
renderer.renderTimeline(nextEvents);
|
|
6256
6550
|
}
|
|
6257
6551
|
}
|
|
6258
|
-
return { ok: true, group: "run", command, details: { runId: run.value, events } };
|
|
6552
|
+
return { ok: true, group: "run", command, details: { runId: run.value, events, cursor } };
|
|
6259
6553
|
}
|
|
6260
6554
|
case "attach": {
|
|
6261
6555
|
let pending = rest;
|
|
@@ -6545,10 +6839,10 @@ async function executeServer(context, args, options) {
|
|
|
6545
6839
|
}
|
|
6546
6840
|
|
|
6547
6841
|
// packages/cli/src/commands/task.ts
|
|
6548
|
-
import { readFileSync as
|
|
6549
|
-
import { spawnSync as
|
|
6550
|
-
import { createInterface as
|
|
6551
|
-
import { resolve as
|
|
6842
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
6843
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
6844
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
6845
|
+
import { resolve as resolve19 } from "path";
|
|
6552
6846
|
import {
|
|
6553
6847
|
taskArtifactDir,
|
|
6554
6848
|
taskArtifacts,
|
|
@@ -6566,19 +6860,9 @@ import {
|
|
|
6566
6860
|
} from "@rig/runtime/control-plane/native/task-ops";
|
|
6567
6861
|
|
|
6568
6862
|
// packages/cli/src/commands/_task-picker.ts
|
|
6569
|
-
|
|
6570
|
-
function taskId(task) {
|
|
6863
|
+
function taskId2(task) {
|
|
6571
6864
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6572
6865
|
}
|
|
6573
|
-
function taskTitle(task) {
|
|
6574
|
-
return typeof task.title === "string" && task.title.trim() ? task.title : "Untitled task";
|
|
6575
|
-
}
|
|
6576
|
-
function taskStatus(task) {
|
|
6577
|
-
return typeof task.status === "string" && task.status.trim() ? task.status : "unknown";
|
|
6578
|
-
}
|
|
6579
|
-
function renderTaskPickerRows(tasks) {
|
|
6580
|
-
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
6581
|
-
}
|
|
6582
6866
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
6583
6867
|
if (tasks.length === 0)
|
|
6584
6868
|
return null;
|
|
@@ -6588,17 +6872,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6588
6872
|
if (!isTty) {
|
|
6589
6873
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
6590
6874
|
}
|
|
6591
|
-
const prompt = io.prompt ??
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
} finally {
|
|
6596
|
-
rl.close();
|
|
6597
|
-
}
|
|
6598
|
-
});
|
|
6599
|
-
console.log("Select Rig task:");
|
|
6875
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
6876
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
6877
|
+
`) };
|
|
6878
|
+
renderer.writeLine("Select Rig task:");
|
|
6600
6879
|
for (const row of renderTaskPickerRows(tasks))
|
|
6601
|
-
|
|
6880
|
+
renderer.writeLine(` ${row}`);
|
|
6602
6881
|
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
6603
6882
|
if (!answer)
|
|
6604
6883
|
return null;
|
|
@@ -6606,7 +6885,7 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
6606
6885
|
const index = Number.parseInt(answer, 10) - 1;
|
|
6607
6886
|
return tasks[index] ?? null;
|
|
6608
6887
|
}
|
|
6609
|
-
return tasks.find((task) =>
|
|
6888
|
+
return tasks.find((task) => taskId2(task) === answer) ?? null;
|
|
6610
6889
|
}
|
|
6611
6890
|
|
|
6612
6891
|
// packages/cli/src/commands/task.ts
|
|
@@ -6684,7 +6963,7 @@ function normalizePrMode(value) {
|
|
|
6684
6963
|
throw new CliError2("--pr must be auto, ask, or off.", 2);
|
|
6685
6964
|
}
|
|
6686
6965
|
function detectLocalDirtyState(projectRoot) {
|
|
6687
|
-
const result =
|
|
6966
|
+
const result = spawnSync3("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
6688
6967
|
if (result.status !== 0)
|
|
6689
6968
|
return { dirty: false, modified: 0, untracked: 0, lines: [] };
|
|
6690
6969
|
const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
|
|
@@ -6718,7 +6997,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
6718
6997
|
if (explicit)
|
|
6719
6998
|
return { mode: explicit, state };
|
|
6720
6999
|
if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
|
|
6721
|
-
const rl =
|
|
7000
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6722
7001
|
try {
|
|
6723
7002
|
const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
|
|
6724
7003
|
return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
|
|
@@ -6803,12 +7082,12 @@ async function executeTask(context, args, options) {
|
|
|
6803
7082
|
const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
|
|
6804
7083
|
const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
|
|
6805
7084
|
requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
|
|
6806
|
-
const
|
|
6807
|
-
if (!
|
|
7085
|
+
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
7086
|
+
if (!taskId3)
|
|
6808
7087
|
throw new CliError2("task show requires a task id.", 2);
|
|
6809
|
-
const task = await getWorkspaceTaskViaServer(context,
|
|
7088
|
+
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
6810
7089
|
if (!task)
|
|
6811
|
-
throw new CliError2(`Task not found: ${
|
|
7090
|
+
throw new CliError2(`Task not found: ${taskId3}`, 3);
|
|
6812
7091
|
const summary = summarizeTask(task, { raw: true });
|
|
6813
7092
|
if (context.outputMode === "text")
|
|
6814
7093
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -6879,7 +7158,7 @@ async function executeTask(context, args, options) {
|
|
|
6879
7158
|
const fileFlag = takeOption(rest.slice(1), "--file");
|
|
6880
7159
|
let content;
|
|
6881
7160
|
if (fileFlag.value) {
|
|
6882
|
-
content =
|
|
7161
|
+
content = readFileSync9(resolve19(context.projectRoot, fileFlag.value), "utf-8");
|
|
6883
7162
|
} else {
|
|
6884
7163
|
content = await readStdin();
|
|
6885
7164
|
}
|
|
@@ -7100,9 +7379,9 @@ async function executeTask(context, args, options) {
|
|
|
7100
7379
|
}
|
|
7101
7380
|
|
|
7102
7381
|
// packages/cli/src/commands/task-run-driver.ts
|
|
7103
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
7104
|
-
import { resolve as
|
|
7105
|
-
import { spawn as spawn2, spawnSync as
|
|
7382
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7383
|
+
import { resolve as resolve20 } from "path";
|
|
7384
|
+
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
7106
7385
|
import { createInterface as createLineInterface } from "readline";
|
|
7107
7386
|
import { loadConfig as loadConfig2 } from "@rig/core/load-config";
|
|
7108
7387
|
import {
|
|
@@ -7127,8 +7406,7 @@ import {
|
|
|
7127
7406
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
7128
7407
|
import { readAuthorityRun as readAuthorityRun5, readJsonFile as readJsonFile3, resolveTaskArtifactDirs as resolveTaskArtifactDirs2 } from "@rig/runtime/control-plane/authority-files";
|
|
7129
7408
|
import {
|
|
7130
|
-
buildTaskRunLifecycleComment
|
|
7131
|
-
updateConfiguredTaskSourceTask
|
|
7409
|
+
buildTaskRunLifecycleComment
|
|
7132
7410
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
7133
7411
|
import {
|
|
7134
7412
|
closeIssueAfterMergedPr,
|
|
@@ -7166,7 +7444,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
7166
7444
|
};
|
|
7167
7445
|
}
|
|
7168
7446
|
function runGitSync(cwd, args, input) {
|
|
7169
|
-
const result =
|
|
7447
|
+
const result = spawnSync4("git", [...args], {
|
|
7170
7448
|
cwd,
|
|
7171
7449
|
input,
|
|
7172
7450
|
encoding: "utf8",
|
|
@@ -7184,12 +7462,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
7184
7462
|
return 0;
|
|
7185
7463
|
let copied = 0;
|
|
7186
7464
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
7187
|
-
const sourcePath =
|
|
7188
|
-
const targetPath =
|
|
7465
|
+
const sourcePath = resolve20(sourceRoot, relativePath);
|
|
7466
|
+
const targetPath = resolve20(targetRoot, relativePath);
|
|
7189
7467
|
try {
|
|
7190
7468
|
if (!statSync2(sourcePath).isFile())
|
|
7191
7469
|
continue;
|
|
7192
|
-
mkdirSync7(
|
|
7470
|
+
mkdirSync7(resolve20(targetPath, ".."), { recursive: true });
|
|
7193
7471
|
copyFileSync3(sourcePath, targetPath);
|
|
7194
7472
|
copied += 1;
|
|
7195
7473
|
} catch {}
|
|
@@ -7228,7 +7506,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
7228
7506
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
7229
7507
|
return {
|
|
7230
7508
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
7231
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
7509
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve20(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
7232
7510
|
};
|
|
7233
7511
|
}
|
|
7234
7512
|
function positiveInt(value, fallback) {
|
|
@@ -7339,9 +7617,9 @@ function createCommandRunner(binary) {
|
|
|
7339
7617
|
const stderrChunks = [];
|
|
7340
7618
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7341
7619
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
7342
|
-
return await new Promise((
|
|
7343
|
-
child.once("error", (error) =>
|
|
7344
|
-
child.once("close", (code) =>
|
|
7620
|
+
return await new Promise((resolve21) => {
|
|
7621
|
+
child.once("error", (error) => resolve21({ exitCode: 1, stderr: error.message }));
|
|
7622
|
+
child.once("close", (code) => resolve21({
|
|
7345
7623
|
exitCode: code ?? 1,
|
|
7346
7624
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
7347
7625
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -7361,12 +7639,13 @@ async function resolvePostValidationBranch(input) {
|
|
|
7361
7639
|
return input.configuredBranch;
|
|
7362
7640
|
}
|
|
7363
7641
|
async function runTaskRunPostValidationLifecycle(input) {
|
|
7364
|
-
const
|
|
7365
|
-
if (!
|
|
7642
|
+
const taskId3 = input.taskId?.trim();
|
|
7643
|
+
if (!taskId3) {
|
|
7366
7644
|
return { status: "skipped" };
|
|
7367
7645
|
}
|
|
7368
|
-
const
|
|
7369
|
-
const
|
|
7646
|
+
const configInput = input.config ?? null;
|
|
7647
|
+
const config = configInput ?? {};
|
|
7648
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
7370
7649
|
if (prMode === "off" || prMode === "ask") {
|
|
7371
7650
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
7372
7651
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -7382,7 +7661,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7382
7661
|
gitCommand
|
|
7383
7662
|
});
|
|
7384
7663
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
7385
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
7664
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
7386
7665
|
const stage = input.appendStage ?? (() => {
|
|
7387
7666
|
return;
|
|
7388
7667
|
});
|
|
@@ -7400,7 +7679,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7400
7679
|
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
7401
7680
|
const commit = await commitRunChanges({
|
|
7402
7681
|
cwd: workspace,
|
|
7403
|
-
message: `rig: complete task ${
|
|
7682
|
+
message: `rig: complete task ${taskId3}`,
|
|
7404
7683
|
command: gitCommand
|
|
7405
7684
|
});
|
|
7406
7685
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
@@ -7408,14 +7687,15 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7408
7687
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
7409
7688
|
const pr = await prAutomation({
|
|
7410
7689
|
projectRoot: workspace,
|
|
7411
|
-
taskId:
|
|
7690
|
+
taskId: taskId3,
|
|
7412
7691
|
runId: input.runId,
|
|
7413
7692
|
branch,
|
|
7414
7693
|
config,
|
|
7415
7694
|
sourceTask: input.sourceTask,
|
|
7416
7695
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
7417
|
-
artifactRoot:
|
|
7696
|
+
artifactRoot: resolve20(input.projectRoot, "artifacts", taskId3),
|
|
7418
7697
|
command: ghCommand,
|
|
7698
|
+
gitCommand,
|
|
7419
7699
|
steerPi,
|
|
7420
7700
|
lifecycle: {
|
|
7421
7701
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -7423,7 +7703,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7423
7703
|
try {
|
|
7424
7704
|
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
7425
7705
|
await updateTaskSource(input.projectRoot, {
|
|
7426
|
-
taskId:
|
|
7706
|
+
taskId: taskId3,
|
|
7427
7707
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7428
7708
|
update: {
|
|
7429
7709
|
status: "under_review",
|
|
@@ -7453,7 +7733,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7453
7733
|
`), "reviewing", "error");
|
|
7454
7734
|
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
7455
7735
|
await updateTaskSource(input.projectRoot, {
|
|
7456
|
-
taskId:
|
|
7736
|
+
taskId: taskId3,
|
|
7457
7737
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7458
7738
|
update: {
|
|
7459
7739
|
status: "ci_fixing",
|
|
@@ -7464,8 +7744,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7464
7744
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7465
7745
|
})
|
|
7466
7746
|
}
|
|
7467
|
-
}).catch(() => {
|
|
7468
|
-
return;
|
|
7469
7747
|
});
|
|
7470
7748
|
}
|
|
7471
7749
|
},
|
|
@@ -7473,7 +7751,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7473
7751
|
stage("Merge", prUrl, "running", "tool");
|
|
7474
7752
|
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
7475
7753
|
await updateTaskSource(input.projectRoot, {
|
|
7476
|
-
taskId:
|
|
7754
|
+
taskId: taskId3,
|
|
7477
7755
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7478
7756
|
update: {
|
|
7479
7757
|
status: "merging",
|
|
@@ -7484,8 +7762,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7484
7762
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
7485
7763
|
})
|
|
7486
7764
|
}
|
|
7487
|
-
}).catch(() => {
|
|
7488
|
-
return;
|
|
7489
7765
|
});
|
|
7490
7766
|
}
|
|
7491
7767
|
},
|
|
@@ -7502,7 +7778,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7502
7778
|
stage("Needs attention", detail, "needs_attention", "error");
|
|
7503
7779
|
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
7504
7780
|
await updateTaskSource(input.projectRoot, {
|
|
7505
|
-
taskId:
|
|
7781
|
+
taskId: taskId3,
|
|
7506
7782
|
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
7507
7783
|
update: {
|
|
7508
7784
|
status: "needs_attention",
|
|
@@ -7514,8 +7790,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7514
7790
|
errorText: detail
|
|
7515
7791
|
})
|
|
7516
7792
|
}
|
|
7517
|
-
}).catch(() => {
|
|
7518
|
-
|
|
7793
|
+
}).catch((error) => {
|
|
7794
|
+
try {
|
|
7795
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
7796
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
7797
|
+
title: "Task source needs-attention update failed",
|
|
7798
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
7799
|
+
tone: "error",
|
|
7800
|
+
status: "needs_attention",
|
|
7801
|
+
createdAt: new Date().toISOString()
|
|
7802
|
+
});
|
|
7803
|
+
} catch {}
|
|
7519
7804
|
});
|
|
7520
7805
|
}
|
|
7521
7806
|
return { status: "needs_attention", pr };
|
|
@@ -7523,7 +7808,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7523
7808
|
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
7524
7809
|
await closeIssueAfterMergedPr({
|
|
7525
7810
|
projectRoot: input.projectRoot,
|
|
7526
|
-
taskId:
|
|
7811
|
+
taskId: taskId3,
|
|
7527
7812
|
runId: input.runId,
|
|
7528
7813
|
prUrl: pr.prUrl,
|
|
7529
7814
|
sourceTask: input.sourceTask,
|
|
@@ -7533,12 +7818,12 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
7533
7818
|
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
7534
7819
|
return { status: "completed", pr };
|
|
7535
7820
|
}
|
|
7536
|
-
function summarizeValidationFailure(projectRoot,
|
|
7537
|
-
if (!
|
|
7821
|
+
function summarizeValidationFailure(projectRoot, taskId3) {
|
|
7822
|
+
if (!taskId3) {
|
|
7538
7823
|
return null;
|
|
7539
7824
|
}
|
|
7540
|
-
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot,
|
|
7541
|
-
const summary = readJsonFile3(
|
|
7825
|
+
for (const artifactDir of resolveTaskArtifactDirs2(projectRoot, taskId3)) {
|
|
7826
|
+
const summary = readJsonFile3(resolve20(artifactDir, "validation-summary.json"), null);
|
|
7542
7827
|
if (!summary || summary.status !== "fail") {
|
|
7543
7828
|
continue;
|
|
7544
7829
|
}
|
|
@@ -7619,9 +7904,9 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
7619
7904
|
if (!input.taskId || !input.workspaceDir) {
|
|
7620
7905
|
return { accepted: false, reason: null };
|
|
7621
7906
|
}
|
|
7622
|
-
const artifactDir =
|
|
7623
|
-
const reviewStatusPath =
|
|
7624
|
-
const taskResultPath =
|
|
7907
|
+
const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
|
|
7908
|
+
const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
|
|
7909
|
+
const taskResultPath = resolve20(artifactDir, "task-result.json");
|
|
7625
7910
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7626
7911
|
if (reviewStatus !== "APPROVED") {
|
|
7627
7912
|
return { accepted: false, reason: null };
|
|
@@ -7658,12 +7943,12 @@ function resolveTaskRunRetryContext(input) {
|
|
|
7658
7943
|
if (!input.taskId || !input.workspaceDir) {
|
|
7659
7944
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
7660
7945
|
}
|
|
7661
|
-
const artifactDir =
|
|
7662
|
-
const reviewStatePath =
|
|
7663
|
-
const reviewFeedbackPath =
|
|
7664
|
-
const reviewStatusPath =
|
|
7665
|
-
const failedApproachesPath =
|
|
7666
|
-
const validationSummaryPath =
|
|
7946
|
+
const artifactDir = resolve20(input.workspaceDir, "artifacts", input.taskId);
|
|
7947
|
+
const reviewStatePath = resolve20(artifactDir, "review-state.json");
|
|
7948
|
+
const reviewFeedbackPath = resolve20(artifactDir, "review-feedback.md");
|
|
7949
|
+
const reviewStatusPath = resolve20(artifactDir, "review-status.txt");
|
|
7950
|
+
const failedApproachesPath = resolve20(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
7951
|
+
const validationSummaryPath = resolve20(artifactDir, "validation-summary.json");
|
|
7667
7952
|
const reviewState = readJsonFile3(reviewStatePath, null);
|
|
7668
7953
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
7669
7954
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
@@ -7717,12 +8002,60 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
7717
8002
|
}
|
|
7718
8003
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
7719
8004
|
}
|
|
8005
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
8006
|
+
let nextAssistantText = input.assistantText;
|
|
8007
|
+
if (input.record.type === "message_update") {
|
|
8008
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
8009
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
8010
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
8011
|
+
}
|
|
8012
|
+
} else if (input.record.type === "stream_event") {
|
|
8013
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
8014
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
8015
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
8016
|
+
nextAssistantText += delta.text;
|
|
8017
|
+
}
|
|
8018
|
+
} else if (input.record.type === "assistant") {
|
|
8019
|
+
const message2 = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
8020
|
+
const content = Array.isArray(message2.content) ? message2.content : [];
|
|
8021
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
8022
|
+
if (fullText.length > nextAssistantText.length)
|
|
8023
|
+
nextAssistantText = fullText;
|
|
8024
|
+
}
|
|
8025
|
+
if (nextAssistantText !== input.assistantText) {
|
|
8026
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8027
|
+
id: input.messageId,
|
|
8028
|
+
type: "assistant_message",
|
|
8029
|
+
text: nextAssistantText,
|
|
8030
|
+
state: "streaming",
|
|
8031
|
+
createdAt: new Date().toISOString()
|
|
8032
|
+
});
|
|
8033
|
+
}
|
|
8034
|
+
return nextAssistantText;
|
|
8035
|
+
}
|
|
8036
|
+
function appendToolTimelineFromLog(input) {
|
|
8037
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
8038
|
+
if (title !== "Tool activity")
|
|
8039
|
+
return;
|
|
8040
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
8041
|
+
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;
|
|
8042
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
8043
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
8044
|
+
id: `tool:${logId}`,
|
|
8045
|
+
type: "tool_execution_update",
|
|
8046
|
+
toolName,
|
|
8047
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
8048
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
8049
|
+
payload,
|
|
8050
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
8051
|
+
});
|
|
8052
|
+
}
|
|
7720
8053
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
7721
|
-
if (!
|
|
8054
|
+
if (!existsSync11(reviewStatusPath)) {
|
|
7722
8055
|
return null;
|
|
7723
8056
|
}
|
|
7724
8057
|
try {
|
|
7725
|
-
const status =
|
|
8058
|
+
const status = readFileSync10(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
7726
8059
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
7727
8060
|
} catch {
|
|
7728
8061
|
return null;
|
|
@@ -7740,14 +8073,45 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
7740
8073
|
function runSourceTaskIdentity(sourceTask) {
|
|
7741
8074
|
return sourceTask;
|
|
7742
8075
|
}
|
|
7743
|
-
|
|
7744
|
-
if (!
|
|
8076
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
8077
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
8078
|
+
return null;
|
|
8079
|
+
const record = sourceTask;
|
|
8080
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
8081
|
+
if (direct?.trim())
|
|
8082
|
+
return direct.trim();
|
|
8083
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
8084
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
8085
|
+
}
|
|
8086
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
8087
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
8088
|
+
id: input.taskId,
|
|
8089
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
8090
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
8091
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
8092
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
8093
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
8094
|
+
});
|
|
8095
|
+
if (serverResult.ok === false) {
|
|
8096
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
8097
|
+
}
|
|
8098
|
+
return {
|
|
8099
|
+
updated: serverResult.ok !== false,
|
|
8100
|
+
taskId: input.taskId,
|
|
8101
|
+
status: input.update.status,
|
|
8102
|
+
source: "server",
|
|
8103
|
+
sourceKind: "server",
|
|
8104
|
+
projectSync: serverResult.projectSync
|
|
8105
|
+
};
|
|
8106
|
+
};
|
|
8107
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId3, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
8108
|
+
if (!taskId3)
|
|
7745
8109
|
return;
|
|
7746
8110
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
7747
8111
|
if (!shouldWriteDriverIssueUpdate(config, status))
|
|
7748
8112
|
return;
|
|
7749
8113
|
const result = await updateTaskSource(projectRoot, {
|
|
7750
|
-
taskId:
|
|
8114
|
+
taskId: taskId3,
|
|
7751
8115
|
sourceTask: runSourceTaskIdentity(sourceTask),
|
|
7752
8116
|
update: {
|
|
7753
8117
|
status,
|
|
@@ -7766,14 +8130,14 @@ async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId2, sourc
|
|
|
7766
8130
|
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
7767
8131
|
}
|
|
7768
8132
|
}
|
|
7769
|
-
function readRunSourceTaskContract(projectRoot, runId,
|
|
8133
|
+
function readRunSourceTaskContract(projectRoot, runId, taskId3) {
|
|
7770
8134
|
const run = readAuthorityRun5(projectRoot, runId);
|
|
7771
8135
|
const raw = run?.sourceTask;
|
|
7772
8136
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7773
8137
|
return null;
|
|
7774
8138
|
}
|
|
7775
8139
|
const record = raw;
|
|
7776
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() :
|
|
8140
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId3?.trim();
|
|
7777
8141
|
if (!id) {
|
|
7778
8142
|
return null;
|
|
7779
8143
|
}
|
|
@@ -7909,15 +8273,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
7909
8273
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
7910
8274
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
7911
8275
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
7912
|
-
const planningArtifactPath =
|
|
8276
|
+
const planningArtifactPath = resolve20("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
7913
8277
|
const persistedPlanning = {
|
|
7914
8278
|
...planningClassification,
|
|
7915
8279
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
7916
8280
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
7917
8281
|
classifiedAt: new Date().toISOString()
|
|
7918
8282
|
};
|
|
7919
|
-
mkdirSync7(
|
|
7920
|
-
writeFileSync6(
|
|
8283
|
+
mkdirSync7(resolve20(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
8284
|
+
writeFileSync6(resolve20(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
7921
8285
|
`, "utf8");
|
|
7922
8286
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
7923
8287
|
prompt = `${prompt}
|
|
@@ -7967,7 +8331,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
7967
8331
|
let verificationStarted = false;
|
|
7968
8332
|
let reviewStarted = false;
|
|
7969
8333
|
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
7970
|
-
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ?
|
|
8334
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve20(existingRunRecord.sessionPath, "..") : null;
|
|
7971
8335
|
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
7972
8336
|
let latestProviderCommand = null;
|
|
7973
8337
|
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
@@ -8050,10 +8414,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8050
8414
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8051
8415
|
status: "running",
|
|
8052
8416
|
worktreePath: latestRuntimeWorkspace,
|
|
8053
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
8417
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve20(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
8054
8418
|
logRoot: latestLogsDir,
|
|
8055
|
-
sessionPath: latestSessionDir ?
|
|
8056
|
-
sessionLogPath: latestLogsDir ?
|
|
8419
|
+
sessionPath: latestSessionDir ? resolve20(latestSessionDir, "session.json") : null,
|
|
8420
|
+
sessionLogPath: latestLogsDir ? resolve20(latestLogsDir, "agent-stdout.log") : null,
|
|
8057
8421
|
branch: runtimeId
|
|
8058
8422
|
});
|
|
8059
8423
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -8061,7 +8425,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8061
8425
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
8062
8426
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
8063
8427
|
if (readyFile) {
|
|
8064
|
-
mkdirSync7(
|
|
8428
|
+
mkdirSync7(resolve20(readyFile, ".."), { recursive: true });
|
|
8065
8429
|
writeFileSync6(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
8066
8430
|
`, "utf8");
|
|
8067
8431
|
}
|
|
@@ -8210,7 +8574,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8210
8574
|
if (providerLogs.length > 0) {
|
|
8211
8575
|
for (const providerLog of providerLogs) {
|
|
8212
8576
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
8577
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
8213
8578
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
8579
|
+
if (providerLog.title === "Tool activity")
|
|
8580
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8214
8581
|
}
|
|
8215
8582
|
}
|
|
8216
8583
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -8368,7 +8735,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8368
8735
|
let acceptedArtifactObservedAt = null;
|
|
8369
8736
|
let acceptedArtifactPollTimer = null;
|
|
8370
8737
|
let acceptedArtifactKillTimer = null;
|
|
8371
|
-
const attemptExit = await new Promise((
|
|
8738
|
+
const attemptExit = await new Promise((resolve21) => {
|
|
8372
8739
|
let settled = false;
|
|
8373
8740
|
const settle = (result) => {
|
|
8374
8741
|
if (settled)
|
|
@@ -8376,7 +8743,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8376
8743
|
settled = true;
|
|
8377
8744
|
if (acceptedArtifactPollTimer)
|
|
8378
8745
|
clearInterval(acceptedArtifactPollTimer);
|
|
8379
|
-
|
|
8746
|
+
resolve21(result);
|
|
8380
8747
|
};
|
|
8381
8748
|
const pollAcceptedArtifacts = () => {
|
|
8382
8749
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -8449,7 +8816,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
8449
8816
|
});
|
|
8450
8817
|
for (const pendingLog of pendingLogs) {
|
|
8451
8818
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
8819
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
8452
8820
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
8821
|
+
if (pendingLog.title === "Tool activity")
|
|
8822
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
8453
8823
|
}
|
|
8454
8824
|
process.off("SIGTERM", forwardSigterm);
|
|
8455
8825
|
if (attemptExit.error) {
|
|
@@ -8575,8 +8945,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8575
8945
|
}
|
|
8576
8946
|
if (planningClassification.planningRequired) {
|
|
8577
8947
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
8578
|
-
const expectedPlanPath =
|
|
8579
|
-
if (!
|
|
8948
|
+
const expectedPlanPath = resolve20(planWorkspace, planningArtifactPath);
|
|
8949
|
+
if (!existsSync11(expectedPlanPath)) {
|
|
8580
8950
|
const failedAt = new Date().toISOString();
|
|
8581
8951
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
8582
8952
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -8596,6 +8966,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8596
8966
|
throw new CliError2(failureDetail, 1);
|
|
8597
8967
|
}
|
|
8598
8968
|
}
|
|
8969
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
8970
|
+
appendPiStageLog({
|
|
8971
|
+
projectRoot: context.projectRoot,
|
|
8972
|
+
runId: input.runId,
|
|
8973
|
+
stage: "Validate",
|
|
8974
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
8975
|
+
status: "completed"
|
|
8976
|
+
});
|
|
8977
|
+
if (verificationAction && !reviewStarted) {
|
|
8978
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
8979
|
+
}
|
|
8980
|
+
if (!reviewAction) {
|
|
8981
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
8982
|
+
}
|
|
8983
|
+
if (reviewAction) {
|
|
8984
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
8985
|
+
}
|
|
8986
|
+
const requestedAt = new Date().toISOString();
|
|
8987
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
8988
|
+
status: "reviewing",
|
|
8989
|
+
completedAt: null,
|
|
8990
|
+
errorText: null,
|
|
8991
|
+
serverCloseout: {
|
|
8992
|
+
status: "pending",
|
|
8993
|
+
phase: "queued",
|
|
8994
|
+
requestedAt,
|
|
8995
|
+
updatedAt: requestedAt,
|
|
8996
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
8997
|
+
branch: latestRuntimeBranch,
|
|
8998
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
8999
|
+
}
|
|
9000
|
+
});
|
|
9001
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
9002
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
9003
|
+
title: "Server-owned closeout requested",
|
|
9004
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
9005
|
+
tone: "info",
|
|
9006
|
+
status: "reviewing",
|
|
9007
|
+
createdAt: requestedAt,
|
|
9008
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
9009
|
+
});
|
|
9010
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
9011
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
9012
|
+
await context.emitEvent("command.finished", {
|
|
9013
|
+
command: [
|
|
9014
|
+
"rig",
|
|
9015
|
+
"server",
|
|
9016
|
+
"task-run",
|
|
9017
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
9018
|
+
...input.title ? ["--title", input.title] : []
|
|
9019
|
+
],
|
|
9020
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
9021
|
+
exitCode: 0,
|
|
9022
|
+
durationMs: 0,
|
|
9023
|
+
startedAt,
|
|
9024
|
+
finishedAt: requestedAt
|
|
9025
|
+
});
|
|
9026
|
+
return;
|
|
9027
|
+
}
|
|
8599
9028
|
const runPiPrFeedbackFix = async (message2) => {
|
|
8600
9029
|
appendPiStageLog({
|
|
8601
9030
|
projectRoot: context.projectRoot,
|
|
@@ -8623,11 +9052,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8623
9052
|
child.stdin.write(message2);
|
|
8624
9053
|
}
|
|
8625
9054
|
child.stdin.end();
|
|
9055
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
9056
|
+
let feedbackAssistantText = "";
|
|
9057
|
+
const feedbackPendingToolUses = new Map;
|
|
8626
9058
|
const stdout = createLineInterface({ input: child.stdout });
|
|
8627
9059
|
stdout.on("line", (line) => {
|
|
8628
9060
|
const trimmed = line.trim();
|
|
8629
9061
|
if (!trimmed)
|
|
8630
9062
|
return;
|
|
9063
|
+
try {
|
|
9064
|
+
const record = JSON.parse(trimmed);
|
|
9065
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
9066
|
+
runId: input.runId,
|
|
9067
|
+
record,
|
|
9068
|
+
createdAtFallback: new Date().toISOString(),
|
|
9069
|
+
status: "reviewing",
|
|
9070
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9071
|
+
});
|
|
9072
|
+
for (const providerLog of providerLogs) {
|
|
9073
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
9074
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
9075
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
9076
|
+
if (providerLog.title === "Tool activity")
|
|
9077
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9078
|
+
}
|
|
9079
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
9080
|
+
projectRoot: context.projectRoot,
|
|
9081
|
+
runId: input.runId,
|
|
9082
|
+
messageId: feedbackAssistantMessageId,
|
|
9083
|
+
record,
|
|
9084
|
+
assistantText: feedbackAssistantText
|
|
9085
|
+
});
|
|
9086
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
9087
|
+
if (hadAssistantDelta) {
|
|
9088
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
9089
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9090
|
+
}
|
|
9091
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
9092
|
+
return;
|
|
9093
|
+
} catch {}
|
|
8631
9094
|
appendRunLog(context.projectRoot, input.runId, {
|
|
8632
9095
|
id: nextRunLogId(),
|
|
8633
9096
|
title: "Pi PR feedback fix output",
|
|
@@ -8653,10 +9116,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
8653
9116
|
});
|
|
8654
9117
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
8655
9118
|
});
|
|
8656
|
-
const exitCode = await new Promise((
|
|
8657
|
-
child.once("error", () =>
|
|
8658
|
-
child.once("close", (code) =>
|
|
9119
|
+
const exitCode = await new Promise((resolve21) => {
|
|
9120
|
+
child.once("error", () => resolve21(1));
|
|
9121
|
+
child.once("close", (code) => resolve21(code ?? 1));
|
|
8659
9122
|
});
|
|
9123
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
9124
|
+
runId: input.runId,
|
|
9125
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
9126
|
+
pendingToolUses: feedbackPendingToolUses
|
|
9127
|
+
})) {
|
|
9128
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
9129
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
9130
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
9131
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9132
|
+
}
|
|
9133
|
+
if (feedbackAssistantText.trim()) {
|
|
9134
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
9135
|
+
id: feedbackAssistantMessageId,
|
|
9136
|
+
type: "assistant_message",
|
|
9137
|
+
text: feedbackAssistantText,
|
|
9138
|
+
state: "completed",
|
|
9139
|
+
createdAt: new Date().toISOString(),
|
|
9140
|
+
completedAt: new Date().toISOString()
|
|
9141
|
+
});
|
|
9142
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
9143
|
+
}
|
|
8660
9144
|
if (exitCode !== 0) {
|
|
8661
9145
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
8662
9146
|
}
|
|
@@ -8774,8 +9258,8 @@ async function executeTest(context, args) {
|
|
|
8774
9258
|
}
|
|
8775
9259
|
|
|
8776
9260
|
// packages/cli/src/commands/setup.ts
|
|
8777
|
-
import { existsSync as
|
|
8778
|
-
import { resolve as
|
|
9261
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
9262
|
+
import { resolve as resolve21 } from "path";
|
|
8779
9263
|
import { createPluginHost } from "@rig/core";
|
|
8780
9264
|
import {
|
|
8781
9265
|
isSupportedBunVersion as isSupportedBunVersion2,
|
|
@@ -8838,8 +9322,8 @@ function runSetupInit(projectRoot) {
|
|
|
8838
9322
|
mkdirSync8(stateDir, { recursive: true });
|
|
8839
9323
|
mkdirSync8(logsDir, { recursive: true });
|
|
8840
9324
|
mkdirSync8(artifactsDir, { recursive: true });
|
|
8841
|
-
const failuresPath =
|
|
8842
|
-
if (!
|
|
9325
|
+
const failuresPath = resolve21(stateDir, "failed_approaches.md");
|
|
9326
|
+
if (!existsSync12(failuresPath)) {
|
|
8843
9327
|
writeFileSync7(failuresPath, `# Failed Approaches
|
|
8844
9328
|
|
|
8845
9329
|
`, "utf-8");
|
|
@@ -8857,18 +9341,18 @@ async function runSetupCheck(projectRoot) {
|
|
|
8857
9341
|
}
|
|
8858
9342
|
async function runSetupPreflight(projectRoot) {
|
|
8859
9343
|
await runSetupCheck(projectRoot);
|
|
8860
|
-
const validationRoot =
|
|
8861
|
-
if (
|
|
9344
|
+
const validationRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
9345
|
+
if (existsSync12(validationRoot)) {
|
|
8862
9346
|
const validators = readdirSync2(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
8863
9347
|
for (const validator of validators) {
|
|
8864
|
-
const script =
|
|
8865
|
-
if (
|
|
9348
|
+
const script = resolve21(validationRoot, validator.name, "validate.sh");
|
|
9349
|
+
if (existsSync12(script)) {
|
|
8866
9350
|
console.log(`OK: validator script ${script}`);
|
|
8867
9351
|
}
|
|
8868
9352
|
}
|
|
8869
9353
|
}
|
|
8870
|
-
const hooksRoot =
|
|
8871
|
-
if (
|
|
9354
|
+
const hooksRoot = resolve21(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
|
|
9355
|
+
if (existsSync12(hooksRoot)) {
|
|
8872
9356
|
const hooks = readdirSync2(hooksRoot).filter((name) => name.endsWith(".sh"));
|
|
8873
9357
|
for (const hook of hooks) {
|
|
8874
9358
|
console.log(`OK: hook ${hook}`);
|