@h-rig/cli 0.0.6-alpha.2 → 0.0.6-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +1288 -315
- package/dist/src/commands/_authority-runs.js +1 -0
- package/dist/src/commands/_cli-format.js +106 -0
- package/dist/src/commands/_doctor-checks.js +10 -22
- package/dist/src/commands/_operator-surface.js +204 -0
- package/dist/src/commands/_operator-view.js +207 -51
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-session.js +253 -0
- package/dist/src/commands/_preflight.js +33 -28
- package/dist/src/commands/_server-client.js +80 -27
- package/dist/src/commands/_snapshot-upload.js +7 -20
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +2 -0
- package/dist/src/commands/doctor.js +10 -22
- package/dist/src/commands/github.js +9 -22
- package/dist/src/commands/init.js +295 -66
- package/dist/src/commands/queue.js +1 -0
- package/dist/src/commands/run.js +456 -95
- package/dist/src/commands/server.js +9 -22
- package/dist/src/commands/setup.js +10 -22
- package/dist/src/commands/task-run-driver.js +539 -64
- package/dist/src/commands/task.js +502 -130
- package/dist/src/commands.js +1276 -306
- package/dist/src/index.js +1288 -315
- package/dist/src/launcher.js +5 -3
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/task-run-driver.ts
|
|
3
|
-
import { copyFileSync, existsSync as
|
|
4
|
-
import { resolve as
|
|
3
|
+
import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4
|
+
import { resolve as resolve6 } from "path";
|
|
5
5
|
import { spawn, spawnSync } from "child_process";
|
|
6
6
|
import { createInterface as createLineInterface } from "readline";
|
|
7
7
|
|
|
@@ -39,10 +39,9 @@ import {
|
|
|
39
39
|
isCodexExecRecord
|
|
40
40
|
} from "@rig/runtime/control-plane/provider/codex-exec-records";
|
|
41
41
|
import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
|
|
42
|
-
import { readAuthorityRun as readAuthorityRun3, readJsonFile, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
|
|
42
|
+
import { readAuthorityRun as readAuthorityRun3, readJsonFile as readJsonFile2, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
|
|
43
43
|
import {
|
|
44
|
-
buildTaskRunLifecycleComment
|
|
45
|
-
updateConfiguredTaskSourceTask
|
|
44
|
+
buildTaskRunLifecycleComment
|
|
46
45
|
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
47
46
|
import {
|
|
48
47
|
closeIssueAfterMergedPr,
|
|
@@ -135,6 +134,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
|
|
|
135
134
|
const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
|
|
136
135
|
const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
|
|
137
136
|
const next = {
|
|
137
|
+
...existing ?? {},
|
|
138
138
|
runId: input.runId,
|
|
139
139
|
projectRoot,
|
|
140
140
|
workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
|
|
@@ -387,6 +387,194 @@ function renderSourceScopeValidation(task, validation) {
|
|
|
387
387
|
`);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
// packages/cli/src/commands/_server-client.ts
|
|
391
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
392
|
+
import { resolve as resolve5 } from "path";
|
|
393
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
394
|
+
|
|
395
|
+
// packages/cli/src/commands/_connection-state.ts
|
|
396
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
397
|
+
import { homedir } from "os";
|
|
398
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
399
|
+
function resolveGlobalConnectionsPath(env = process.env) {
|
|
400
|
+
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
401
|
+
if (explicit)
|
|
402
|
+
return resolve4(explicit);
|
|
403
|
+
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
404
|
+
if (stateDir)
|
|
405
|
+
return resolve4(stateDir, "connections.json");
|
|
406
|
+
return resolve4(homedir(), ".rig", "connections.json");
|
|
407
|
+
}
|
|
408
|
+
function resolveRepoConnectionPath(projectRoot) {
|
|
409
|
+
return resolve4(projectRoot, ".rig", "state", "connection.json");
|
|
410
|
+
}
|
|
411
|
+
function readJsonFile(path) {
|
|
412
|
+
if (!existsSync2(path))
|
|
413
|
+
return null;
|
|
414
|
+
try {
|
|
415
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function normalizeConnection(value) {
|
|
421
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
422
|
+
return null;
|
|
423
|
+
const record = value;
|
|
424
|
+
if (record.kind === "local")
|
|
425
|
+
return { kind: "local", mode: "auto" };
|
|
426
|
+
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
427
|
+
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
428
|
+
return { kind: "remote", baseUrl };
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
function readGlobalConnections(options = {}) {
|
|
433
|
+
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
434
|
+
const payload = readJsonFile(path);
|
|
435
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
436
|
+
return { connections: {} };
|
|
437
|
+
}
|
|
438
|
+
const rawConnections = payload.connections;
|
|
439
|
+
const connections = {};
|
|
440
|
+
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
441
|
+
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
442
|
+
const connection = normalizeConnection(raw);
|
|
443
|
+
if (connection)
|
|
444
|
+
connections[alias] = connection;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { connections };
|
|
448
|
+
}
|
|
449
|
+
function readRepoConnection(projectRoot) {
|
|
450
|
+
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
451
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
452
|
+
return null;
|
|
453
|
+
const record = payload;
|
|
454
|
+
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
455
|
+
if (!selected)
|
|
456
|
+
return null;
|
|
457
|
+
return {
|
|
458
|
+
selected,
|
|
459
|
+
project: typeof record.project === "string" ? record.project : undefined,
|
|
460
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
464
|
+
const repo = readRepoConnection(projectRoot);
|
|
465
|
+
if (!repo)
|
|
466
|
+
return null;
|
|
467
|
+
if (repo.selected === "local")
|
|
468
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
469
|
+
const global = readGlobalConnections(options);
|
|
470
|
+
const connection = global.connections[repo.selected];
|
|
471
|
+
if (!connection) {
|
|
472
|
+
throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
|
|
473
|
+
}
|
|
474
|
+
return { alias: repo.selected, connection };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// packages/cli/src/commands/_server-client.ts
|
|
478
|
+
var scopedGitHubBearerTokens = new Map;
|
|
479
|
+
function cleanToken(value) {
|
|
480
|
+
const trimmed = value?.trim();
|
|
481
|
+
return trimmed ? trimmed : null;
|
|
482
|
+
}
|
|
483
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
484
|
+
const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
|
|
485
|
+
if (!existsSync3(path))
|
|
486
|
+
return null;
|
|
487
|
+
try {
|
|
488
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
489
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
495
|
+
const scopedKey = resolve5(projectRoot);
|
|
496
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
497
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
498
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
499
|
+
if (privateSession)
|
|
500
|
+
return privateSession;
|
|
501
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
502
|
+
}
|
|
503
|
+
async function ensureServerForCli(projectRoot) {
|
|
504
|
+
try {
|
|
505
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
506
|
+
if (selected?.connection.kind === "remote") {
|
|
507
|
+
return {
|
|
508
|
+
baseUrl: selected.connection.baseUrl,
|
|
509
|
+
authToken: readGitHubBearerTokenForRemote(projectRoot),
|
|
510
|
+
connectionKind: "remote"
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
514
|
+
return {
|
|
515
|
+
baseUrl: connection.baseUrl,
|
|
516
|
+
authToken: connection.authToken,
|
|
517
|
+
connectionKind: "local"
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
if (error instanceof Error) {
|
|
521
|
+
throw new CliError2(error.message, 1);
|
|
522
|
+
}
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function mergeHeaders(headers, authToken) {
|
|
527
|
+
const merged = new Headers(headers);
|
|
528
|
+
if (authToken) {
|
|
529
|
+
merged.set("authorization", `Bearer ${authToken}`);
|
|
530
|
+
}
|
|
531
|
+
return merged;
|
|
532
|
+
}
|
|
533
|
+
function diagnosticMessage(payload) {
|
|
534
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
535
|
+
return null;
|
|
536
|
+
const record = payload;
|
|
537
|
+
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
538
|
+
const messages = diagnostics.flatMap((entry) => {
|
|
539
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
540
|
+
return [];
|
|
541
|
+
const diagnostic = entry;
|
|
542
|
+
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
543
|
+
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
544
|
+
return message ? [`${kind}: ${message}`] : [];
|
|
545
|
+
});
|
|
546
|
+
return messages.length > 0 ? messages.join("; ") : null;
|
|
547
|
+
}
|
|
548
|
+
async function requestServerJson(context, pathname, init = {}) {
|
|
549
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
550
|
+
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
551
|
+
...init,
|
|
552
|
+
headers: mergeHeaders(init.headers, server.authToken)
|
|
553
|
+
});
|
|
554
|
+
const text = await response.text();
|
|
555
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
556
|
+
try {
|
|
557
|
+
return JSON.parse(text);
|
|
558
|
+
} catch {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
})() : null;
|
|
562
|
+
if (!response.ok) {
|
|
563
|
+
const diagnostics = diagnosticMessage(payload);
|
|
564
|
+
const detail = diagnostics ?? (text || response.statusText);
|
|
565
|
+
throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
|
|
566
|
+
}
|
|
567
|
+
return payload;
|
|
568
|
+
}
|
|
569
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
570
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
571
|
+
method: "POST",
|
|
572
|
+
headers: { "content-type": "application/json" },
|
|
573
|
+
body: JSON.stringify(input)
|
|
574
|
+
});
|
|
575
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
576
|
+
}
|
|
577
|
+
|
|
390
578
|
// packages/cli/src/commands/task-run-driver.ts
|
|
391
579
|
var PI_CANONICAL_RUN_STAGES = [
|
|
392
580
|
"Connect",
|
|
@@ -405,7 +593,24 @@ var PI_CANONICAL_RUN_STAGES = [
|
|
|
405
593
|
function canonicalPiRunStages() {
|
|
406
594
|
return [...PI_CANONICAL_RUN_STAGES];
|
|
407
595
|
}
|
|
596
|
+
function looksLikeGitHubToken(value) {
|
|
597
|
+
const token = value?.trim();
|
|
598
|
+
if (!token)
|
|
599
|
+
return false;
|
|
600
|
+
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
601
|
+
}
|
|
602
|
+
function githubBridgeEnv(token) {
|
|
603
|
+
const clean = token?.trim();
|
|
604
|
+
if (!clean)
|
|
605
|
+
return {};
|
|
606
|
+
return {
|
|
607
|
+
RIG_GITHUB_TOKEN: clean,
|
|
608
|
+
GITHUB_TOKEN: clean,
|
|
609
|
+
GH_TOKEN: clean
|
|
610
|
+
};
|
|
611
|
+
}
|
|
408
612
|
function buildPiRigBridgeEnv(input) {
|
|
613
|
+
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
409
614
|
return {
|
|
410
615
|
RIG_PROJECT_ROOT: input.projectRoot,
|
|
411
616
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
@@ -415,7 +620,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
415
620
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
416
621
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
417
622
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
418
|
-
...
|
|
623
|
+
...githubBridgeEnv(githubToken)
|
|
419
624
|
};
|
|
420
625
|
}
|
|
421
626
|
function runGitSync(cwd, args, input) {
|
|
@@ -437,12 +642,12 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
437
642
|
return 0;
|
|
438
643
|
let copied = 0;
|
|
439
644
|
for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
|
|
440
|
-
const sourcePath =
|
|
441
|
-
const targetPath =
|
|
645
|
+
const sourcePath = resolve6(sourceRoot, relativePath);
|
|
646
|
+
const targetPath = resolve6(targetRoot, relativePath);
|
|
442
647
|
try {
|
|
443
648
|
if (!statSync(sourcePath).isFile())
|
|
444
649
|
continue;
|
|
445
|
-
|
|
650
|
+
mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
|
|
446
651
|
copyFileSync(sourcePath, targetPath);
|
|
447
652
|
copied += 1;
|
|
448
653
|
} catch {}
|
|
@@ -481,7 +686,7 @@ function buildDirtyBaselineHandshakeEnv(input) {
|
|
|
481
686
|
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
482
687
|
return {
|
|
483
688
|
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
484
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
689
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
485
690
|
};
|
|
486
691
|
}
|
|
487
692
|
function positiveInt(value, fallback) {
|
|
@@ -489,7 +694,7 @@ function positiveInt(value, fallback) {
|
|
|
489
694
|
}
|
|
490
695
|
function resolveTaskRunAutomationLimits(config, env = process.env) {
|
|
491
696
|
const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
|
|
492
|
-
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations,
|
|
697
|
+
const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
|
|
493
698
|
return {
|
|
494
699
|
maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
|
|
495
700
|
maxPrFixIterations: configuredPrFixIterations
|
|
@@ -599,9 +804,9 @@ function createCommandRunner(binary) {
|
|
|
599
804
|
const stderrChunks = [];
|
|
600
805
|
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
601
806
|
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
602
|
-
return await new Promise((
|
|
603
|
-
child.once("error", (error) =>
|
|
604
|
-
child.once("close", (code) =>
|
|
807
|
+
return await new Promise((resolve7) => {
|
|
808
|
+
child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
|
|
809
|
+
child.once("close", (code) => resolve7({
|
|
605
810
|
exitCode: code ?? 1,
|
|
606
811
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
607
812
|
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
@@ -625,8 +830,9 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
625
830
|
if (!taskId) {
|
|
626
831
|
return { status: "skipped" };
|
|
627
832
|
}
|
|
628
|
-
const
|
|
629
|
-
const
|
|
833
|
+
const configInput = input.config ?? null;
|
|
834
|
+
const config = configInput ?? {};
|
|
835
|
+
const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
|
|
630
836
|
if (prMode === "off" || prMode === "ask") {
|
|
631
837
|
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
632
838
|
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
@@ -642,7 +848,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
642
848
|
gitCommand
|
|
643
849
|
});
|
|
644
850
|
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
645
|
-
const updateTaskSource = input.updateTaskSource ??
|
|
851
|
+
const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
|
|
646
852
|
const stage = input.appendStage ?? (() => {
|
|
647
853
|
return;
|
|
648
854
|
});
|
|
@@ -674,7 +880,9 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
674
880
|
config,
|
|
675
881
|
sourceTask: input.sourceTask,
|
|
676
882
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
883
|
+
artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
|
|
677
884
|
command: ghCommand,
|
|
885
|
+
gitCommand,
|
|
678
886
|
steerPi,
|
|
679
887
|
lifecycle: {
|
|
680
888
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -723,8 +931,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
723
931
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
724
932
|
})
|
|
725
933
|
}
|
|
726
|
-
}).catch(() => {
|
|
727
|
-
return;
|
|
728
934
|
});
|
|
729
935
|
}
|
|
730
936
|
},
|
|
@@ -743,8 +949,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
743
949
|
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
744
950
|
})
|
|
745
951
|
}
|
|
746
|
-
}).catch(() => {
|
|
747
|
-
return;
|
|
748
952
|
});
|
|
749
953
|
}
|
|
750
954
|
},
|
|
@@ -773,8 +977,17 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
773
977
|
errorText: detail
|
|
774
978
|
})
|
|
775
979
|
}
|
|
776
|
-
}).catch(() => {
|
|
777
|
-
|
|
980
|
+
}).catch((error) => {
|
|
981
|
+
try {
|
|
982
|
+
appendRunLog(input.projectRoot, input.runId, {
|
|
983
|
+
id: `log:${input.runId}:task-source-needs-attention-update`,
|
|
984
|
+
title: "Task source needs-attention update failed",
|
|
985
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
986
|
+
tone: "error",
|
|
987
|
+
status: "needs_attention",
|
|
988
|
+
createdAt: new Date().toISOString()
|
|
989
|
+
});
|
|
990
|
+
} catch {}
|
|
778
991
|
});
|
|
779
992
|
}
|
|
780
993
|
return { status: "needs_attention", pr };
|
|
@@ -797,7 +1010,7 @@ function summarizeValidationFailure(projectRoot, taskId) {
|
|
|
797
1010
|
return null;
|
|
798
1011
|
}
|
|
799
1012
|
for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
|
|
800
|
-
const summary =
|
|
1013
|
+
const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
|
|
801
1014
|
if (!summary || summary.status !== "fail") {
|
|
802
1015
|
continue;
|
|
803
1016
|
}
|
|
@@ -878,14 +1091,14 @@ function readTaskRunAcceptedArtifactState(input) {
|
|
|
878
1091
|
if (!input.taskId || !input.workspaceDir) {
|
|
879
1092
|
return { accepted: false, reason: null };
|
|
880
1093
|
}
|
|
881
|
-
const artifactDir =
|
|
882
|
-
const reviewStatusPath =
|
|
883
|
-
const taskResultPath =
|
|
1094
|
+
const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
|
|
1095
|
+
const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
|
|
1096
|
+
const taskResultPath = resolve6(artifactDir, "task-result.json");
|
|
884
1097
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
885
1098
|
if (reviewStatus !== "APPROVED") {
|
|
886
1099
|
return { accepted: false, reason: null };
|
|
887
1100
|
}
|
|
888
|
-
const taskResult =
|
|
1101
|
+
const taskResult = readJsonFile2(taskResultPath, null);
|
|
889
1102
|
if (taskResult?.status !== "completed") {
|
|
890
1103
|
return { accepted: false, reason: null };
|
|
891
1104
|
}
|
|
@@ -917,13 +1130,13 @@ function resolveTaskRunRetryContext(input) {
|
|
|
917
1130
|
if (!input.taskId || !input.workspaceDir) {
|
|
918
1131
|
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
919
1132
|
}
|
|
920
|
-
const artifactDir =
|
|
921
|
-
const reviewStatePath =
|
|
922
|
-
const reviewFeedbackPath =
|
|
923
|
-
const reviewStatusPath =
|
|
924
|
-
const failedApproachesPath =
|
|
925
|
-
const validationSummaryPath =
|
|
926
|
-
const reviewState =
|
|
1133
|
+
const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
|
|
1134
|
+
const reviewStatePath = resolve6(artifactDir, "review-state.json");
|
|
1135
|
+
const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
|
|
1136
|
+
const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
|
|
1137
|
+
const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
1138
|
+
const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
|
|
1139
|
+
const reviewState = readJsonFile2(reviewStatePath, null);
|
|
927
1140
|
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
928
1141
|
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
929
1142
|
if (reviewStatus === "APPROVED") {
|
|
@@ -976,12 +1189,80 @@ function summarizeTaskRunReviewFailure(reviewState) {
|
|
|
976
1189
|
}
|
|
977
1190
|
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
978
1191
|
}
|
|
1192
|
+
function appendAssistantTimelineFromRecord(input) {
|
|
1193
|
+
let nextAssistantText = input.assistantText;
|
|
1194
|
+
if (input.record.type === "message_update") {
|
|
1195
|
+
const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
|
|
1196
|
+
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
1197
|
+
nextAssistantText += assistantMessageEvent.delta;
|
|
1198
|
+
}
|
|
1199
|
+
} else if (input.record.type === "stream_event") {
|
|
1200
|
+
const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
|
|
1201
|
+
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
1202
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
1203
|
+
nextAssistantText += delta.text;
|
|
1204
|
+
}
|
|
1205
|
+
} else if (input.record.type === "assistant") {
|
|
1206
|
+
const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
|
|
1207
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
1208
|
+
const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
1209
|
+
if (fullText.length > nextAssistantText.length)
|
|
1210
|
+
nextAssistantText = fullText;
|
|
1211
|
+
}
|
|
1212
|
+
if (nextAssistantText !== input.assistantText) {
|
|
1213
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
1214
|
+
id: input.messageId,
|
|
1215
|
+
type: "assistant_message",
|
|
1216
|
+
text: nextAssistantText,
|
|
1217
|
+
state: "streaming",
|
|
1218
|
+
createdAt: new Date().toISOString()
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
return nextAssistantText;
|
|
1222
|
+
}
|
|
1223
|
+
function appendPiToolTimelineFromRecord(input) {
|
|
1224
|
+
const type = typeof input.record.type === "string" ? input.record.type : "";
|
|
1225
|
+
if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
|
|
1226
|
+
return false;
|
|
1227
|
+
const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
|
|
1228
|
+
const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
|
|
1229
|
+
const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
|
|
1230
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
1231
|
+
id: `tool:${toolCallId}:${type}`,
|
|
1232
|
+
type,
|
|
1233
|
+
toolName,
|
|
1234
|
+
status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
|
|
1235
|
+
createdAt: new Date().toISOString()
|
|
1236
|
+
});
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
function isNonRenderablePiProtocolRecord(record) {
|
|
1240
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
1241
|
+
return type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
|
|
1242
|
+
}
|
|
1243
|
+
function appendToolTimelineFromLog(input) {
|
|
1244
|
+
const title = typeof input.log.title === "string" ? input.log.title : "";
|
|
1245
|
+
if (title !== "Tool activity")
|
|
1246
|
+
return;
|
|
1247
|
+
const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
|
|
1248
|
+
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;
|
|
1249
|
+
const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
|
|
1250
|
+
appendRunTimeline(input.projectRoot, input.runId, {
|
|
1251
|
+
id: `tool:${logId}`,
|
|
1252
|
+
type: "tool_execution_update",
|
|
1253
|
+
toolName,
|
|
1254
|
+
status: typeof input.log.status === "string" ? input.log.status : "running",
|
|
1255
|
+
detail: typeof input.log.detail === "string" ? input.log.detail : null,
|
|
1256
|
+
payload,
|
|
1257
|
+
createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
979
1260
|
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
980
|
-
if (!
|
|
1261
|
+
if (!existsSync4(reviewStatusPath)) {
|
|
981
1262
|
return null;
|
|
982
1263
|
}
|
|
983
1264
|
try {
|
|
984
|
-
const status =
|
|
1265
|
+
const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
|
|
985
1266
|
return status === "APPROVED" || status === "REJECTED" ? status : null;
|
|
986
1267
|
} catch {
|
|
987
1268
|
return null;
|
|
@@ -999,7 +1280,38 @@ function isTaskRunReviewRejected(reviewState) {
|
|
|
999
1280
|
function runSourceTaskIdentity(sourceTask) {
|
|
1000
1281
|
return sourceTask;
|
|
1001
1282
|
}
|
|
1002
|
-
|
|
1283
|
+
function sourceTaskIssueNodeId(sourceTask) {
|
|
1284
|
+
if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
|
|
1285
|
+
return null;
|
|
1286
|
+
const record = sourceTask;
|
|
1287
|
+
const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
|
|
1288
|
+
if (direct?.trim())
|
|
1289
|
+
return direct.trim();
|
|
1290
|
+
const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
|
|
1291
|
+
return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
|
|
1292
|
+
}
|
|
1293
|
+
var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
|
|
1294
|
+
const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
|
|
1295
|
+
id: input.taskId,
|
|
1296
|
+
...input.update.status ? { status: input.update.status } : {},
|
|
1297
|
+
...input.update.comment ? { comment: input.update.comment } : {},
|
|
1298
|
+
...input.update.title ? { title: input.update.title } : {},
|
|
1299
|
+
...typeof input.update.body === "string" ? { body: input.update.body } : {},
|
|
1300
|
+
issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
|
|
1301
|
+
});
|
|
1302
|
+
if (serverResult.ok === false) {
|
|
1303
|
+
throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
updated: serverResult.ok !== false,
|
|
1307
|
+
taskId: input.taskId,
|
|
1308
|
+
status: input.update.status,
|
|
1309
|
+
source: "server",
|
|
1310
|
+
sourceKind: "server",
|
|
1311
|
+
projectSync: serverResult.projectSync
|
|
1312
|
+
};
|
|
1313
|
+
};
|
|
1314
|
+
async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
|
|
1003
1315
|
if (!taskId)
|
|
1004
1316
|
return;
|
|
1005
1317
|
const config = await loadTaskRunAutomationConfig(projectRoot);
|
|
@@ -1064,6 +1376,9 @@ function stringArrayField(record, key) {
|
|
|
1064
1376
|
}
|
|
1065
1377
|
async function executeRigOwnedTaskRun(context, input) {
|
|
1066
1378
|
const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
|
|
1379
|
+
const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
|
|
1380
|
+
const resumeMode = process.env.RIG_RUN_RESUME === "1";
|
|
1381
|
+
const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
|
|
1067
1382
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
1068
1383
|
let prompt = buildRunPrompt({
|
|
1069
1384
|
projectRoot: context.projectRoot,
|
|
@@ -1119,14 +1434,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1119
1434
|
taskId: runtimeTaskId,
|
|
1120
1435
|
createdAt: startedAt,
|
|
1121
1436
|
runtimeAdapter: input.runtimeAdapter,
|
|
1122
|
-
status: "created"
|
|
1437
|
+
status: resumeMode ? "preparing" : "created"
|
|
1123
1438
|
});
|
|
1124
1439
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1125
1440
|
status: "preparing",
|
|
1126
1441
|
startedAt,
|
|
1127
1442
|
completedAt: null,
|
|
1128
1443
|
errorText: null,
|
|
1129
|
-
artifactRoot: null,
|
|
1444
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
1130
1445
|
runtimeAdapter: input.runtimeAdapter,
|
|
1131
1446
|
runtimeMode: input.runtimeMode,
|
|
1132
1447
|
interactionMode: input.interactionMode,
|
|
@@ -1142,9 +1457,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1142
1457
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
1143
1458
|
});
|
|
1144
1459
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1145
|
-
id: `log:${input.runId}:start`,
|
|
1146
|
-
title: "Rig task run started",
|
|
1147
|
-
detail: input.taskId ?? input.title ?? runtimeTaskId,
|
|
1460
|
+
id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
|
|
1461
|
+
title: resumeMode ? "Rig task run resumed" : "Rig task run started",
|
|
1462
|
+
detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
|
|
1148
1463
|
tone: "info",
|
|
1149
1464
|
status: "preparing",
|
|
1150
1465
|
createdAt: startedAt
|
|
@@ -1165,15 +1480,15 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1165
1480
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
1166
1481
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
1167
1482
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
1168
|
-
const planningArtifactPath =
|
|
1483
|
+
const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
1169
1484
|
const persistedPlanning = {
|
|
1170
1485
|
...planningClassification,
|
|
1171
1486
|
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
1172
1487
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
1173
1488
|
classifiedAt: new Date().toISOString()
|
|
1174
1489
|
};
|
|
1175
|
-
|
|
1176
|
-
|
|
1490
|
+
mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
1491
|
+
writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
1177
1492
|
`, "utf8");
|
|
1178
1493
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
1179
1494
|
prompt = `${prompt}
|
|
@@ -1222,11 +1537,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1222
1537
|
let reviewAction;
|
|
1223
1538
|
let verificationStarted = false;
|
|
1224
1539
|
let reviewStarted = false;
|
|
1225
|
-
let latestRuntimeWorkspace = null;
|
|
1226
|
-
let latestSessionDir = null;
|
|
1227
|
-
let latestLogsDir = null;
|
|
1540
|
+
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
1541
|
+
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
|
|
1542
|
+
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
1228
1543
|
let latestProviderCommand = null;
|
|
1229
|
-
let latestRuntimeBranch = null;
|
|
1544
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
1230
1545
|
let snapshotSidecarPromise = null;
|
|
1231
1546
|
let dirtyBaselineApplied = false;
|
|
1232
1547
|
const childEnv = {
|
|
@@ -1244,7 +1559,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1244
1559
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
1245
1560
|
RIG_SERVER_RUN_ID: input.runId
|
|
1246
1561
|
},
|
|
1247
|
-
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
|
|
1562
|
+
...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
|
|
1563
|
+
...resumeMode ? {
|
|
1564
|
+
RIG_RUN_RESUME: "1",
|
|
1565
|
+
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
1566
|
+
} : {}
|
|
1248
1567
|
};
|
|
1249
1568
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
1250
1569
|
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
@@ -1302,10 +1621,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1302
1621
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1303
1622
|
status: "running",
|
|
1304
1623
|
worktreePath: latestRuntimeWorkspace,
|
|
1305
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ?
|
|
1624
|
+
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
1306
1625
|
logRoot: latestLogsDir,
|
|
1307
|
-
sessionPath: latestSessionDir ?
|
|
1308
|
-
sessionLogPath: latestLogsDir ?
|
|
1626
|
+
sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
|
|
1627
|
+
sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
|
|
1309
1628
|
branch: runtimeId
|
|
1310
1629
|
});
|
|
1311
1630
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
@@ -1313,8 +1632,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1313
1632
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
1314
1633
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
1315
1634
|
if (readyFile) {
|
|
1316
|
-
|
|
1317
|
-
|
|
1635
|
+
mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
|
|
1636
|
+
writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
1318
1637
|
`, "utf8");
|
|
1319
1638
|
}
|
|
1320
1639
|
appendRunLog(context.projectRoot, input.runId, {
|
|
@@ -1446,6 +1765,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1446
1765
|
try {
|
|
1447
1766
|
const record = JSON.parse(trimmed);
|
|
1448
1767
|
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
1768
|
+
if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
|
|
1769
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1449
1772
|
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
1450
1773
|
runId: input.runId,
|
|
1451
1774
|
record,
|
|
@@ -1462,7 +1785,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1462
1785
|
if (providerLogs.length > 0) {
|
|
1463
1786
|
for (const providerLog of providerLogs) {
|
|
1464
1787
|
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
1788
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
1465
1789
|
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
1790
|
+
if (providerLog.title === "Tool activity")
|
|
1791
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1466
1792
|
}
|
|
1467
1793
|
}
|
|
1468
1794
|
if (input.runtimeAdapter === "codex") {
|
|
@@ -1514,6 +1840,9 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1514
1840
|
return;
|
|
1515
1841
|
}
|
|
1516
1842
|
}
|
|
1843
|
+
if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1517
1846
|
if (record.type === "assistant") {
|
|
1518
1847
|
const message = record.message && typeof record.message === "object" ? record.message : record;
|
|
1519
1848
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
@@ -1550,7 +1879,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1550
1879
|
let reviewFailureDetail = null;
|
|
1551
1880
|
const stderrLines = [];
|
|
1552
1881
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
1553
|
-
|
|
1882
|
+
if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
|
|
1883
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
1884
|
+
id: `log:${input.runId}:resume-closeout-phase`,
|
|
1885
|
+
title: "Resume continuing from closeout phase",
|
|
1886
|
+
detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
|
|
1887
|
+
tone: "info",
|
|
1888
|
+
status: resumePreviousStatus,
|
|
1889
|
+
createdAt: new Date().toISOString()
|
|
1890
|
+
});
|
|
1891
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
|
|
1892
|
+
exit = { code: 0, signal: null };
|
|
1893
|
+
} else if (resumeMode && latestRuntimeWorkspace) {
|
|
1894
|
+
const acceptedArtifactState = readTaskRunAcceptedArtifactState({
|
|
1895
|
+
taskId: input.taskId ?? runtimeTaskId,
|
|
1896
|
+
workspaceDir: latestRuntimeWorkspace
|
|
1897
|
+
});
|
|
1898
|
+
if (acceptedArtifactState.accepted) {
|
|
1899
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
1900
|
+
id: `log:${input.runId}:resume-accepted-artifacts`,
|
|
1901
|
+
title: "Resume found accepted artifacts; continuing closeout",
|
|
1902
|
+
detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
|
|
1903
|
+
tone: "info",
|
|
1904
|
+
status: "validating",
|
|
1905
|
+
createdAt: new Date().toISOString()
|
|
1906
|
+
});
|
|
1907
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
|
|
1908
|
+
exit = { code: 0, signal: null };
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
|
|
1554
1912
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
1555
1913
|
const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
1556
1914
|
cwd: context.projectRoot,
|
|
@@ -1591,7 +1949,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1591
1949
|
let acceptedArtifactObservedAt = null;
|
|
1592
1950
|
let acceptedArtifactPollTimer = null;
|
|
1593
1951
|
let acceptedArtifactKillTimer = null;
|
|
1594
|
-
const attemptExit = await new Promise((
|
|
1952
|
+
const attemptExit = await new Promise((resolve7) => {
|
|
1595
1953
|
let settled = false;
|
|
1596
1954
|
const settle = (result) => {
|
|
1597
1955
|
if (settled)
|
|
@@ -1599,7 +1957,7 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1599
1957
|
settled = true;
|
|
1600
1958
|
if (acceptedArtifactPollTimer)
|
|
1601
1959
|
clearInterval(acceptedArtifactPollTimer);
|
|
1602
|
-
|
|
1960
|
+
resolve7(result);
|
|
1603
1961
|
};
|
|
1604
1962
|
const pollAcceptedArtifacts = () => {
|
|
1605
1963
|
const artifactState = readTaskRunAcceptedArtifactState({
|
|
@@ -1672,7 +2030,10 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
1672
2030
|
});
|
|
1673
2031
|
for (const pendingLog of pendingLogs) {
|
|
1674
2032
|
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
2033
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
1675
2034
|
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
2035
|
+
if (pendingLog.title === "Tool activity")
|
|
2036
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1676
2037
|
}
|
|
1677
2038
|
process.off("SIGTERM", forwardSigterm);
|
|
1678
2039
|
if (attemptExit.error) {
|
|
@@ -1798,8 +2159,8 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1798
2159
|
}
|
|
1799
2160
|
if (planningClassification.planningRequired) {
|
|
1800
2161
|
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
1801
|
-
const expectedPlanPath =
|
|
1802
|
-
if (!
|
|
2162
|
+
const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
|
|
2163
|
+
if (!existsSync4(expectedPlanPath)) {
|
|
1803
2164
|
const failedAt = new Date().toISOString();
|
|
1804
2165
|
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
1805
2166
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
@@ -1819,6 +2180,65 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1819
2180
|
throw new CliError2(failureDetail, 1);
|
|
1820
2181
|
}
|
|
1821
2182
|
}
|
|
2183
|
+
if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
|
|
2184
|
+
appendPiStageLog({
|
|
2185
|
+
projectRoot: context.projectRoot,
|
|
2186
|
+
runId: input.runId,
|
|
2187
|
+
stage: "Validate",
|
|
2188
|
+
detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
|
|
2189
|
+
status: "completed"
|
|
2190
|
+
});
|
|
2191
|
+
if (verificationAction && !reviewStarted) {
|
|
2192
|
+
verificationAction.complete("Completion verification checks finished.");
|
|
2193
|
+
}
|
|
2194
|
+
if (!reviewAction) {
|
|
2195
|
+
promoteToReviewing("Server-owned closeout is queued.");
|
|
2196
|
+
}
|
|
2197
|
+
if (reviewAction) {
|
|
2198
|
+
reviewAction.complete("Provider work accepted; server-owned closeout requested.");
|
|
2199
|
+
}
|
|
2200
|
+
const requestedAt = new Date().toISOString();
|
|
2201
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
2202
|
+
status: "reviewing",
|
|
2203
|
+
completedAt: null,
|
|
2204
|
+
errorText: null,
|
|
2205
|
+
serverCloseout: {
|
|
2206
|
+
status: "pending",
|
|
2207
|
+
phase: "queued",
|
|
2208
|
+
requestedAt,
|
|
2209
|
+
updatedAt: requestedAt,
|
|
2210
|
+
runtimeWorkspace: latestRuntimeWorkspace,
|
|
2211
|
+
branch: latestRuntimeBranch,
|
|
2212
|
+
taskId: input.taskId ?? runtimeTaskId
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
2216
|
+
id: `log:${input.runId}:server-closeout-requested`,
|
|
2217
|
+
title: "Server-owned closeout requested",
|
|
2218
|
+
detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
|
|
2219
|
+
tone: "info",
|
|
2220
|
+
status: "reviewing",
|
|
2221
|
+
createdAt: requestedAt,
|
|
2222
|
+
payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
|
|
2223
|
+
});
|
|
2224
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
|
|
2225
|
+
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: "Server-owned closeout requested." });
|
|
2226
|
+
await context.emitEvent("command.finished", {
|
|
2227
|
+
command: [
|
|
2228
|
+
"rig",
|
|
2229
|
+
"server",
|
|
2230
|
+
"task-run",
|
|
2231
|
+
...input.taskId ? ["--task", input.taskId] : [],
|
|
2232
|
+
...input.title ? ["--title", input.title] : []
|
|
2233
|
+
],
|
|
2234
|
+
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
2235
|
+
exitCode: 0,
|
|
2236
|
+
durationMs: 0,
|
|
2237
|
+
startedAt,
|
|
2238
|
+
finishedAt: requestedAt
|
|
2239
|
+
});
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
1822
2242
|
const runPiPrFeedbackFix = async (message) => {
|
|
1823
2243
|
appendPiStageLog({
|
|
1824
2244
|
projectRoot: context.projectRoot,
|
|
@@ -1846,11 +2266,45 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1846
2266
|
child.stdin.write(message);
|
|
1847
2267
|
}
|
|
1848
2268
|
child.stdin.end();
|
|
2269
|
+
const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
|
|
2270
|
+
let feedbackAssistantText = "";
|
|
2271
|
+
const feedbackPendingToolUses = new Map;
|
|
1849
2272
|
const stdout = createLineInterface({ input: child.stdout });
|
|
1850
2273
|
stdout.on("line", (line) => {
|
|
1851
2274
|
const trimmed = line.trim();
|
|
1852
2275
|
if (!trimmed)
|
|
1853
2276
|
return;
|
|
2277
|
+
try {
|
|
2278
|
+
const record = JSON.parse(trimmed);
|
|
2279
|
+
const providerLogs = buildClaudeLogsFromRecord({
|
|
2280
|
+
runId: input.runId,
|
|
2281
|
+
record,
|
|
2282
|
+
createdAtFallback: new Date().toISOString(),
|
|
2283
|
+
status: "reviewing",
|
|
2284
|
+
pendingToolUses: feedbackPendingToolUses
|
|
2285
|
+
});
|
|
2286
|
+
for (const providerLog of providerLogs) {
|
|
2287
|
+
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
2288
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
|
|
2289
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
2290
|
+
if (providerLog.title === "Tool activity")
|
|
2291
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
2292
|
+
}
|
|
2293
|
+
const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
|
|
2294
|
+
projectRoot: context.projectRoot,
|
|
2295
|
+
runId: input.runId,
|
|
2296
|
+
messageId: feedbackAssistantMessageId,
|
|
2297
|
+
record,
|
|
2298
|
+
assistantText: feedbackAssistantText
|
|
2299
|
+
});
|
|
2300
|
+
const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
|
|
2301
|
+
if (hadAssistantDelta) {
|
|
2302
|
+
feedbackAssistantText = nextFeedbackAssistantText;
|
|
2303
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
2304
|
+
}
|
|
2305
|
+
if (providerLogs.length > 0 || hadAssistantDelta)
|
|
2306
|
+
return;
|
|
2307
|
+
} catch {}
|
|
1854
2308
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1855
2309
|
id: nextRunLogId(),
|
|
1856
2310
|
title: "Pi PR feedback fix output",
|
|
@@ -1876,10 +2330,31 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1876
2330
|
});
|
|
1877
2331
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
1878
2332
|
});
|
|
1879
|
-
const exitCode = await new Promise((
|
|
1880
|
-
child.once("error", () =>
|
|
1881
|
-
child.once("close", (code) =>
|
|
2333
|
+
const exitCode = await new Promise((resolve7) => {
|
|
2334
|
+
child.once("error", () => resolve7(1));
|
|
2335
|
+
child.once("close", (code) => resolve7(code ?? 1));
|
|
1882
2336
|
});
|
|
2337
|
+
for (const pendingLog of flushPendingClaudeToolUseLogs({
|
|
2338
|
+
runId: input.runId,
|
|
2339
|
+
status: exitCode === 0 ? "completed" : "failed",
|
|
2340
|
+
pendingToolUses: feedbackPendingToolUses
|
|
2341
|
+
})) {
|
|
2342
|
+
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
2343
|
+
appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
|
|
2344
|
+
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
2345
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
2346
|
+
}
|
|
2347
|
+
if (feedbackAssistantText.trim()) {
|
|
2348
|
+
appendRunTimeline(context.projectRoot, input.runId, {
|
|
2349
|
+
id: feedbackAssistantMessageId,
|
|
2350
|
+
type: "assistant_message",
|
|
2351
|
+
text: feedbackAssistantText,
|
|
2352
|
+
state: "completed",
|
|
2353
|
+
createdAt: new Date().toISOString(),
|
|
2354
|
+
completedAt: new Date().toISOString()
|
|
2355
|
+
});
|
|
2356
|
+
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
2357
|
+
}
|
|
1883
2358
|
if (exitCode !== 0) {
|
|
1884
2359
|
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
1885
2360
|
}
|