@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.91
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/README.md +18 -19
- package/dist/bin/build-rig-binaries.d.ts +2 -0
- package/dist/bin/build-rig-binaries.js +22 -10
- package/dist/bin/rig.d.ts +72 -0
- package/dist/bin/rig.js +15637 -7344
- package/dist/config/rig-default-config.yml +5 -0
- package/dist/src/app/drone-ui.d.ts +34 -0
- package/dist/src/app/drone-ui.js +278 -0
- package/dist/src/commands/_async-ui.d.ts +10 -0
- package/dist/src/commands/_async-ui.js +121 -0
- package/dist/src/commands/_cli-format.d.ts +56 -0
- package/dist/src/commands/_cli-format.js +319 -0
- package/dist/src/commands/_connection-state.d.ts +54 -0
- package/dist/src/commands/_connection-state.js +75 -11
- package/dist/src/commands/_doctor-checks.d.ts +46 -0
- package/dist/src/commands/_doctor-checks.js +100 -267
- package/dist/src/commands/_help-catalog.d.ts +51 -0
- package/dist/src/commands/_help-catalog.js +502 -0
- package/dist/src/commands/_inprocess-services.d.ts +33 -0
- package/dist/src/commands/_inprocess-services.js +102 -0
- package/dist/src/commands/_json-output.d.ts +11 -0
- package/dist/src/commands/_json-output.js +60 -0
- package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
- package/dist/src/commands/_lazy-reconcile.js +102 -0
- package/dist/src/commands/_parsers.d.ts +15 -0
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_paths.d.ts +11 -0
- package/dist/src/commands/_paths.js +1 -1
- package/dist/src/commands/_pi-frontend.d.ts +35 -0
- package/dist/src/commands/_pi-frontend.js +64 -0
- package/dist/src/commands/_pi-install.d.ts +42 -0
- package/dist/src/commands/_pi-install.js +19 -36
- package/dist/src/commands/_policy.d.ts +8 -0
- package/dist/src/commands/_policy.js +67 -19
- package/dist/src/commands/_probes.d.ts +1 -0
- package/dist/src/commands/_run-bridge.d.ts +114 -0
- package/dist/src/commands/_run-bridge.js +387 -0
- package/dist/src/commands/_run-diagnostics.d.ts +9 -0
- package/dist/src/commands/_run-diagnostics.js +51 -0
- package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
- package/dist/src/commands/_run-driver-helpers.js +79 -230
- package/dist/src/commands/_run-projection.d.ts +50 -0
- package/dist/src/commands/_run-projection.js +349 -0
- package/dist/src/commands/_run-subcommands.d.ts +3 -0
- package/dist/src/commands/_run-subcommands.js +31 -0
- package/dist/src/commands/_spinner.d.ts +25 -0
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/agent.d.ts +3 -0
- package/dist/src/commands/agent.js +8575 -236
- package/dist/src/commands/dist.d.ts +28 -0
- package/dist/src/commands/dist.js +44 -29
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +219 -268
- package/dist/src/commands/github.d.ts +3 -0
- package/dist/src/commands/github.js +236 -188
- package/dist/src/commands/inbox.d.ts +28 -0
- package/dist/src/commands/inbox.js +530 -111
- package/dist/src/commands/init.d.ts +64 -0
- package/dist/src/commands/init.js +912 -705
- package/dist/src/commands/inspect.d.ts +20 -0
- package/dist/src/commands/inspect.js +709 -119
- package/dist/src/commands/pi.d.ts +3 -0
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.d.ts +16 -0
- package/dist/src/commands/plugin.js +576 -29
- package/dist/src/commands/profile-and-review.d.ts +4 -0
- package/dist/src/commands/profile-and-review.js +112 -67
- package/dist/src/commands/queue.d.ts +3 -0
- package/dist/src/commands/queue.js +20 -20
- package/dist/src/commands/remote.d.ts +3 -0
- package/dist/src/commands/remote.js +880 -50
- package/dist/src/commands/repo-git-harness.d.ts +5 -0
- package/dist/src/commands/repo-git-harness.js +78 -28
- package/dist/src/commands/run.d.ts +21 -0
- package/dist/src/commands/run.js +17701 -661
- package/dist/src/commands/server.d.ts +3 -0
- package/dist/src/commands/server.js +166 -303
- package/dist/src/commands/setup.d.ts +16 -0
- package/dist/src/commands/setup.js +244 -299
- package/dist/src/commands/stats.d.ts +15 -0
- package/dist/src/commands/stats.js +979 -0
- package/dist/src/commands/task-run-driver.d.ts +94 -0
- package/dist/src/commands/task-run-driver.js +116 -1961
- package/dist/src/commands/task.d.ts +35 -0
- package/dist/src/commands/task.js +676 -1201
- package/dist/src/commands/test.d.ts +3 -0
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/triage.d.ts +11 -0
- package/dist/src/commands/triage.js +227 -0
- package/dist/src/commands/workspace.d.ts +3 -0
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.d.ts +13 -0
- package/dist/src/commands.js +16792 -8055
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +16862 -8450
- package/dist/src/launcher.d.ts +61 -0
- package/dist/src/launcher.js +81 -13
- package/dist/src/operator-cli.d.ts +2 -0
- package/dist/src/operator-cli.js +17837 -0
- package/dist/src/operator-entry.d.ts +1 -0
- package/dist/src/operator-entry.js +3 -0
- package/dist/src/runner.d.ts +47 -0
- package/dist/src/runner.js +16 -22
- package/dist/src/withMutedConsole.d.ts +2 -0
- package/package.json +21 -8
- package/dist/src/commands/_authority-runs.js +0 -111
- package/dist/src/commands/_operator-view.js +0 -340
- package/dist/src/commands/_preflight.js +0 -478
- package/dist/src/commands/_server-client.js +0 -382
- package/dist/src/commands/_snapshot-upload.js +0 -331
- package/dist/src/commands/_task-picker.js +0 -48
- package/dist/src/commands/browser.js +0 -890
- package/dist/src/commands/connect.js +0 -180
- package/dist/src/commands/inspector.js +0 -256
- package/dist/src/commands/task-report-bug.js +0 -1083
- package/dist/src/report-bug.js +0 -260
|
@@ -1,2032 +1,188 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/task-run-driver.ts
|
|
3
|
-
import { copyFileSync, existsSync
|
|
4
|
-
import { resolve as resolve4 } from "path";
|
|
5
|
-
import { spawn, spawnSync } from "child_process";
|
|
6
|
-
import { createInterface as createLineInterface } from "readline";
|
|
7
|
-
|
|
8
|
-
// packages/cli/src/runner.ts
|
|
9
|
-
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
10
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
11
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
12
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
13
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
14
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
|
-
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
16
|
-
function formatCommand(parts) {
|
|
17
|
-
return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// packages/cli/src/commands/task-run-driver.ts
|
|
21
|
-
import { loadConfig } from "@rig/core/load-config";
|
|
22
|
-
import {
|
|
23
|
-
listAgentRuntimes,
|
|
24
|
-
startRuntimeSnapshotSidecar
|
|
25
|
-
} from "@rig/runtime/control-plane/runtime/isolation";
|
|
26
|
-
import {
|
|
27
|
-
finalizeTaskRunSnapshot,
|
|
28
|
-
resolveTaskRunSnapshotSidecar
|
|
29
|
-
} from "@rig/runtime/control-plane/runtime/snapshot/task-run";
|
|
30
|
-
import { loadRuntimeSnapshotConfig } from "@rig/runtime/control-plane/runtime/guard";
|
|
31
|
-
import {
|
|
32
|
-
buildClaudeLogsFromRecord,
|
|
33
|
-
flushPendingClaudeToolUseLogs
|
|
34
|
-
} from "@rig/runtime/control-plane/provider/claude-stream-records";
|
|
35
|
-
import {
|
|
36
|
-
buildCodexLogsFromRecord,
|
|
37
|
-
extractCodexAssistantMessageText,
|
|
38
|
-
flushPendingCodexToolUseLogs,
|
|
39
|
-
isCodexExecRecord
|
|
40
|
-
} from "@rig/runtime/control-plane/provider/codex-exec-records";
|
|
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";
|
|
43
|
-
import {
|
|
44
|
-
buildTaskRunLifecycleComment,
|
|
45
|
-
updateConfiguredTaskSourceTask
|
|
46
|
-
} from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
47
|
-
import {
|
|
48
|
-
closeIssueAfterMergedPr,
|
|
49
|
-
commitRunChanges,
|
|
50
|
-
runPrAutomation
|
|
51
|
-
} from "@rig/runtime/control-plane/native/pr-automation";
|
|
52
|
-
|
|
53
|
-
// packages/cli/src/commands/_run-driver-helpers.ts
|
|
54
|
-
import { readFileSync } from "fs";
|
|
55
|
-
import { resolve as resolve3 } from "path";
|
|
56
|
-
import {
|
|
57
|
-
appendJsonlRecord,
|
|
58
|
-
readAuthorityRun as readAuthorityRun2,
|
|
59
|
-
resolveAuthorityRunDir as resolveAuthorityRunDir2,
|
|
60
|
-
writeJsonFile as writeJsonFile2
|
|
61
|
-
} from "@rig/runtime/control-plane/authority-files";
|
|
62
|
-
import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
|
|
63
|
-
import { buildProviderTaskRunInstructionLines } from "@rig/runtime/control-plane/provider/runtime-instructions";
|
|
64
|
-
import { loadRigTaskRunSkillBody } from "@rig/runtime/control-plane/provider/rig-task-run-skill";
|
|
65
|
-
|
|
66
|
-
// packages/cli/src/commands/_authority-runs.ts
|
|
67
|
-
import { existsSync } from "fs";
|
|
68
|
-
import { resolve as resolve2 } from "path";
|
|
69
|
-
import {
|
|
70
|
-
readAuthorityRun,
|
|
71
|
-
readJsonlFile,
|
|
72
|
-
resolveAuthorityRunDir,
|
|
73
|
-
writeJsonFile
|
|
74
|
-
} from "@rig/runtime/control-plane/authority-files";
|
|
75
|
-
|
|
76
|
-
// packages/cli/src/commands/_paths.ts
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "fs";
|
|
77
4
|
import { resolve } from "path";
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// packages/cli/src/commands/_authority-runs.ts
|
|
87
|
-
var RIG_WORKSPACE_ID = "rig-local-workspace";
|
|
88
|
-
function normalizeRuntimeAdapter(value) {
|
|
89
|
-
const normalized = value?.trim().toLowerCase();
|
|
90
|
-
if (!normalized) {
|
|
91
|
-
return "pi";
|
|
92
|
-
}
|
|
93
|
-
if (normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || normalized === "gpt-codex") {
|
|
94
|
-
return "codex";
|
|
95
|
-
}
|
|
96
|
-
if (normalized === "pi" || normalized === "rig-pi" || normalized === "@rig/pi") {
|
|
97
|
-
return "pi";
|
|
98
|
-
}
|
|
99
|
-
return "claude-code";
|
|
100
|
-
}
|
|
101
|
-
function readLatestBeadRecord(projectRoot, taskId) {
|
|
102
|
-
const issuesPath = resolve2(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
|
|
103
|
-
if (!existsSync(issuesPath)) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
let latest = null;
|
|
107
|
-
for (const issue of readJsonlFile(issuesPath)) {
|
|
108
|
-
if (!issue || typeof issue !== "object") {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const record = issue;
|
|
112
|
-
if (record.id === taskId) {
|
|
113
|
-
latest = record;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return latest;
|
|
117
|
-
}
|
|
118
|
-
function resolveTaskTitleForAuthorityRun(projectRoot, taskId) {
|
|
119
|
-
try {
|
|
120
|
-
const record = readLatestBeadRecord(projectRoot, taskId);
|
|
121
|
-
const title = record && typeof record.title === "string" ? record.title.trim() : "";
|
|
122
|
-
if (title) {
|
|
123
|
-
return title;
|
|
124
|
-
}
|
|
125
|
-
} catch {}
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
function upsertAgentAuthorityRun(projectRoot, input) {
|
|
129
|
-
const current = readAuthorityRun(projectRoot, input.runId);
|
|
130
|
-
const existing = current;
|
|
131
|
-
const createdAt = existing?.createdAt ?? input.createdAt;
|
|
132
|
-
const runtimeMode = typeof existing?.runtimeMode === "string" ? existing.runtimeMode : "full-access";
|
|
133
|
-
const interactionMode = typeof existing?.interactionMode === "string" ? existing.interactionMode : "default";
|
|
134
|
-
const runMode = typeof existing?.runMode === "string" ? existing.runMode : "autonomous";
|
|
135
|
-
const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
|
|
136
|
-
const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
|
|
137
|
-
const next = {
|
|
138
|
-
...existing ?? {},
|
|
139
|
-
runId: input.runId,
|
|
140
|
-
projectRoot,
|
|
141
|
-
workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
|
|
142
|
-
taskId: input.taskId,
|
|
143
|
-
threadId: existing?.threadId ?? null,
|
|
144
|
-
mode: "local",
|
|
145
|
-
runtimeAdapter,
|
|
146
|
-
status: input.status,
|
|
147
|
-
createdAt,
|
|
148
|
-
startedAt: input.startedAt ?? existing?.startedAt ?? null,
|
|
149
|
-
completedAt: input.completedAt ?? existing?.completedAt ?? null,
|
|
150
|
-
endpointId: existing?.endpointId ?? null,
|
|
151
|
-
hostId: existing?.hostId ?? null,
|
|
152
|
-
worktreePath: input.worktreePath ?? existing?.worktreePath ?? null,
|
|
153
|
-
artifactRoot: input.artifactRoot ?? existing?.artifactRoot ?? null,
|
|
154
|
-
logRoot: input.logRoot ?? existing?.logRoot ?? null,
|
|
155
|
-
sessionPath: input.sessionPath ?? existing?.sessionPath ?? null,
|
|
156
|
-
sessionLogPath: input.sessionLogPath ?? existing?.sessionLogPath ?? null,
|
|
157
|
-
pid: input.pid ?? existing?.pid ?? null,
|
|
158
|
-
updatedAt: input.createdAt,
|
|
159
|
-
title,
|
|
160
|
-
model: typeof existing?.model === "string" ? existing.model : null,
|
|
161
|
-
runtimeMode,
|
|
162
|
-
interactionMode,
|
|
163
|
-
runMode,
|
|
164
|
-
initialPrompt: typeof existing?.initialPrompt === "string" ? existing.initialPrompt : null
|
|
165
|
-
};
|
|
166
|
-
if (input.errorText !== undefined) {
|
|
167
|
-
next.errorText = input.errorText;
|
|
168
|
-
} else if ("errorText" in next) {
|
|
169
|
-
delete next.errorText;
|
|
170
|
-
}
|
|
171
|
-
writeJsonFile(resolve2(resolveAuthorityRunDir(projectRoot, input.runId), "run.json"), next);
|
|
172
|
-
return next;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// packages/cli/src/commands/_run-driver-helpers.ts
|
|
176
|
-
function patchAuthorityRun(projectRoot, runId, patch) {
|
|
177
|
-
const current = readAuthorityRun2(projectRoot, runId);
|
|
178
|
-
if (!current) {
|
|
179
|
-
throw new CliError2(`Run not found: ${runId}`, 2);
|
|
180
|
-
}
|
|
181
|
-
const next = {
|
|
182
|
-
...current,
|
|
183
|
-
...patch,
|
|
184
|
-
updatedAt: new Date().toISOString()
|
|
185
|
-
};
|
|
186
|
-
writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
|
|
187
|
-
return next;
|
|
188
|
-
}
|
|
189
|
-
function touchAuthorityRun(projectRoot, runId) {
|
|
190
|
-
const current = readAuthorityRun2(projectRoot, runId);
|
|
191
|
-
if (!current) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
writeJsonFile2(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), {
|
|
195
|
-
...current,
|
|
196
|
-
updatedAt: new Date().toISOString()
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
function appendRunTimeline(projectRoot, runId, value) {
|
|
200
|
-
appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), value);
|
|
201
|
-
touchAuthorityRun(projectRoot, runId);
|
|
202
|
-
}
|
|
203
|
-
function appendRunLog(projectRoot, runId, value) {
|
|
204
|
-
appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "logs.jsonl"), value);
|
|
205
|
-
touchAuthorityRun(projectRoot, runId);
|
|
206
|
-
}
|
|
207
|
-
function appendRunAction(projectRoot, runId, value) {
|
|
208
|
-
appendJsonlRecord(resolve3(resolveAuthorityRunDir2(projectRoot, runId), "timeline.jsonl"), {
|
|
209
|
-
id: value.id,
|
|
210
|
-
type: "action",
|
|
211
|
-
actionType: value.actionType,
|
|
212
|
-
title: value.title,
|
|
213
|
-
detail: value.detail ?? null,
|
|
214
|
-
state: value.state,
|
|
215
|
-
createdAt: value.startedAt,
|
|
216
|
-
completedAt: value.completedAt ?? null,
|
|
217
|
-
payload: value.payload ?? {}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function startRunAction(projectRoot, runId, input) {
|
|
221
|
-
const startedAt = new Date().toISOString();
|
|
222
|
-
appendRunAction(projectRoot, runId, {
|
|
223
|
-
id: input.actionId,
|
|
224
|
-
actionType: input.actionType,
|
|
225
|
-
title: input.title,
|
|
226
|
-
detail: input.detail,
|
|
227
|
-
state: "running",
|
|
228
|
-
startedAt,
|
|
229
|
-
payload: input.payload
|
|
230
|
-
});
|
|
231
|
-
emitServerRunEvent({ type: "timeline", runId });
|
|
232
|
-
return {
|
|
233
|
-
startedAt,
|
|
234
|
-
complete: (detail, payload) => {
|
|
235
|
-
appendRunAction(projectRoot, runId, {
|
|
236
|
-
id: input.actionId,
|
|
237
|
-
actionType: input.actionType,
|
|
238
|
-
title: input.title,
|
|
239
|
-
detail: detail ?? input.detail ?? null,
|
|
240
|
-
state: "completed",
|
|
241
|
-
startedAt,
|
|
242
|
-
completedAt: new Date().toISOString(),
|
|
243
|
-
payload: payload ?? input.payload
|
|
244
|
-
});
|
|
245
|
-
emitServerRunEvent({ type: "timeline", runId });
|
|
246
|
-
},
|
|
247
|
-
fail: (detail, payload) => {
|
|
248
|
-
appendRunAction(projectRoot, runId, {
|
|
249
|
-
id: input.actionId,
|
|
250
|
-
actionType: input.actionType,
|
|
251
|
-
title: input.title,
|
|
252
|
-
detail,
|
|
253
|
-
state: "failed",
|
|
254
|
-
startedAt,
|
|
255
|
-
completedAt: new Date().toISOString(),
|
|
256
|
-
payload: payload ?? input.payload
|
|
257
|
-
});
|
|
258
|
-
emitServerRunEvent({ type: "timeline", runId });
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function emitServerRunEvent(event) {
|
|
263
|
-
console.log(`__RIG_RUN_EVENT__${JSON.stringify({ ...event, at: new Date().toISOString() })}`);
|
|
264
|
-
}
|
|
265
|
-
function buildRunPrompt(input) {
|
|
266
|
-
const initialPrompt = input.initialPrompt?.trim() || null;
|
|
267
|
-
if (!input.taskId) {
|
|
268
|
-
return initialPrompt ?? input.fallbackTitle?.trim() ?? "Continue the requested Rig run and complete the work.";
|
|
269
|
-
}
|
|
270
|
-
const issues = (() => {
|
|
271
|
-
try {
|
|
272
|
-
return taskLookup(input.projectRoot, input.taskId);
|
|
273
|
-
} catch {
|
|
274
|
-
return "";
|
|
275
|
-
}
|
|
276
|
-
})();
|
|
277
|
-
const scopeText = (() => {
|
|
278
|
-
try {
|
|
279
|
-
const parsed = JSON.parse(readFileSync(resolveControlPlaneTaskConfigPath(input.projectRoot), "utf8"));
|
|
280
|
-
const entry = parsed[input.taskId] ?? {};
|
|
281
|
-
const scope = Array.isArray(entry.scope) ? entry.scope.filter((item) => typeof item === "string") : [];
|
|
282
|
-
const validation = Array.isArray(entry.validation) ? entry.validation.filter((item) => typeof item === "string") : [];
|
|
283
|
-
const lines = [];
|
|
284
|
-
if (scope.length > 0)
|
|
285
|
-
lines.push(`Scope:
|
|
286
|
-
- ${scope.join(`
|
|
287
|
-
- `)}`);
|
|
288
|
-
if (validation.length > 0)
|
|
289
|
-
lines.push(`Validation:
|
|
290
|
-
- ${validation.join(`
|
|
291
|
-
- `)}`);
|
|
292
|
-
return lines.join(`
|
|
293
|
-
|
|
294
|
-
`);
|
|
295
|
-
} catch {
|
|
296
|
-
return "";
|
|
297
|
-
}
|
|
298
|
-
})();
|
|
299
|
-
const bead = readLatestBeadRecord(input.projectRoot, input.taskId);
|
|
300
|
-
const sourceTask = input.sourceTask ?? null;
|
|
301
|
-
const sourceDescription = firstPromptString(sourceTask?.description, sourceTask?.body);
|
|
302
|
-
const sourceAcceptance = firstPromptString(sourceTask?.acceptanceCriteria, sourceTask?.acceptance_criteria);
|
|
303
|
-
const sourceValidation = uniqueStrings([...sourceTask?.validation ?? [], ...sourceTask?.validators ?? []]);
|
|
304
|
-
const title = (bead && typeof bead === "object" && typeof bead.title === "string" ? bead.title : null) ?? firstPromptString(sourceTask?.title) ?? input.fallbackTitle ?? issues ?? input.taskId;
|
|
305
|
-
const description = (bead && typeof bead === "object" && typeof bead.description === "string" ? bead.description : null) ?? sourceDescription ?? input.fallbackDescription ?? "";
|
|
306
|
-
const acceptance = bead && typeof bead === "object" && typeof bead.acceptance_criteria === "string" ? bead.acceptance_criteria : sourceAcceptance ?? input.fallbackAcceptanceCriteria ?? "";
|
|
307
|
-
const sourceContractLines = renderSourceTaskContract(sourceTask, sourceValidation);
|
|
308
|
-
const providerLines = buildProviderTaskRunInstructionLines(input.runtimeAdapter);
|
|
309
|
-
const skillBody = loadRigTaskRunSkillBody();
|
|
310
|
-
return [
|
|
311
|
-
`You are working on task ${input.taskId}: ${title}.`,
|
|
312
|
-
description ? `Description:
|
|
313
|
-
${description}` : null,
|
|
314
|
-
acceptance ? `Acceptance criteria:
|
|
315
|
-
${acceptance}` : null,
|
|
316
|
-
sourceContractLines.length > 0 ? `Source task contract:
|
|
317
|
-
${sourceContractLines.join(`
|
|
318
|
-
`)}` : null,
|
|
319
|
-
scopeText || renderSourceScopeValidation(sourceTask, sourceValidation) || null,
|
|
320
|
-
initialPrompt ? `Additional operator guidance:
|
|
321
|
-
${initialPrompt}` : null,
|
|
322
|
-
providerLines.length > 0 ? `Provider-specific runtime tooling:
|
|
323
|
-
${providerLines.join(" ")}` : null,
|
|
324
|
-
skillBody || null
|
|
325
|
-
].filter((value) => Boolean(value)).join(`
|
|
326
|
-
|
|
327
|
-
`);
|
|
328
|
-
}
|
|
329
|
-
function firstPromptString(...values) {
|
|
330
|
-
for (const value of values) {
|
|
331
|
-
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
332
|
-
if (trimmed.length > 0) {
|
|
333
|
-
return trimmed;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
function uniqueStrings(values) {
|
|
339
|
-
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
|
|
340
|
-
}
|
|
341
|
-
function renderSourceTaskContract(task, validation) {
|
|
342
|
-
if (!task) {
|
|
343
|
-
return [];
|
|
344
|
-
}
|
|
345
|
-
const lines = [];
|
|
346
|
-
const sourceIssueId = firstPromptString(task.sourceIssueId);
|
|
347
|
-
if (sourceIssueId)
|
|
348
|
-
lines.push(`Source issue: ${sourceIssueId}`);
|
|
349
|
-
const status = firstPromptString(task.status);
|
|
350
|
-
if (status)
|
|
351
|
-
lines.push(`Source status: ${status}`);
|
|
352
|
-
const issueType = firstPromptString(task.issueType);
|
|
353
|
-
if (issueType)
|
|
354
|
-
lines.push(`Issue type: ${issueType}`);
|
|
355
|
-
const role = firstPromptString(task.role);
|
|
356
|
-
if (role)
|
|
357
|
-
lines.push(`Role: ${role}`);
|
|
358
|
-
const externalRef = firstPromptString(task.externalRef);
|
|
359
|
-
if (externalRef)
|
|
360
|
-
lines.push(`External ref: ${externalRef}`);
|
|
361
|
-
const labels = uniqueStrings(task.labels ?? []);
|
|
362
|
-
if (labels.length > 0)
|
|
363
|
-
lines.push(`Labels:
|
|
364
|
-
- ${labels.join(`
|
|
365
|
-
- `)}`);
|
|
366
|
-
if (validation.length > 0)
|
|
367
|
-
lines.push(`Validators:
|
|
368
|
-
- ${validation.join(`
|
|
369
|
-
- `)}`);
|
|
370
|
-
return lines;
|
|
371
|
-
}
|
|
372
|
-
function renderSourceScopeValidation(task, validation) {
|
|
373
|
-
if (!task) {
|
|
374
|
-
return "";
|
|
375
|
-
}
|
|
376
|
-
const lines = [];
|
|
377
|
-
const scope = uniqueStrings(task.scope ?? []);
|
|
378
|
-
if (scope.length > 0)
|
|
379
|
-
lines.push(`Scope:
|
|
380
|
-
- ${scope.join(`
|
|
381
|
-
- `)}`);
|
|
382
|
-
if (validation.length > 0)
|
|
383
|
-
lines.push(`Validation:
|
|
384
|
-
- ${validation.join(`
|
|
385
|
-
- `)}`);
|
|
386
|
-
return lines.join(`
|
|
387
|
-
|
|
388
|
-
`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// packages/cli/src/commands/task-run-driver.ts
|
|
392
|
-
var PI_CANONICAL_RUN_STAGES = [
|
|
393
|
-
"Connect",
|
|
394
|
-
"GitHub/task sync",
|
|
395
|
-
"Prepare workspace",
|
|
396
|
-
"Launch Pi",
|
|
397
|
-
"Plan",
|
|
398
|
-
"Implement",
|
|
399
|
-
"Validate",
|
|
400
|
-
"Commit",
|
|
401
|
-
"Open PR",
|
|
402
|
-
"Review/CI",
|
|
403
|
-
"Merge",
|
|
404
|
-
"Complete"
|
|
405
|
-
];
|
|
5
|
+
import { createEnvCloseoutRunners } from "@rig/runtime/control-plane/native/closeout-runners";
|
|
6
|
+
import {
|
|
7
|
+
runInProcessCloseout
|
|
8
|
+
} from "@rig/runtime/control-plane/native/in-process-closeout";
|
|
9
|
+
import { updateRunTaskSourceLifecycle } from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
10
|
+
var PI_CANONICAL_RUN_STAGES = ["Connect", "Prepare", "Execute", "Validate", "Review", "Publish", "Complete"];
|
|
406
11
|
function canonicalPiRunStages() {
|
|
407
12
|
return [...PI_CANONICAL_RUN_STAGES];
|
|
408
13
|
}
|
|
409
|
-
function looksLikeGitHubToken(value) {
|
|
410
|
-
const token = value?.trim();
|
|
411
|
-
if (!token)
|
|
412
|
-
return false;
|
|
413
|
-
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
414
|
-
}
|
|
415
|
-
function githubBridgeEnv(token) {
|
|
416
|
-
const clean = token?.trim();
|
|
417
|
-
if (!clean)
|
|
418
|
-
return {};
|
|
419
|
-
return {
|
|
420
|
-
RIG_GITHUB_TOKEN: clean,
|
|
421
|
-
GITHUB_TOKEN: clean,
|
|
422
|
-
GH_TOKEN: clean
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
14
|
function buildPiRigBridgeEnv(input) {
|
|
426
|
-
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
427
15
|
return {
|
|
428
|
-
RIG_PROJECT_ROOT: input.projectRoot,
|
|
429
16
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
430
17
|
RIG_RUN_ID: input.runId,
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
435
|
-
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
436
|
-
...githubBridgeEnv(githubToken)
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
function runGitSync(cwd, args, input) {
|
|
440
|
-
const result = spawnSync("git", [...args], {
|
|
441
|
-
cwd,
|
|
442
|
-
input,
|
|
443
|
-
encoding: "utf8",
|
|
444
|
-
stdio: input === undefined ? ["ignore", "pipe", "pipe"] : ["pipe", "pipe", "pipe"]
|
|
445
|
-
});
|
|
446
|
-
return {
|
|
447
|
-
ok: result.status === 0,
|
|
448
|
-
stdout: result.stdout || "",
|
|
449
|
-
stderr: result.stderr || (result.error instanceof Error ? result.error.message : "")
|
|
18
|
+
...input.taskId ? { RIG_TASK_ID: input.taskId } : {},
|
|
19
|
+
...input.runtimeMode ? { RIG_RUNTIME_MODE: input.runtimeMode } : {},
|
|
20
|
+
...input.model ? { RIG_MODEL: input.model } : {}
|
|
450
21
|
};
|
|
451
22
|
}
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
if (!listed.ok || !listed.stdout)
|
|
23
|
+
function copyTree(sourceRoot, targetRoot) {
|
|
24
|
+
if (!existsSync(sourceRoot))
|
|
455
25
|
return 0;
|
|
456
26
|
let copied = 0;
|
|
457
|
-
for (const
|
|
458
|
-
const sourcePath =
|
|
459
|
-
const targetPath =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
27
|
+
for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
28
|
+
const sourcePath = resolve(sourceRoot, entry.name);
|
|
29
|
+
const targetPath = resolve(targetRoot, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
mkdirSync(targetPath, { recursive: true });
|
|
32
|
+
copied += copyTree(sourcePath, targetPath);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
mkdirSync(resolve(targetPath, ".."), { recursive: true });
|
|
36
|
+
copyFileSync(sourcePath, targetPath);
|
|
37
|
+
copied += 1;
|
|
467
38
|
}
|
|
468
39
|
return copied;
|
|
469
40
|
}
|
|
470
41
|
function applyDirtyBaselineSnapshot(input) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return { applied: false, copiedUntracked: 0, detail: diff.stderr || "git diff failed" };
|
|
474
|
-
}
|
|
475
|
-
let applied = false;
|
|
476
|
-
if (diff.stdout.trim()) {
|
|
477
|
-
const apply = runGitSync(input.targetRoot, ["apply", "--whitespace=nowarn", "-"], diff.stdout);
|
|
478
|
-
if (!apply.ok) {
|
|
479
|
-
return { applied: false, copiedUntracked: 0, detail: apply.stderr || "git apply failed" };
|
|
480
|
-
}
|
|
481
|
-
applied = true;
|
|
482
|
-
}
|
|
483
|
-
const copiedUntracked = copyUntrackedDirtyFiles(input.sourceRoot, input.targetRoot);
|
|
42
|
+
mkdirSync(input.targetRoot, { recursive: true });
|
|
43
|
+
const copied = copyTree(input.sourceRoot, input.targetRoot);
|
|
484
44
|
return {
|
|
485
|
-
applied:
|
|
486
|
-
copiedUntracked,
|
|
487
|
-
detail:
|
|
45
|
+
applied: copied > 0,
|
|
46
|
+
copiedUntracked: copied,
|
|
47
|
+
detail: copied > 0 ? `Copied ${copied} file(s) into the runtime workspace.` : "No files copied."
|
|
488
48
|
};
|
|
489
49
|
}
|
|
490
50
|
function buildTaskRunReviewEnv(config) {
|
|
491
|
-
const review = config?.review;
|
|
492
51
|
return {
|
|
493
|
-
...review?.mode ? { AI_REVIEW_MODE: review.mode } : {},
|
|
494
|
-
...review?.provider ? { AI_REVIEW_PROVIDER: review.provider } : {}
|
|
52
|
+
...config?.review?.mode ? { AI_REVIEW_MODE: config.review.mode } : {},
|
|
53
|
+
...config?.review?.provider ? { AI_REVIEW_PROVIDER: config.review.provider } : {}
|
|
495
54
|
};
|
|
496
55
|
}
|
|
497
56
|
function buildDirtyBaselineHandshakeEnv(input) {
|
|
498
|
-
if (input.baselineMode !== "dirty-snapshot")
|
|
499
|
-
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
500
57
|
return {
|
|
501
|
-
|
|
502
|
-
RIG_DIRTY_BASELINE_READY_FILE:
|
|
58
|
+
RIG_DIRTY_BASELINE_MODE: input.baselineMode,
|
|
59
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve(input.projectRoot, ".rig", "state", `${input.runId}.dirty-ready`)
|
|
503
60
|
};
|
|
504
61
|
}
|
|
505
|
-
function positiveInt(value, fallback) {
|
|
506
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
507
|
-
}
|
|
508
62
|
function resolveTaskRunAutomationLimits(config, env = process.env) {
|
|
509
|
-
const
|
|
510
|
-
const
|
|
63
|
+
const maxValidationAttempts = Number.parseInt(env.RIG_MAX_VALIDATION_ATTEMPTS ?? "", 10);
|
|
64
|
+
const maxPrFixIterations = Number.parseInt(env.RIG_MAX_PR_FIX_ITERATIONS ?? "", 10);
|
|
511
65
|
return {
|
|
512
|
-
maxValidationAttempts:
|
|
513
|
-
maxPrFixIterations:
|
|
66
|
+
maxValidationAttempts: Number.isFinite(maxValidationAttempts) && maxValidationAttempts > 0 ? maxValidationAttempts : config?.automation?.maxValidationAttempts ?? 30,
|
|
67
|
+
maxPrFixIterations: Number.isFinite(maxPrFixIterations) && maxPrFixIterations > 0 ? maxPrFixIterations : config?.automation?.maxPrFixIterations ?? 100500
|
|
514
68
|
};
|
|
515
69
|
}
|
|
516
70
|
function classifyPlanningNeed(input) {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
const skipLabels = planning.skipForLabels ?? ["no-plan", "small", "trivial"];
|
|
522
|
-
const bodyLength = `${input.sourceTask?.description ?? ""}
|
|
523
|
-
${input.sourceTask?.body ?? ""}
|
|
524
|
-
${input.sourceTask?.acceptanceCriteria ?? input.sourceTask?.acceptance_criteria ?? ""}`.trim().length;
|
|
525
|
-
const forcedLabel = requireLabels.find((label) => labels.has(label.toLowerCase()));
|
|
526
|
-
const skippedLabel = skipLabels.find((label) => labels.has(label.toLowerCase()));
|
|
527
|
-
const highRisk = labels.has("risk:high") || labels.has("security") || labels.has("migration");
|
|
528
|
-
const size = labels.has("large") || bodyLength > 4000 ? "large" : bodyLength > 1000 ? "medium" : "small";
|
|
529
|
-
const risk = highRisk ? "high" : labels.has("risk:medium") || size !== "small" ? "medium" : "low";
|
|
530
|
-
if (mode === "always") {
|
|
531
|
-
return { planningRequired: true, size, risk, reason: "planning.mode=always", policy: "always" };
|
|
532
|
-
}
|
|
533
|
-
if (mode === "off") {
|
|
534
|
-
return { planningRequired: false, size, risk, reason: "planning.mode=off", policy: "off" };
|
|
535
|
-
}
|
|
536
|
-
if (forcedLabel) {
|
|
537
|
-
return { planningRequired: true, size, risk, reason: `label:${forcedLabel}`, policy: "auto" };
|
|
538
|
-
}
|
|
539
|
-
if (skippedLabel && !highRisk) {
|
|
540
|
-
return { planningRequired: false, size, risk, reason: `label:${skippedLabel}`, policy: "auto" };
|
|
541
|
-
}
|
|
542
|
-
const planningRequired = size !== "small" || risk === "high";
|
|
543
|
-
return {
|
|
544
|
-
planningRequired,
|
|
545
|
-
size,
|
|
546
|
-
risk,
|
|
547
|
-
reason: planningRequired ? `auto:${size}/${risk}` : "auto:small/low",
|
|
548
|
-
policy: "auto"
|
|
549
|
-
};
|
|
71
|
+
const text = `${input.prompt}
|
|
72
|
+
${input.acceptanceCriteria ?? ""}`.toLowerCase();
|
|
73
|
+
const needsPlanning = /plan|design|approach|architecture|multi-step/.test(text);
|
|
74
|
+
return { needsPlanning, reason: needsPlanning ? "operator requested a planning pass" : "no planning trigger detected" };
|
|
550
75
|
}
|
|
551
76
|
function buildPiValidationRetrySteeringPrompt(input) {
|
|
552
77
|
return [
|
|
553
|
-
`
|
|
554
|
-
|
|
555
|
-
""
|
|
556
|
-
`Latest blocker: ${input.failureDetail}`,
|
|
557
|
-
...input.validationOutput?.trim() ? ["", "Validation output:", input.validationOutput.trim()] : [],
|
|
558
|
-
"",
|
|
559
|
-
"Fix the concrete failure, rerun the relevant validation, and only report completion when the task is accepted."
|
|
78
|
+
`Validation failed on attempt ${input.attempt}${input.taskId ? ` for task ${input.taskId}` : ""}.`,
|
|
79
|
+
input.failureDetail.trim(),
|
|
80
|
+
"Fix the issue, rerun validation, and continue only after it passes."
|
|
560
81
|
].join(`
|
|
82
|
+
|
|
561
83
|
`);
|
|
562
84
|
}
|
|
563
85
|
function classifyValidationRetryOutcome(input) {
|
|
564
86
|
return {
|
|
565
87
|
stage: "Validate failed",
|
|
566
|
-
status: input.attempt
|
|
88
|
+
status: input.attempt < input.maxAttempts ? "retry" : "needs_attention",
|
|
567
89
|
failureDetail: input.failureDetail
|
|
568
90
|
};
|
|
569
91
|
}
|
|
570
|
-
function configuredIssueUpdatesMode(config) {
|
|
571
|
-
const github = config?.github;
|
|
572
|
-
const mode = github && typeof github === "object" && !Array.isArray(github) ? github.issueUpdates : null;
|
|
573
|
-
return mode === "off" || mode === "minimal" || mode === "lifecycle" ? mode : "lifecycle";
|
|
574
|
-
}
|
|
575
|
-
function shouldWriteDriverIssueUpdate(config, status) {
|
|
576
|
-
const mode = configuredIssueUpdatesMode(config);
|
|
577
|
-
if (mode === "off")
|
|
578
|
-
return false;
|
|
579
|
-
if (mode === "lifecycle")
|
|
580
|
-
return true;
|
|
581
|
-
return ["closed", "failed", "needs_attention"].includes(status);
|
|
582
|
-
}
|
|
583
|
-
async function loadTaskRunAutomationConfig(projectRoot) {
|
|
584
|
-
try {
|
|
585
|
-
return await loadConfig(projectRoot);
|
|
586
|
-
} catch {
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
92
|
function taskRunStageLogId(runId, stage) {
|
|
591
93
|
return `log:${runId}:stage:${stage.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
|
|
592
94
|
}
|
|
593
|
-
function appendPiStageLog(input) {
|
|
594
|
-
appendRunLog(input.projectRoot, input.runId, {
|
|
595
|
-
id: taskRunStageLogId(input.runId, input.stage),
|
|
596
|
-
title: input.stage,
|
|
597
|
-
detail: input.detail ?? null,
|
|
598
|
-
tone: input.tone ?? "info",
|
|
599
|
-
status: input.status ?? "running",
|
|
600
|
-
createdAt: new Date().toISOString()
|
|
601
|
-
});
|
|
602
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
|
|
603
|
-
}
|
|
604
|
-
async function runCheckedCommand(command, args, cwd, label = "git") {
|
|
605
|
-
const result = await command(args, { cwd });
|
|
606
|
-
if (result.exitCode !== 0) {
|
|
607
|
-
throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function createCommandRunner(binary) {
|
|
611
|
-
return async (args, options) => {
|
|
612
|
-
const child = spawn(binary, [...args], {
|
|
613
|
-
cwd: options?.cwd,
|
|
614
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
615
|
-
});
|
|
616
|
-
const stdoutChunks = [];
|
|
617
|
-
const stderrChunks = [];
|
|
618
|
-
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
619
|
-
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
620
|
-
return await new Promise((resolve5) => {
|
|
621
|
-
child.once("error", (error) => resolve5({ exitCode: 1, stderr: error.message }));
|
|
622
|
-
child.once("close", (code) => resolve5({
|
|
623
|
-
exitCode: code ?? 1,
|
|
624
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
625
|
-
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
626
|
-
}));
|
|
627
|
-
});
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
async function resolvePostValidationBranch(input) {
|
|
631
|
-
if (!input.configuredBranch.startsWith("task-")) {
|
|
632
|
-
return input.configuredBranch;
|
|
633
|
-
}
|
|
634
|
-
const current = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: input.workspace });
|
|
635
|
-
const currentBranch = current.stdout?.trim();
|
|
636
|
-
if (current.exitCode === 0 && currentBranch && currentBranch !== "HEAD") {
|
|
637
|
-
return currentBranch;
|
|
638
|
-
}
|
|
639
|
-
return input.configuredBranch;
|
|
640
|
-
}
|
|
641
|
-
async function runTaskRunPostValidationLifecycle(input) {
|
|
642
|
-
const taskId = input.taskId?.trim();
|
|
643
|
-
if (!taskId) {
|
|
644
|
-
return { status: "skipped" };
|
|
645
|
-
}
|
|
646
|
-
const config = input.config ?? {};
|
|
647
|
-
const prMode = config.pr?.mode ?? "auto";
|
|
648
|
-
if (prMode === "off" || prMode === "ask") {
|
|
649
|
-
input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
|
|
650
|
-
input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
|
|
651
|
-
return { status: "skipped" };
|
|
652
|
-
}
|
|
653
|
-
const workspace = input.runtimeWorkspace || input.projectRoot;
|
|
654
|
-
const ghCommand = input.ghCommand ?? createCommandRunner("gh");
|
|
655
|
-
const gitCommand = input.gitCommand ?? createCommandRunner("git");
|
|
656
|
-
const configuredBranch = input.branch || `rig/${input.runtimeTaskId}-${input.runId}`;
|
|
657
|
-
const branch = await resolvePostValidationBranch({
|
|
658
|
-
workspace,
|
|
659
|
-
configuredBranch,
|
|
660
|
-
gitCommand
|
|
661
|
-
});
|
|
662
|
-
const prAutomation = input.prAutomation ?? runPrAutomation;
|
|
663
|
-
const updateTaskSource = input.updateTaskSource ?? updateConfiguredTaskSourceTask;
|
|
664
|
-
const stage = input.appendStage ?? (() => {
|
|
665
|
-
return;
|
|
666
|
-
});
|
|
667
|
-
const steerPi = input.steerPi ?? (async (message) => {
|
|
668
|
-
appendRunLog(input.projectRoot, input.runId, {
|
|
669
|
-
id: `log:${input.runId}:pr-steering:${Date.now()}`,
|
|
670
|
-
title: "Steering Pi from PR feedback",
|
|
671
|
-
detail: message,
|
|
672
|
-
tone: "info",
|
|
673
|
-
status: "reviewing",
|
|
674
|
-
createdAt: new Date().toISOString()
|
|
675
|
-
});
|
|
676
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
|
|
677
|
-
});
|
|
678
|
-
stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
|
|
679
|
-
const commit = await commitRunChanges({
|
|
680
|
-
cwd: workspace,
|
|
681
|
-
message: `rig: complete task ${taskId}`,
|
|
682
|
-
command: gitCommand
|
|
683
|
-
});
|
|
684
|
-
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
685
|
-
await runCheckedCommand(gitCommand, ["push", "--set-upstream", "origin", branch], workspace, "git");
|
|
686
|
-
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
687
|
-
const pr = await prAutomation({
|
|
688
|
-
projectRoot: workspace,
|
|
689
|
-
taskId,
|
|
690
|
-
runId: input.runId,
|
|
691
|
-
branch,
|
|
692
|
-
config,
|
|
693
|
-
sourceTask: input.sourceTask,
|
|
694
|
-
uploadedSnapshot: input.uploadedSnapshot,
|
|
695
|
-
command: ghCommand,
|
|
696
|
-
steerPi,
|
|
697
|
-
lifecycle: {
|
|
698
|
-
onPrOpened: async ({ prUrl }) => {
|
|
699
|
-
stage("Open PR", prUrl, "completed", "tool");
|
|
700
|
-
try {
|
|
701
|
-
if (shouldWriteDriverIssueUpdate(config, "under_review")) {
|
|
702
|
-
await updateTaskSource(input.projectRoot, {
|
|
703
|
-
taskId,
|
|
704
|
-
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
705
|
-
update: {
|
|
706
|
-
status: "under_review",
|
|
707
|
-
comment: buildTaskRunLifecycleComment({
|
|
708
|
-
runId: input.runId,
|
|
709
|
-
status: "under_review",
|
|
710
|
-
summary: "Rig opened a pull request for this task.",
|
|
711
|
-
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
712
|
-
})
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
} catch (error) {
|
|
717
|
-
appendRunLog(input.projectRoot, input.runId, {
|
|
718
|
-
id: `log:${input.runId}:task-source-under-review-update`,
|
|
719
|
-
title: "Task source PR status update failed",
|
|
720
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
721
|
-
tone: "error",
|
|
722
|
-
status: "reviewing",
|
|
723
|
-
createdAt: new Date().toISOString()
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
},
|
|
727
|
-
onReviewCiStarted: ({ prUrl, iteration }) => stage("Review/CI", `${prUrl} (iteration ${iteration})`, "running", "info"),
|
|
728
|
-
onFeedback: async ({ feedback }) => {
|
|
729
|
-
stage("Review/CI", feedback.join(`
|
|
730
|
-
`), "reviewing", "error");
|
|
731
|
-
if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
|
|
732
|
-
await updateTaskSource(input.projectRoot, {
|
|
733
|
-
taskId,
|
|
734
|
-
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
735
|
-
update: {
|
|
736
|
-
status: "ci_fixing",
|
|
737
|
-
comment: buildTaskRunLifecycleComment({
|
|
738
|
-
runId: input.runId,
|
|
739
|
-
status: "ci_fixing",
|
|
740
|
-
summary: "Rig is fixing CI/review feedback for this task.",
|
|
741
|
-
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
742
|
-
})
|
|
743
|
-
}
|
|
744
|
-
}).catch(() => {
|
|
745
|
-
return;
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
},
|
|
749
|
-
onMergeStarted: async ({ prUrl }) => {
|
|
750
|
-
stage("Merge", prUrl, "running", "tool");
|
|
751
|
-
if (shouldWriteDriverIssueUpdate(config, "merging")) {
|
|
752
|
-
await updateTaskSource(input.projectRoot, {
|
|
753
|
-
taskId,
|
|
754
|
-
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
755
|
-
update: {
|
|
756
|
-
status: "merging",
|
|
757
|
-
comment: buildTaskRunLifecycleComment({
|
|
758
|
-
runId: input.runId,
|
|
759
|
-
status: "merging",
|
|
760
|
-
summary: "Rig is merging the pull request for this task.",
|
|
761
|
-
runtimeWorkspace: input.runtimeWorkspace ?? null
|
|
762
|
-
})
|
|
763
|
-
}
|
|
764
|
-
}).catch(() => {
|
|
765
|
-
return;
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
},
|
|
769
|
-
onMerged: ({ prUrl }) => stage("Merge", prUrl, "completed", "tool")
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
if (pr.status === "opened" && pr.prUrl) {
|
|
773
|
-
stage("Complete", `PR ready without merge: ${pr.prUrl}`, "completed", "info");
|
|
774
|
-
return { status: "completed", pr };
|
|
775
|
-
}
|
|
776
|
-
if (pr.status !== "merged" || !pr.prUrl) {
|
|
777
|
-
const detail = pr.actionableFeedback.join(`
|
|
778
|
-
`) || "PR automation did not merge the PR.";
|
|
779
|
-
stage("Needs attention", detail, "needs_attention", "error");
|
|
780
|
-
if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
|
|
781
|
-
await updateTaskSource(input.projectRoot, {
|
|
782
|
-
taskId,
|
|
783
|
-
sourceTask: runSourceTaskIdentity(input.sourceTask),
|
|
784
|
-
update: {
|
|
785
|
-
status: "needs_attention",
|
|
786
|
-
comment: buildTaskRunLifecycleComment({
|
|
787
|
-
runId: input.runId,
|
|
788
|
-
status: "needs_attention",
|
|
789
|
-
summary: "Rig needs operator attention before this task can proceed.",
|
|
790
|
-
runtimeWorkspace: input.runtimeWorkspace ?? null,
|
|
791
|
-
errorText: detail
|
|
792
|
-
})
|
|
793
|
-
}
|
|
794
|
-
}).catch(() => {
|
|
795
|
-
return;
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
return { status: "needs_attention", pr };
|
|
799
|
-
}
|
|
800
|
-
if (shouldWriteDriverIssueUpdate(config, "closed")) {
|
|
801
|
-
await closeIssueAfterMergedPr({
|
|
802
|
-
projectRoot: input.projectRoot,
|
|
803
|
-
taskId,
|
|
804
|
-
runId: input.runId,
|
|
805
|
-
prUrl: pr.prUrl,
|
|
806
|
-
sourceTask: input.sourceTask,
|
|
807
|
-
updateTaskSource: async (projectRoot, closeInput) => updateTaskSource(projectRoot, closeInput)
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
|
|
811
|
-
return { status: "completed", pr };
|
|
812
|
-
}
|
|
813
|
-
function summarizeValidationFailure(projectRoot, taskId) {
|
|
814
|
-
if (!taskId) {
|
|
815
|
-
return null;
|
|
816
|
-
}
|
|
817
|
-
for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
|
|
818
|
-
const summary = readJsonFile(resolve4(artifactDir, "validation-summary.json"), null);
|
|
819
|
-
if (!summary || summary.status !== "fail") {
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
const failedCategories = Array.isArray(summary.categories) ? summary.categories.filter((entry) => String(entry?.status ?? "") !== "pass").map((entry) => String(entry?.category ?? "").trim()).filter(Boolean) : [];
|
|
823
|
-
if (failedCategories.length > 0) {
|
|
824
|
-
return `Task validation failed: ${failedCategories.join(", ")}`;
|
|
825
|
-
}
|
|
826
|
-
return "Task validation failed.";
|
|
827
|
-
}
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
function classifyCompletionVerificationLine(line) {
|
|
831
|
-
const trimmed = line.trim();
|
|
832
|
-
if (/^=== Completion Verification: /.test(trimmed)) {
|
|
833
|
-
return { isCompletionLine: true, isVerifierReview: false };
|
|
834
|
-
}
|
|
835
|
-
if (/^\[\d+\/\d+\]/.test(trimmed)) {
|
|
836
|
-
return {
|
|
837
|
-
isCompletionLine: true,
|
|
838
|
-
isVerifierReview: /Verifier review/i.test(trimmed)
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
return { isCompletionLine: false, isVerifierReview: false };
|
|
842
|
-
}
|
|
843
95
|
function createRunLogIdFactory(runId) {
|
|
844
|
-
let
|
|
845
|
-
return () => {
|
|
846
|
-
sequence += 1;
|
|
847
|
-
return `log:${runId}:${Date.now()}:${sequence}`;
|
|
848
|
-
};
|
|
96
|
+
let next = 0;
|
|
97
|
+
return () => `${runId}:log:${++next}`;
|
|
849
98
|
}
|
|
850
99
|
function summarizeTaskRunProcessFailure(input) {
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
const meaningfulLines = [...stderrLines].map((line) => line.trim()).filter(Boolean).filter((line) => !/^Task run failed \([^)]*\)$/i.test(line));
|
|
854
|
-
const taskSourceFailure = meaningfulLines.find((line) => /failed to update task source/i.test(line));
|
|
855
|
-
if (taskSourceFailure) {
|
|
856
|
-
return `Task source update failed: ${taskSourceFailure}`;
|
|
857
|
-
}
|
|
858
|
-
const moduleFailure = meaningfulLines.find((line) => /cannot find module/i.test(line));
|
|
859
|
-
if (moduleFailure) {
|
|
860
|
-
return `Runtime module resolution failed: ${moduleFailure}`;
|
|
861
|
-
}
|
|
862
|
-
for (const line of [...meaningfulLines].reverse()) {
|
|
863
|
-
const trimmed = line.trim();
|
|
864
|
-
if (/server-managed task run cannot finish successfully/i.test(trimmed)) {
|
|
865
|
-
return `Task source remained open: ${trimmed}`;
|
|
866
|
-
}
|
|
867
|
-
if (/no api key found/i.test(trimmed)) {
|
|
868
|
-
return `Missing provider credentials: ${trimmed}`;
|
|
869
|
-
}
|
|
870
|
-
if (/no claude credentials found/i.test(trimmed)) {
|
|
871
|
-
return `Missing provider credentials: ${trimmed}`;
|
|
872
|
-
}
|
|
873
|
-
if (/unauthorized|authentication failed|invalid api key|api key is invalid/i.test(trimmed)) {
|
|
874
|
-
return `Provider authentication failed: ${trimmed}`;
|
|
875
|
-
}
|
|
876
|
-
if (/command not found|no such file or directory|enoent/i.test(trimmed)) {
|
|
877
|
-
return `Missing runtime executable: ${trimmed}`;
|
|
878
|
-
}
|
|
879
|
-
if (/bubblewrap|\bbwrap\b|sandbox-exec|sandbox/i.test(trimmed) && /missing|not found|failed|denied/i.test(trimmed)) {
|
|
880
|
-
return `Sandbox backend unavailable: ${trimmed}`;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return fallback;
|
|
100
|
+
const detail = [input.stderr?.trim(), input.stdout?.trim()].filter(Boolean)[0];
|
|
101
|
+
return `${input.stage} failed${input.exitCode !== null && input.exitCode !== undefined ? ` (exit ${input.exitCode})` : ""}${detail ? `: ${detail}` : input.fallback ? `: ${input.fallback}` : ""}`;
|
|
884
102
|
}
|
|
885
|
-
function
|
|
886
|
-
if (
|
|
887
|
-
return
|
|
888
|
-
}
|
|
889
|
-
const parsed = Number.parseInt(value, 10);
|
|
890
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
891
|
-
throw new CliError2(`Invalid ${option} value: ${value}`);
|
|
103
|
+
function classifyCompletionVerificationLine(line) {
|
|
104
|
+
if (line.startsWith("=== Completion Verification:")) {
|
|
105
|
+
return { isCompletionLine: true, stage: line.replace(/^=== Completion Verification:\s*/, "").replace(/\s*=+$/, "").trim(), status: "started" };
|
|
892
106
|
}
|
|
893
|
-
|
|
107
|
+
if (/^PASS\b/.test(line))
|
|
108
|
+
return { isCompletionLine: true, stage: null, status: "passed" };
|
|
109
|
+
if (/^FAIL\b/.test(line))
|
|
110
|
+
return { isCompletionLine: true, stage: null, status: "failed" };
|
|
111
|
+
return { isCompletionLine: false, stage: null, status: null };
|
|
894
112
|
}
|
|
895
113
|
function readTaskRunAcceptedArtifactState(input) {
|
|
896
|
-
if (!input.
|
|
114
|
+
if (!existsSync(input.artifactPath))
|
|
897
115
|
return { accepted: false, reason: null };
|
|
898
|
-
}
|
|
899
|
-
const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
|
|
900
|
-
const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
|
|
901
|
-
const taskResultPath = resolve4(artifactDir, "task-result.json");
|
|
902
|
-
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
903
|
-
if (reviewStatus !== "APPROVED") {
|
|
904
|
-
return { accepted: false, reason: null };
|
|
905
|
-
}
|
|
906
|
-
const taskResult = readJsonFile(taskResultPath, null);
|
|
907
|
-
if (taskResult?.status !== "completed") {
|
|
908
|
-
return { accepted: false, reason: null };
|
|
909
|
-
}
|
|
910
|
-
if (typeof input.artifactNotBeforeMs === "number" && Number.isFinite(input.artifactNotBeforeMs)) {
|
|
911
|
-
try {
|
|
912
|
-
const reviewStatusMtime = statSync(reviewStatusPath).mtimeMs;
|
|
913
|
-
if (reviewStatusMtime + 1000 < input.artifactNotBeforeMs) {
|
|
914
|
-
return { accepted: false, reason: null };
|
|
915
|
-
}
|
|
916
|
-
} catch {
|
|
917
|
-
return { accepted: false, reason: null };
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return {
|
|
921
|
-
accepted: true,
|
|
922
|
-
reason: `task-result.json is completed and review-status.txt is APPROVED for task ${input.taskId}`
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
function resolvePiAcceptedArtifactExitGraceMs(env = process.env) {
|
|
926
|
-
const raw = env.RIG_PI_ACCEPTED_ARTIFACT_EXIT_GRACE_MS;
|
|
927
|
-
if (raw === undefined || raw.trim() === "")
|
|
928
|
-
return 30000;
|
|
929
|
-
const parsed = Number.parseInt(raw, 10);
|
|
930
|
-
if (!Number.isFinite(parsed) || parsed < 0)
|
|
931
|
-
return 30000;
|
|
932
|
-
return parsed;
|
|
933
|
-
}
|
|
934
|
-
function resolveTaskRunRetryContext(input) {
|
|
935
|
-
if (!input.taskId || !input.workspaceDir) {
|
|
936
|
-
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
937
|
-
}
|
|
938
|
-
const artifactDir = resolve4(input.workspaceDir, "artifacts", input.taskId);
|
|
939
|
-
const reviewStatePath = resolve4(artifactDir, "review-state.json");
|
|
940
|
-
const reviewFeedbackPath = resolve4(artifactDir, "review-feedback.md");
|
|
941
|
-
const reviewStatusPath = resolve4(artifactDir, "review-status.txt");
|
|
942
|
-
const failedApproachesPath = resolve4(input.workspaceDir, ".rig", "state", "failed_approaches.md");
|
|
943
|
-
const validationSummaryPath = resolve4(artifactDir, "validation-summary.json");
|
|
944
|
-
const reviewState = readJsonFile(reviewStatePath, null);
|
|
945
|
-
const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
|
|
946
|
-
const reviewRejected = isTaskRunReviewRejected(reviewState);
|
|
947
|
-
if (reviewStatus === "APPROVED") {
|
|
948
|
-
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
949
|
-
}
|
|
950
|
-
if (reviewStatus !== "REJECTED" && !reviewRejected) {
|
|
951
|
-
return { shouldRetry: false, failureDetail: null, nextPrompt: null };
|
|
952
|
-
}
|
|
953
|
-
const failureDetail = summarizeTaskRunReviewFailure(reviewState);
|
|
954
|
-
if (input.attempt >= input.maxAttempts) {
|
|
955
|
-
return { shouldRetry: false, failureDetail, nextPrompt: null };
|
|
956
|
-
}
|
|
957
|
-
return {
|
|
958
|
-
shouldRetry: true,
|
|
959
|
-
failureDetail,
|
|
960
|
-
nextPrompt: [
|
|
961
|
-
input.basePrompt,
|
|
962
|
-
"",
|
|
963
|
-
`Retry attempt ${input.attempt + 1}/${input.maxAttempts} for task ${input.taskId}.`,
|
|
964
|
-
"The previous attempt did not satisfy completion verification. Continue from the existing runtime workspace and PR state instead of starting over.",
|
|
965
|
-
"",
|
|
966
|
-
`Latest blocker: ${failureDetail}`,
|
|
967
|
-
"",
|
|
968
|
-
"Inspect these artifacts first:",
|
|
969
|
-
`- ${reviewStatusPath}`,
|
|
970
|
-
`- ${reviewStatePath}`,
|
|
971
|
-
`- ${reviewFeedbackPath}`,
|
|
972
|
-
`- ${failedApproachesPath}`,
|
|
973
|
-
`- ${validationSummaryPath}`,
|
|
974
|
-
"",
|
|
975
|
-
"Resolve the concrete issues recorded there, refresh the task artifacts if needed, rerun validation and completion verification, and do not stop until the task is actually accepted. When Greptile review is required, keep iterating until the review verdict is APPROVE with no unresolved comments."
|
|
976
|
-
].join(`
|
|
977
|
-
`)
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
function summarizeTaskRunReviewFailure(reviewState) {
|
|
981
|
-
if (!reviewState) {
|
|
982
|
-
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
983
|
-
}
|
|
984
|
-
const reasons = [
|
|
985
|
-
...reviewState.local_reasons ?? [],
|
|
986
|
-
...reviewState.ai_reasons ?? [],
|
|
987
|
-
...reviewState.ai_warnings ?? []
|
|
988
|
-
].map((value) => value.trim()).filter(Boolean);
|
|
989
|
-
if (reasons.length > 0) {
|
|
990
|
-
return reasons[0];
|
|
991
|
-
}
|
|
992
|
-
if (reviewState.mode === "required" && reviewState.verdict && reviewState.verdict !== "APPROVE") {
|
|
993
|
-
return `[AI Review] Required mode needs a completed Greptile approval; current verdict is ${reviewState.verdict}.`;
|
|
994
|
-
}
|
|
995
|
-
return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
|
|
996
|
-
}
|
|
997
|
-
function readTaskRunReviewStatus(reviewStatusPath) {
|
|
998
|
-
if (!existsSync2(reviewStatusPath)) {
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
116
|
try {
|
|
1002
|
-
const
|
|
1003
|
-
return
|
|
117
|
+
const parsed = JSON.parse(readFileSync(input.artifactPath, "utf8"));
|
|
118
|
+
return {
|
|
119
|
+
accepted: parsed.accepted === true,
|
|
120
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : null
|
|
121
|
+
};
|
|
1004
122
|
} catch {
|
|
1005
|
-
return null;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
function isTaskRunReviewRejected(reviewState) {
|
|
1009
|
-
if (!reviewState) {
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
if (reviewState.approved === false) {
|
|
1013
|
-
return true;
|
|
123
|
+
return { accepted: false, reason: null };
|
|
1014
124
|
}
|
|
1015
|
-
return reviewState.mode === "required" && !!reviewState.verdict && reviewState.verdict !== "APPROVE";
|
|
1016
125
|
}
|
|
1017
|
-
function
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
126
|
+
async function runTaskRunPostValidationLifecycle(input) {
|
|
127
|
+
const projectRoot = cleanString(input.projectRoot) ?? process.cwd();
|
|
128
|
+
const runId = cleanString(input.runId) ?? "run";
|
|
129
|
+
const taskId = cleanString(input.runtimeTaskId) ?? cleanString(input.taskId);
|
|
1021
130
|
if (!taskId)
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const
|
|
131
|
+
throw new Error("Task run post-validation lifecycle requires a task id.");
|
|
132
|
+
const workspace = cleanString(input.runtimeWorkspace) ?? cleanString(input.workspace) ?? projectRoot;
|
|
133
|
+
const branch = cleanString(input.branch) ?? `rig/${taskId}-${runId}`;
|
|
134
|
+
const config = input.config && typeof input.config === "object" && !Array.isArray(input.config) ? input.config : {};
|
|
135
|
+
const sourceTask = input.sourceTask && typeof input.sourceTask === "object" && !Array.isArray(input.sourceTask) ? input.sourceTask : null;
|
|
136
|
+
const runners = createEnvCloseoutRunners(process.env);
|
|
137
|
+
const appendStage = typeof input.appendStage === "function" ? input.appendStage : null;
|
|
138
|
+
const result = await runInProcessCloseout({
|
|
139
|
+
projectRoot,
|
|
140
|
+
runId,
|
|
1027
141
|
taskId,
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
runId,
|
|
1033
|
-
status,
|
|
1034
|
-
summary,
|
|
1035
|
-
runtimeWorkspace: input.latestRuntimeWorkspace,
|
|
1036
|
-
logsDir: input.latestLogsDir,
|
|
1037
|
-
sessionDir: input.latestSessionDir,
|
|
1038
|
-
errorText: input.errorText
|
|
1039
|
-
})
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
if (!result.updated && (result.source === "plugin" || result.sourceKind)) {
|
|
1043
|
-
throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function readRunSourceTaskContract(projectRoot, runId, taskId) {
|
|
1047
|
-
const run = readAuthorityRun3(projectRoot, runId);
|
|
1048
|
-
const raw = run?.sourceTask;
|
|
1049
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1050
|
-
return null;
|
|
1051
|
-
}
|
|
1052
|
-
const record = raw;
|
|
1053
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId?.trim();
|
|
1054
|
-
if (!id) {
|
|
1055
|
-
return null;
|
|
1056
|
-
}
|
|
1057
|
-
return {
|
|
1058
|
-
id,
|
|
1059
|
-
...optionalStringField(record, "title"),
|
|
1060
|
-
...optionalStringField(record, "description"),
|
|
1061
|
-
...optionalStringField(record, "body"),
|
|
1062
|
-
...optionalStringField(record, "acceptanceCriteria"),
|
|
1063
|
-
...optionalStringField(record, "acceptance_criteria"),
|
|
1064
|
-
...optionalStringField(record, "sourceIssueId"),
|
|
1065
|
-
...optionalStringField(record, "status"),
|
|
1066
|
-
...optionalStringField(record, "issueType"),
|
|
1067
|
-
...optionalStringField(record, "role"),
|
|
1068
|
-
...optionalStringField(record, "externalRef"),
|
|
1069
|
-
scope: stringArrayField(record, "scope"),
|
|
1070
|
-
validation: stringArrayField(record, "validation"),
|
|
1071
|
-
validators: stringArrayField(record, "validators"),
|
|
1072
|
-
labels: stringArrayField(record, "labels")
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
function optionalStringField(record, key) {
|
|
1076
|
-
const value = record[String(key)];
|
|
1077
|
-
return typeof value === "string" ? { [key]: value } : {};
|
|
1078
|
-
}
|
|
1079
|
-
function stringArrayField(record, key) {
|
|
1080
|
-
const value = record[key];
|
|
1081
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1082
|
-
}
|
|
1083
|
-
async function executeRigOwnedTaskRun(context, input) {
|
|
1084
|
-
const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
|
|
1085
|
-
const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
|
|
1086
|
-
const resumeMode = process.env.RIG_RUN_RESUME === "1";
|
|
1087
|
-
const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
|
|
1088
|
-
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
1089
|
-
let prompt = buildRunPrompt({
|
|
1090
|
-
projectRoot: context.projectRoot,
|
|
1091
|
-
taskId: input.taskId,
|
|
1092
|
-
fallbackTitle: input.title,
|
|
1093
|
-
fallbackDescription: sourceTask?.description ?? sourceTask?.body ?? null,
|
|
1094
|
-
fallbackAcceptanceCriteria: sourceTask?.acceptanceCriteria ?? sourceTask?.acceptance_criteria ?? null,
|
|
142
|
+
branch,
|
|
143
|
+
workspace,
|
|
144
|
+
artifactRoot: cleanString(input.artifactRoot),
|
|
145
|
+
config,
|
|
1095
146
|
sourceTask,
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
"--runtime-mode",
|
|
1104
|
-
input.runtimeMode,
|
|
1105
|
-
"--interaction-mode",
|
|
1106
|
-
input.interactionMode,
|
|
1107
|
-
...input.model ? ["--model", input.model] : [],
|
|
1108
|
-
"--prompt"
|
|
1109
|
-
] : input.runtimeAdapter === "pi" ? [
|
|
1110
|
-
"--print",
|
|
1111
|
-
"--verbose",
|
|
1112
|
-
"--mode",
|
|
1113
|
-
"json",
|
|
1114
|
-
"--no-session",
|
|
1115
|
-
...input.model ? ["--model", input.model] : []
|
|
1116
|
-
] : [
|
|
1117
|
-
"--print",
|
|
1118
|
-
"--verbose",
|
|
1119
|
-
"--output-format",
|
|
1120
|
-
"stream-json",
|
|
1121
|
-
"--dangerously-skip-permissions"
|
|
1122
|
-
];
|
|
1123
|
-
const buildHostAgentCommand = (attemptPrompt) => input.runtimeAdapter === "codex" ? [hostAgentBinary, ...baseAgentArgs, attemptPrompt] : [hostAgentBinary, ...baseAgentArgs];
|
|
1124
|
-
const hostAgentCommand = buildHostAgentCommand(prompt);
|
|
1125
|
-
const hostAgentCommandText = formatCommand(hostAgentCommand);
|
|
1126
|
-
const provisioningAction = startRunAction(context.projectRoot, input.runId, {
|
|
1127
|
-
actionId: `action:${input.runId}:runtime-provision`,
|
|
1128
|
-
actionType: "engine.runtime.provision",
|
|
1129
|
-
title: "Provision dedicated runtime",
|
|
1130
|
-
detail: input.taskId ? `Preparing isolated worktree for ${input.taskId}` : "Preparing isolated runtime",
|
|
1131
|
-
payload: {
|
|
1132
|
-
hostAgentCommand,
|
|
1133
|
-
formattedCommand: hostAgentCommandText,
|
|
1134
|
-
taskId: input.taskId ?? null,
|
|
1135
|
-
mode: "worktree"
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
upsertAgentAuthorityRun(context.projectRoot, {
|
|
1139
|
-
runId: input.runId,
|
|
1140
|
-
taskId: runtimeTaskId,
|
|
1141
|
-
createdAt: startedAt,
|
|
1142
|
-
runtimeAdapter: input.runtimeAdapter,
|
|
1143
|
-
status: resumeMode ? "preparing" : "created"
|
|
1144
|
-
});
|
|
1145
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1146
|
-
status: "preparing",
|
|
1147
|
-
startedAt,
|
|
1148
|
-
completedAt: null,
|
|
1149
|
-
errorText: null,
|
|
1150
|
-
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
1151
|
-
runtimeAdapter: input.runtimeAdapter,
|
|
1152
|
-
runtimeMode: input.runtimeMode,
|
|
1153
|
-
interactionMode: input.interactionMode,
|
|
1154
|
-
runMode: input.runtimeMode === "full-access" ? "autonomous" : "interactive",
|
|
1155
|
-
initialPrompt: input.initialPrompt ?? null,
|
|
1156
|
-
title: input.taskId ? undefined : input.title ?? `Run ${input.runId}`,
|
|
1157
|
-
pid: process.pid
|
|
1158
|
-
});
|
|
1159
|
-
emitServerRunEvent({
|
|
1160
|
-
type: "status",
|
|
1161
|
-
runId: input.runId,
|
|
1162
|
-
status: "preparing",
|
|
1163
|
-
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
1164
|
-
});
|
|
1165
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1166
|
-
id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
|
|
1167
|
-
title: resumeMode ? "Rig task run resumed" : "Rig task run started",
|
|
1168
|
-
detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
|
|
1169
|
-
tone: "info",
|
|
1170
|
-
status: "preparing",
|
|
1171
|
-
createdAt: startedAt
|
|
1172
|
-
});
|
|
1173
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1174
|
-
id: `log:${input.runId}:wrapper-command`,
|
|
1175
|
-
title: "Agent wrapper command",
|
|
1176
|
-
detail: hostAgentCommandText,
|
|
1177
|
-
tone: "tool",
|
|
1178
|
-
status: "preparing",
|
|
1179
|
-
payload: {
|
|
1180
|
-
command: hostAgentCommand,
|
|
1181
|
-
formattedCommand: hostAgentCommandText
|
|
147
|
+
command: typeof input.command === "function" ? input.command : runners.command,
|
|
148
|
+
gitCommand: typeof input.gitCommand === "function" ? input.gitCommand : runners.gitCommand,
|
|
149
|
+
steerPi: async (message) => {
|
|
150
|
+
appendStage?.("Review/CI", message, "running");
|
|
151
|
+
if (typeof input.steerPi === "function") {
|
|
152
|
+
await input.steerPi(message);
|
|
153
|
+
}
|
|
1182
154
|
},
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
1187
|
-
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
1188
|
-
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
1189
|
-
const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
1190
|
-
const persistedPlanning = {
|
|
1191
|
-
...planningClassification,
|
|
1192
|
-
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
1193
|
-
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
1194
|
-
classifiedAt: new Date().toISOString()
|
|
1195
|
-
};
|
|
1196
|
-
mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
1197
|
-
writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
1198
|
-
`, "utf8");
|
|
1199
|
-
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
1200
|
-
prompt = `${prompt}
|
|
1201
|
-
|
|
1202
|
-
Rig planning classification:
|
|
1203
|
-
${JSON.stringify(persistedPlanning, null, 2)}
|
|
1204
|
-
${planningClassification.planningRequired ? `Before implementing, write a concise implementation plan to ${planningArtifactPath}. Treat that plan artifact as required acceptance evidence for the Plan stage.` : "Planning is not required for this run; briefly state why before implementation."}`;
|
|
1205
|
-
if (input.runtimeAdapter === "pi") {
|
|
1206
|
-
for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
|
|
1207
|
-
appendPiStageLog({
|
|
1208
|
-
projectRoot: context.projectRoot,
|
|
1209
|
-
runId: input.runId,
|
|
1210
|
-
stage,
|
|
1211
|
-
detail: stage === "Launch Pi" ? "Pi runtime bridge starting with pi-rig environment." : stage === "Plan" ? `${planningClassification.planningRequired ? "recorded" : "skipped"} (${planningClassification.reason}; size=${planningClassification.size}; risk=${planningClassification.risk})` : stage === "Implement" ? "Pi implementation pass is running." : null,
|
|
1212
|
-
status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1217
|
-
id: `message:${input.runId}:user`,
|
|
1218
|
-
type: "user_message",
|
|
1219
|
-
text: prompt,
|
|
1220
|
-
createdAt: startedAt,
|
|
1221
|
-
state: "completed",
|
|
1222
|
-
completedAt: startedAt
|
|
1223
|
-
});
|
|
1224
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1225
|
-
await context.emitEvent("command.started", {
|
|
1226
|
-
command: [
|
|
1227
|
-
"rig",
|
|
1228
|
-
"server",
|
|
1229
|
-
"task-run",
|
|
1230
|
-
...input.taskId ? ["--task", input.taskId] : [],
|
|
1231
|
-
...input.title ? ["--title", input.title] : []
|
|
1232
|
-
],
|
|
1233
|
-
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
1234
|
-
startedAt,
|
|
1235
|
-
dryRun: false
|
|
1236
|
-
});
|
|
1237
|
-
const assistantMessageId = `message:${input.runId}:assistant`;
|
|
1238
|
-
let assistantText = "";
|
|
1239
|
-
const pendingClaudeToolUses = new Map;
|
|
1240
|
-
const pendingCodexToolUses = new Map;
|
|
1241
|
-
let launchAction;
|
|
1242
|
-
let verificationAction;
|
|
1243
|
-
let reviewAction;
|
|
1244
|
-
let verificationStarted = false;
|
|
1245
|
-
let reviewStarted = false;
|
|
1246
|
-
let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
|
|
1247
|
-
let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
|
|
1248
|
-
let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
|
|
1249
|
-
let latestProviderCommand = null;
|
|
1250
|
-
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
1251
|
-
let snapshotSidecarPromise = null;
|
|
1252
|
-
let dirtyBaselineApplied = false;
|
|
1253
|
-
const childEnv = {
|
|
1254
|
-
...process.env,
|
|
1255
|
-
...input.runtimeAdapter === "pi" ? buildPiRigBridgeEnv({
|
|
1256
|
-
projectRoot: context.projectRoot,
|
|
1257
|
-
runId: input.runId,
|
|
1258
|
-
taskId: runtimeTaskId,
|
|
1259
|
-
serverUrl: process.env.RIG_SERVER_URL ?? null,
|
|
1260
|
-
authToken: process.env.RIG_AUTH_TOKEN ?? null,
|
|
1261
|
-
githubToken: process.env.RIG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null
|
|
1262
|
-
}) : {
|
|
1263
|
-
PROJECT_RIG_ROOT: context.projectRoot,
|
|
1264
|
-
RIG_TASK_ID: runtimeTaskId,
|
|
1265
|
-
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
1266
|
-
RIG_SERVER_RUN_ID: input.runId
|
|
155
|
+
journalPhase: (phase, outcome, detail) => {
|
|
156
|
+
const status = outcome === "completed" ? "completed" : outcome === "failed" ? "needs_attention" : "running";
|
|
157
|
+
appendStage?.(phase, detail ?? null, status);
|
|
1267
158
|
},
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
|
|
1272
|
-
} : {}
|
|
1273
|
-
};
|
|
1274
|
-
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
1275
|
-
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
1276
|
-
const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
|
|
1277
|
-
const maxAttempts = automationLimits.maxValidationAttempts;
|
|
1278
|
-
const promoteToValidating = (detail) => {
|
|
1279
|
-
if (verificationStarted)
|
|
1280
|
-
return;
|
|
1281
|
-
verificationStarted = true;
|
|
1282
|
-
patchAuthorityRun(context.projectRoot, input.runId, { status: "validating" });
|
|
1283
|
-
emitServerRunEvent({ type: "status", runId: input.runId, status: "validating", detail: detail ?? null });
|
|
1284
|
-
verificationAction = startRunAction(context.projectRoot, input.runId, {
|
|
1285
|
-
actionId: `action:${input.runId}:completion-verification`,
|
|
1286
|
-
actionType: "completion-verification",
|
|
1287
|
-
title: "Run completion verification",
|
|
1288
|
-
detail: detail ?? "Rig completion-verification hook is running."
|
|
1289
|
-
});
|
|
1290
|
-
};
|
|
1291
|
-
const promoteToReviewing = (detail) => {
|
|
1292
|
-
if (reviewStarted)
|
|
1293
|
-
return;
|
|
1294
|
-
reviewStarted = true;
|
|
1295
|
-
patchAuthorityRun(context.projectRoot, input.runId, { status: "reviewing" });
|
|
1296
|
-
emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: detail ?? null });
|
|
1297
|
-
reviewAction = startRunAction(context.projectRoot, input.runId, {
|
|
1298
|
-
actionId: `action:${input.runId}:review-closeout`,
|
|
1299
|
-
actionType: "run.review",
|
|
1300
|
-
title: "Finalize run review",
|
|
1301
|
-
detail: detail ?? "Verifier review is running."
|
|
1302
|
-
});
|
|
1303
|
-
};
|
|
1304
|
-
const handleWrapperEvent = (rawPayload) => {
|
|
1305
|
-
let event = null;
|
|
1306
|
-
try {
|
|
1307
|
-
event = JSON.parse(rawPayload);
|
|
1308
|
-
} catch {
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
if (!event?.type)
|
|
1312
|
-
return false;
|
|
1313
|
-
const payload = event.payload ?? {};
|
|
1314
|
-
if (event.type === "runtime.provision.completed") {
|
|
1315
|
-
latestRuntimeWorkspace = typeof payload.workspaceDir === "string" ? payload.workspaceDir : latestRuntimeWorkspace;
|
|
1316
|
-
latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
|
|
1317
|
-
latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
|
|
1318
|
-
const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
|
|
1319
|
-
latestRuntimeBranch = runtimeId;
|
|
1320
|
-
provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
|
|
1321
|
-
runtimeId: runtimeId ?? null,
|
|
1322
|
-
workspaceDir: latestRuntimeWorkspace,
|
|
1323
|
-
sessionDir: latestSessionDir,
|
|
1324
|
-
logsDir: latestLogsDir,
|
|
1325
|
-
contextFile: payload.contextFile ?? null
|
|
1326
|
-
});
|
|
1327
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1328
|
-
status: "running",
|
|
1329
|
-
worktreePath: latestRuntimeWorkspace,
|
|
1330
|
-
artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve4(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
|
|
1331
|
-
logRoot: latestLogsDir,
|
|
1332
|
-
sessionPath: latestSessionDir ? resolve4(latestSessionDir, "session.json") : null,
|
|
1333
|
-
sessionLogPath: latestLogsDir ? resolve4(latestLogsDir, "agent-stdout.log") : null,
|
|
1334
|
-
branch: runtimeId
|
|
1335
|
-
});
|
|
1336
|
-
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
1337
|
-
dirtyBaselineApplied = true;
|
|
1338
|
-
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
1339
|
-
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
1340
|
-
if (readyFile) {
|
|
1341
|
-
mkdirSync(resolve4(readyFile, ".."), { recursive: true });
|
|
1342
|
-
writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
1343
|
-
`, "utf8");
|
|
1344
|
-
}
|
|
1345
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1346
|
-
id: `log:${input.runId}:dirty-baseline`,
|
|
1347
|
-
title: "Dirty baseline snapshot",
|
|
1348
|
-
detail: dirty.detail,
|
|
1349
|
-
tone: dirty.applied ? "tool" : "info",
|
|
1350
|
-
status: dirty.applied ? "completed" : "skipped",
|
|
1351
|
-
createdAt: new Date().toISOString(),
|
|
1352
|
-
payload: readyFile ? { readyFile } : undefined
|
|
1353
|
-
});
|
|
1354
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
|
|
1355
|
-
}
|
|
1356
|
-
if (!snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
|
|
1357
|
-
snapshotSidecarPromise = (async () => {
|
|
1358
|
-
const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
|
|
1359
|
-
projectRoot: context.projectRoot,
|
|
1360
|
-
runtimeId,
|
|
1361
|
-
workspaceDir: latestRuntimeWorkspace,
|
|
1362
|
-
listRuntimes: listAgentRuntimes,
|
|
1363
|
-
startSidecar: (runtime) => startRuntimeSnapshotSidecar(runtime, { instanceId: `${runtime.id}-run-${input.runId}` })
|
|
1364
|
-
});
|
|
1365
|
-
if (!sidecar) {
|
|
1366
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1367
|
-
id: `log:${input.runId}:snapshot-unavailable`,
|
|
1368
|
-
title: "Runtime snapshotting unavailable",
|
|
1369
|
-
detail: error ?? `Could not locate runtime metadata for ${runtimeId}.`,
|
|
1370
|
-
tone: "error",
|
|
1371
|
-
status: "running",
|
|
1372
|
-
createdAt: new Date().toISOString()
|
|
1373
|
-
});
|
|
1374
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Runtime snapshotting unavailable" });
|
|
1375
|
-
return null;
|
|
1376
|
-
}
|
|
1377
|
-
return sidecar;
|
|
1378
|
-
})();
|
|
1379
|
-
}
|
|
1380
|
-
emitServerRunEvent({
|
|
1381
|
-
type: "status",
|
|
1382
|
-
runId: input.runId,
|
|
1383
|
-
status: "running",
|
|
1384
|
-
detail: latestRuntimeWorkspace
|
|
1385
|
-
});
|
|
1386
|
-
return true;
|
|
1387
|
-
}
|
|
1388
|
-
if (event.type === "runtime.provision.failed") {
|
|
1389
|
-
const detail = typeof payload.error === "string" ? payload.error : "Runtime provisioning failed.";
|
|
1390
|
-
provisioningAction.fail(detail, payload);
|
|
1391
|
-
emitServerRunEvent({ type: "failed", runId: input.runId, error: detail });
|
|
1392
|
-
return true;
|
|
1393
|
-
}
|
|
1394
|
-
if (event.type === "provider.launch") {
|
|
1395
|
-
const command = Array.isArray(payload.command) ? payload.command.filter((value) => typeof value === "string") : [];
|
|
1396
|
-
latestProviderCommand = command.length > 0 ? command : null;
|
|
1397
|
-
const commandText = command.length > 0 ? formatCommand(command) : String(payload.provider ?? "provider");
|
|
1398
|
-
launchAction = startRunAction(context.projectRoot, input.runId, {
|
|
1399
|
-
actionId: `action:${input.runId}:provider-launch`,
|
|
1400
|
-
actionType: "agent.launch",
|
|
1401
|
-
title: "Launch agent runtime",
|
|
1402
|
-
detail: commandText,
|
|
1403
|
-
payload: {
|
|
1404
|
-
runtimeAdapter: input.runtimeAdapter,
|
|
1405
|
-
runtimeMode: input.runtimeMode,
|
|
1406
|
-
interactionMode: input.interactionMode,
|
|
1407
|
-
workspaceDir: payload.workspaceDir ?? latestRuntimeWorkspace,
|
|
1408
|
-
command,
|
|
1409
|
-
formattedCommand: commandText
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1413
|
-
id: `log:${input.runId}:provider-command`,
|
|
1414
|
-
title: "Agent launch command",
|
|
1415
|
-
detail: commandText,
|
|
1416
|
-
tone: "tool",
|
|
1417
|
-
status: "running",
|
|
1418
|
-
payload: {
|
|
1419
|
-
command,
|
|
1420
|
-
formattedCommand: commandText
|
|
1421
|
-
},
|
|
1422
|
-
createdAt: new Date().toISOString()
|
|
1423
|
-
});
|
|
1424
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Agent launch command" });
|
|
1425
|
-
return true;
|
|
1426
|
-
}
|
|
1427
|
-
if (event.type === "provider.completed") {
|
|
1428
|
-
const exitCode = typeof payload.exitCode === "number" ? payload.exitCode : null;
|
|
1429
|
-
if (launchAction) {
|
|
1430
|
-
if (exitCode === 0) {
|
|
1431
|
-
launchAction.complete("Agent runtime finished.");
|
|
1432
|
-
} else {
|
|
1433
|
-
launchAction.fail(`Agent runtime failed (${String(exitCode ?? "unknown")})`, payload);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
return true;
|
|
1437
|
-
}
|
|
1438
|
-
return false;
|
|
1439
|
-
};
|
|
1440
|
-
const nextRunLogId = createRunLogIdFactory(input.runId);
|
|
1441
|
-
const handleAgentStdoutLine = (line) => {
|
|
1442
|
-
const trimmed = line.trim();
|
|
1443
|
-
if (!trimmed)
|
|
1444
|
-
return;
|
|
1445
|
-
if (trimmed.startsWith("__RIG_WRAPPER_EVENT__")) {
|
|
1446
|
-
if (handleWrapperEvent(trimmed.slice("__RIG_WRAPPER_EVENT__".length))) {
|
|
1447
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
const completionLine = classifyCompletionVerificationLine(trimmed);
|
|
1452
|
-
if (completionLine.isCompletionLine) {
|
|
1453
|
-
promoteToValidating(trimmed);
|
|
1454
|
-
if (completionLine.isVerifierReview) {
|
|
1455
|
-
if (verificationAction) {
|
|
1456
|
-
verificationAction.complete("Completion verification checks finished.");
|
|
1457
|
-
}
|
|
1458
|
-
promoteToReviewing(trimmed);
|
|
1459
|
-
}
|
|
1460
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1461
|
-
id: nextRunLogId(),
|
|
1462
|
-
title: verificationStarted ? "Completion verification" : "Agent output",
|
|
1463
|
-
detail: trimmed,
|
|
1464
|
-
tone: "info",
|
|
1465
|
-
status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
|
|
1466
|
-
createdAt: new Date().toISOString()
|
|
1467
|
-
});
|
|
1468
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: verificationStarted ? "Completion verification" : "Agent output" });
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
try {
|
|
1472
|
-
const record = JSON.parse(trimmed);
|
|
1473
|
-
const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
|
|
1474
|
-
const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
|
|
1475
|
-
runId: input.runId,
|
|
1476
|
-
record,
|
|
1477
|
-
createdAtFallback: new Date().toISOString(),
|
|
1478
|
-
status: liveLogStatus,
|
|
1479
|
-
pendingToolUses: pendingCodexToolUses
|
|
1480
|
-
}) : buildClaudeLogsFromRecord({
|
|
1481
|
-
runId: input.runId,
|
|
1482
|
-
record,
|
|
1483
|
-
createdAtFallback: new Date().toISOString(),
|
|
1484
|
-
status: liveLogStatus,
|
|
1485
|
-
pendingToolUses: pendingClaudeToolUses
|
|
1486
|
-
});
|
|
1487
|
-
if (providerLogs.length > 0) {
|
|
1488
|
-
for (const providerLog of providerLogs) {
|
|
1489
|
-
appendRunLog(context.projectRoot, input.runId, providerLog);
|
|
1490
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
if (input.runtimeAdapter === "codex") {
|
|
1494
|
-
const nextAssistantText = extractCodexAssistantMessageText(record);
|
|
1495
|
-
if (nextAssistantText) {
|
|
1496
|
-
assistantText = nextAssistantText;
|
|
1497
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1498
|
-
id: assistantMessageId,
|
|
1499
|
-
type: "assistant_message",
|
|
1500
|
-
text: assistantText,
|
|
1501
|
-
state: "streaming",
|
|
1502
|
-
createdAt: new Date().toISOString()
|
|
1503
|
-
});
|
|
1504
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
if (providerLogs.length > 0 || isCodexExecRecord(record)) {
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
if (record.type === "message_update") {
|
|
1512
|
-
const assistantMessageEvent = record.assistantMessageEvent && typeof record.assistantMessageEvent === "object" ? record.assistantMessageEvent : null;
|
|
1513
|
-
if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
|
|
1514
|
-
assistantText += assistantMessageEvent.delta;
|
|
1515
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1516
|
-
id: assistantMessageId,
|
|
1517
|
-
type: "assistant_message",
|
|
1518
|
-
text: assistantText,
|
|
1519
|
-
state: "streaming",
|
|
1520
|
-
createdAt: new Date().toISOString()
|
|
1521
|
-
});
|
|
1522
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
if (record.type === "stream_event") {
|
|
1527
|
-
const event = record.event && typeof record.event === "object" ? record.event : null;
|
|
1528
|
-
const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
|
|
1529
|
-
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
1530
|
-
assistantText += delta.text;
|
|
1531
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1532
|
-
id: assistantMessageId,
|
|
1533
|
-
type: "assistant_message",
|
|
1534
|
-
text: assistantText,
|
|
1535
|
-
state: "streaming",
|
|
1536
|
-
createdAt: new Date().toISOString()
|
|
1537
|
-
});
|
|
1538
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
if (record.type === "assistant") {
|
|
1543
|
-
const message = record.message && typeof record.message === "object" ? record.message : record;
|
|
1544
|
-
const content = Array.isArray(message.content) ? message.content : [];
|
|
1545
|
-
const nextText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
|
|
1546
|
-
if (nextText.length > assistantText.length) {
|
|
1547
|
-
assistantText = nextText;
|
|
1548
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1549
|
-
id: assistantMessageId,
|
|
1550
|
-
type: "assistant_message",
|
|
1551
|
-
text: assistantText,
|
|
1552
|
-
state: "streaming",
|
|
1553
|
-
createdAt: new Date().toISOString()
|
|
1554
|
-
});
|
|
1555
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1556
|
-
}
|
|
1557
|
-
return;
|
|
1558
|
-
}
|
|
1559
|
-
if (providerLogs.length > 0) {
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
} catch {}
|
|
1563
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1564
|
-
id: nextRunLogId(),
|
|
1565
|
-
title: verificationStarted ? "Completion verification" : "Agent output",
|
|
1566
|
-
detail: trimmed,
|
|
1567
|
-
tone: "info",
|
|
1568
|
-
status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
|
|
1569
|
-
createdAt: new Date().toISOString()
|
|
1570
|
-
});
|
|
1571
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: verificationStarted ? "Completion verification" : "Agent output" });
|
|
1572
|
-
};
|
|
1573
|
-
let currentPrompt = prompt;
|
|
1574
|
-
let exit = null;
|
|
1575
|
-
let reviewFailureDetail = null;
|
|
1576
|
-
const stderrLines = [];
|
|
1577
|
-
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
1578
|
-
if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
|
|
1579
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1580
|
-
id: `log:${input.runId}:resume-closeout-phase`,
|
|
1581
|
-
title: "Resume continuing from closeout phase",
|
|
1582
|
-
detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
|
|
1583
|
-
tone: "info",
|
|
1584
|
-
status: resumePreviousStatus,
|
|
1585
|
-
createdAt: new Date().toISOString()
|
|
1586
|
-
});
|
|
1587
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
|
|
1588
|
-
exit = { code: 0, signal: null };
|
|
1589
|
-
} else if (resumeMode && latestRuntimeWorkspace) {
|
|
1590
|
-
const acceptedArtifactState = readTaskRunAcceptedArtifactState({
|
|
1591
|
-
taskId: input.taskId ?? runtimeTaskId,
|
|
1592
|
-
workspaceDir: latestRuntimeWorkspace
|
|
1593
|
-
});
|
|
1594
|
-
if (acceptedArtifactState.accepted) {
|
|
1595
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1596
|
-
id: `log:${input.runId}:resume-accepted-artifacts`,
|
|
1597
|
-
title: "Resume found accepted artifacts; continuing closeout",
|
|
1598
|
-
detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
|
|
1599
|
-
tone: "info",
|
|
1600
|
-
status: "validating",
|
|
1601
|
-
createdAt: new Date().toISOString()
|
|
1602
|
-
});
|
|
1603
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
|
|
1604
|
-
exit = { code: 0, signal: null };
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
|
|
1608
|
-
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
1609
|
-
const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
1610
|
-
cwd: context.projectRoot,
|
|
1611
|
-
env: childEnv,
|
|
1612
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1613
|
-
});
|
|
1614
|
-
const forwardSigterm = () => {
|
|
1615
|
-
try {
|
|
1616
|
-
child.kill("SIGTERM");
|
|
1617
|
-
} catch {}
|
|
1618
|
-
};
|
|
1619
|
-
process.once("SIGTERM", forwardSigterm);
|
|
1620
|
-
if (input.runtimeAdapter === "claude-code" || input.runtimeAdapter === "pi") {
|
|
1621
|
-
child.stdin.write(currentPrompt);
|
|
1622
|
-
child.stdin.end();
|
|
1623
|
-
} else {
|
|
1624
|
-
child.stdin.end();
|
|
1625
|
-
}
|
|
1626
|
-
const stdout = createLineInterface({ input: child.stdout });
|
|
1627
|
-
stdout.on("line", handleAgentStdoutLine);
|
|
1628
|
-
const stderr = createLineInterface({ input: child.stderr });
|
|
1629
|
-
stderr.on("line", (line) => {
|
|
1630
|
-
const trimmed = line.trim();
|
|
1631
|
-
if (!trimmed)
|
|
159
|
+
reflect: async (status, summary, options) => {
|
|
160
|
+
const github = config.github && typeof config.github === "object" && !Array.isArray(config.github) ? config.github : null;
|
|
161
|
+
if (github?.issueUpdates === "off")
|
|
1632
162
|
return;
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
});
|
|
1642
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Agent stderr" });
|
|
1643
|
-
});
|
|
1644
|
-
let acceptedArtifactExit = false;
|
|
1645
|
-
let acceptedArtifactObservedAt = null;
|
|
1646
|
-
let acceptedArtifactPollTimer = null;
|
|
1647
|
-
let acceptedArtifactKillTimer = null;
|
|
1648
|
-
const attemptExit = await new Promise((resolve5) => {
|
|
1649
|
-
let settled = false;
|
|
1650
|
-
const settle = (result) => {
|
|
1651
|
-
if (settled)
|
|
1652
|
-
return;
|
|
1653
|
-
settled = true;
|
|
1654
|
-
if (acceptedArtifactPollTimer)
|
|
1655
|
-
clearInterval(acceptedArtifactPollTimer);
|
|
1656
|
-
resolve5(result);
|
|
1657
|
-
};
|
|
1658
|
-
const pollAcceptedArtifacts = () => {
|
|
1659
|
-
const artifactState = readTaskRunAcceptedArtifactState({
|
|
1660
|
-
taskId: input.taskId ?? runtimeTaskId,
|
|
1661
|
-
workspaceDir: latestRuntimeWorkspace,
|
|
1662
|
-
artifactNotBeforeMs: Date.parse(startedAt)
|
|
1663
|
-
});
|
|
1664
|
-
if (!artifactState.accepted) {
|
|
1665
|
-
acceptedArtifactObservedAt = null;
|
|
1666
|
-
return;
|
|
1667
|
-
}
|
|
1668
|
-
acceptedArtifactObservedAt ??= Date.now();
|
|
1669
|
-
if (Date.now() - acceptedArtifactObservedAt < piAcceptedArtifactExitGraceMs || acceptedArtifactExit) {
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
acceptedArtifactExit = true;
|
|
1673
|
-
const detail = [
|
|
1674
|
-
artifactState.reason ?? "Rig accepted task artifacts are present.",
|
|
1675
|
-
`Pi has not exited after ${piAcceptedArtifactExitGraceMs}ms; terminating the stale Pi process so Rig can continue PR/issue closeout.`
|
|
1676
|
-
].join(" ");
|
|
1677
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1678
|
-
id: `log:${input.runId}:pi-accepted-artifacts-exit`,
|
|
1679
|
-
title: "Pi accepted artifacts; continuing closeout",
|
|
1680
|
-
detail,
|
|
1681
|
-
tone: "info",
|
|
1682
|
-
status: "running",
|
|
1683
|
-
createdAt: new Date().toISOString()
|
|
1684
|
-
});
|
|
1685
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi accepted artifacts; continuing closeout" });
|
|
1686
|
-
try {
|
|
1687
|
-
child.kill("SIGTERM");
|
|
1688
|
-
} catch {}
|
|
1689
|
-
acceptedArtifactKillTimer = setTimeout(() => {
|
|
1690
|
-
try {
|
|
1691
|
-
child.kill("SIGKILL");
|
|
1692
|
-
} catch {}
|
|
1693
|
-
settle({ code: 0, signal: "SIGKILL" });
|
|
1694
|
-
}, 5000);
|
|
1695
|
-
};
|
|
1696
|
-
child.once("error", (error) => {
|
|
1697
|
-
if (acceptedArtifactKillTimer)
|
|
1698
|
-
clearTimeout(acceptedArtifactKillTimer);
|
|
1699
|
-
console.error(`[rig] agent process error: ${error.message}`);
|
|
1700
|
-
settle({ code: 1, signal: null, error });
|
|
1701
|
-
});
|
|
1702
|
-
child.once("close", (code, signal) => {
|
|
1703
|
-
if (acceptedArtifactKillTimer)
|
|
1704
|
-
clearTimeout(acceptedArtifactKillTimer);
|
|
1705
|
-
settle(acceptedArtifactExit ? { code: 0, signal: signal ?? "SIGTERM" } : { code, signal });
|
|
1706
|
-
});
|
|
1707
|
-
if (input.runtimeAdapter === "pi") {
|
|
1708
|
-
acceptedArtifactPollTimer = setInterval(pollAcceptedArtifacts, 5000);
|
|
1709
|
-
acceptedArtifactPollTimer.unref?.();
|
|
1710
|
-
}
|
|
1711
|
-
});
|
|
1712
|
-
await finalizeTaskRunSnapshot({
|
|
1713
|
-
snapshotSidecarPromise,
|
|
1714
|
-
latestProviderCommand,
|
|
1715
|
-
hostAgentCommand: attemptHostAgentCommand,
|
|
1716
|
-
exit: attemptExit
|
|
1717
|
-
});
|
|
1718
|
-
const pendingLogs = input.runtimeAdapter === "codex" ? flushPendingCodexToolUseLogs({
|
|
1719
|
-
runId: input.runId,
|
|
1720
|
-
status: typeof attemptExit.code === "number" && attemptExit.code === 0 ? "completed" : "failed",
|
|
1721
|
-
pendingToolUses: pendingCodexToolUses
|
|
1722
|
-
}) : flushPendingClaudeToolUseLogs({
|
|
1723
|
-
runId: input.runId,
|
|
1724
|
-
status: typeof attemptExit.code === "number" && attemptExit.code === 0 ? "completed" : "failed",
|
|
1725
|
-
pendingToolUses: pendingClaudeToolUses
|
|
1726
|
-
});
|
|
1727
|
-
for (const pendingLog of pendingLogs) {
|
|
1728
|
-
appendRunLog(context.projectRoot, input.runId, pendingLog);
|
|
1729
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
|
|
1730
|
-
}
|
|
1731
|
-
process.off("SIGTERM", forwardSigterm);
|
|
1732
|
-
if (attemptExit.error) {
|
|
1733
|
-
throw new CliError2(`Task run failed to start: ${attemptExit.error.message}`, 1);
|
|
1734
|
-
}
|
|
1735
|
-
const retryContext = resolveTaskRunRetryContext({
|
|
1736
|
-
projectRoot: context.projectRoot,
|
|
1737
|
-
taskId: input.taskId ?? null,
|
|
1738
|
-
workspaceDir: latestRuntimeWorkspace,
|
|
1739
|
-
attempt,
|
|
1740
|
-
maxAttempts,
|
|
1741
|
-
basePrompt: prompt
|
|
1742
|
-
});
|
|
1743
|
-
if (retryContext.shouldRetry && retryContext.nextPrompt) {
|
|
1744
|
-
const failureDetail = retryContext.failureDetail ?? summarizeValidationFailure(context.projectRoot, input.taskId ?? null) ?? "Rig validation failed.";
|
|
1745
|
-
const retrySteering = buildPiValidationRetrySteeringPrompt({
|
|
1746
|
-
taskId: input.taskId ?? runtimeTaskId,
|
|
1747
|
-
attempt,
|
|
1748
|
-
maxAttempts,
|
|
1749
|
-
failureDetail,
|
|
1750
|
-
validationOutput: summarizeValidationFailure(context.projectRoot, input.taskId ?? null)
|
|
1751
|
-
});
|
|
1752
|
-
currentPrompt = [retryContext.nextPrompt, "", "Rig Pi steering message:", retrySteering].join(`
|
|
1753
|
-
`);
|
|
1754
|
-
reviewFailureDetail = failureDetail;
|
|
1755
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1756
|
-
status: "validating",
|
|
1757
|
-
completedAt: null,
|
|
1758
|
-
errorText: null
|
|
1759
|
-
});
|
|
1760
|
-
appendPiStageLog({
|
|
1761
|
-
projectRoot: context.projectRoot,
|
|
1762
|
-
runId: input.runId,
|
|
1763
|
-
stage: "Validate",
|
|
1764
|
-
detail: failureDetail,
|
|
1765
|
-
tone: "error",
|
|
1766
|
-
status: "validating"
|
|
1767
|
-
});
|
|
1768
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1769
|
-
id: `log:${input.runId}:retry:${attempt + 1}`,
|
|
1770
|
-
title: `Steering Pi to retry validation (attempt ${attempt + 1}/${maxAttempts})`,
|
|
1771
|
-
detail: retrySteering,
|
|
1772
|
-
tone: "info",
|
|
1773
|
-
status: "running",
|
|
1774
|
-
createdAt: new Date().toISOString()
|
|
1775
|
-
});
|
|
1776
|
-
emitServerRunEvent({
|
|
1777
|
-
type: "status",
|
|
1778
|
-
runId: input.runId,
|
|
1779
|
-
status: "validating",
|
|
1780
|
-
detail: failureDetail
|
|
1781
|
-
});
|
|
1782
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Validate failed" });
|
|
1783
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: `Steering Pi to retry validation (attempt ${attempt + 1}/${maxAttempts})` });
|
|
1784
|
-
continue;
|
|
1785
|
-
}
|
|
1786
|
-
exit = attemptExit;
|
|
1787
|
-
reviewFailureDetail = retryContext.failureDetail;
|
|
1788
|
-
break;
|
|
1789
|
-
}
|
|
1790
|
-
if (!exit) {
|
|
1791
|
-
throw new CliError2(`Task run for ${runtimeTaskId} exhausted ${maxAttempts} attempts without a terminal result.`, 1);
|
|
1792
|
-
}
|
|
1793
|
-
if (exit.error) {
|
|
1794
|
-
throw new CliError2(`Task run failed to start: ${exit.error.message}`, 1);
|
|
1795
|
-
}
|
|
1796
|
-
if (assistantText.trim()) {
|
|
1797
|
-
appendRunTimeline(context.projectRoot, input.runId, {
|
|
1798
|
-
id: assistantMessageId,
|
|
1799
|
-
type: "assistant_message",
|
|
1800
|
-
text: assistantText,
|
|
1801
|
-
state: "completed",
|
|
1802
|
-
createdAt: new Date().toISOString(),
|
|
1803
|
-
completedAt: new Date().toISOString()
|
|
1804
|
-
});
|
|
1805
|
-
emitServerRunEvent({ type: "timeline", runId: input.runId });
|
|
1806
|
-
}
|
|
1807
|
-
if (exit.code !== 0 || reviewFailureDetail) {
|
|
1808
|
-
const failedAt = new Date().toISOString();
|
|
1809
|
-
const failureDetail = reviewFailureDetail ?? summarizeValidationFailure(context.projectRoot, input.taskId ?? null) ?? summarizeTaskRunProcessFailure({
|
|
1810
|
-
exitCode: exit.code,
|
|
1811
|
-
signal: exit.signal,
|
|
1812
|
-
stderrLines
|
|
1813
|
-
});
|
|
1814
|
-
if (verificationAction && !reviewStarted) {
|
|
1815
|
-
verificationAction.fail(failureDetail);
|
|
1816
|
-
}
|
|
1817
|
-
if (reviewAction) {
|
|
1818
|
-
reviewAction.fail(failureDetail);
|
|
1819
|
-
}
|
|
1820
|
-
let terminalFailureDetail = failureDetail;
|
|
1821
|
-
const terminalRunStatus = reviewFailureDetail ? "needs_attention" : "failed";
|
|
1822
|
-
try {
|
|
1823
|
-
await updateTaskSourceAfterDriverRun(context.projectRoot, input.runId, input.taskId, sourceTask, "failed", terminalRunStatus === "needs_attention" ? "Rig task run needs attention." : "Rig task run failed.", {
|
|
1824
|
-
latestRuntimeWorkspace,
|
|
1825
|
-
latestLogsDir,
|
|
1826
|
-
latestSessionDir,
|
|
1827
|
-
errorText: failureDetail
|
|
1828
|
-
});
|
|
1829
|
-
} catch (error) {
|
|
1830
|
-
terminalFailureDetail = `${failureDetail}
|
|
1831
|
-
Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1832
|
-
}
|
|
1833
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1834
|
-
status: terminalRunStatus,
|
|
1835
|
-
completedAt: failedAt,
|
|
1836
|
-
errorText: terminalFailureDetail
|
|
1837
|
-
});
|
|
1838
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1839
|
-
id: `log:${input.runId}:${terminalRunStatus}`,
|
|
1840
|
-
title: terminalRunStatus === "needs_attention" ? "Task run needs attention" : "Task run failed",
|
|
1841
|
-
detail: terminalFailureDetail,
|
|
1842
|
-
tone: "error",
|
|
1843
|
-
status: terminalRunStatus,
|
|
1844
|
-
createdAt: failedAt
|
|
1845
|
-
});
|
|
1846
|
-
emitServerRunEvent({
|
|
1847
|
-
type: "failed",
|
|
1848
|
-
runId: input.runId,
|
|
1849
|
-
error: terminalFailureDetail
|
|
1850
|
-
});
|
|
1851
|
-
throw new CliError2(terminalFailureDetail, exit.code ?? 1);
|
|
1852
|
-
}
|
|
1853
|
-
if (planningClassification.planningRequired) {
|
|
1854
|
-
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
1855
|
-
const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
|
|
1856
|
-
if (!existsSync2(expectedPlanPath)) {
|
|
1857
|
-
const failedAt = new Date().toISOString();
|
|
1858
|
-
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
1859
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1860
|
-
status: "needs_attention",
|
|
1861
|
-
completedAt: failedAt,
|
|
1862
|
-
errorText: failureDetail
|
|
1863
|
-
});
|
|
1864
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1865
|
-
id: `log:${input.runId}:plan-artifact-missing`,
|
|
1866
|
-
title: "Required plan artifact missing",
|
|
1867
|
-
detail: failureDetail,
|
|
1868
|
-
tone: "error",
|
|
1869
|
-
status: "needs_attention",
|
|
1870
|
-
createdAt: failedAt
|
|
1871
|
-
});
|
|
1872
|
-
emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
|
|
1873
|
-
throw new CliError2(failureDetail, 1);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
const runPiPrFeedbackFix = async (message) => {
|
|
1877
|
-
appendPiStageLog({
|
|
1878
|
-
projectRoot: context.projectRoot,
|
|
1879
|
-
runId: input.runId,
|
|
1880
|
-
stage: "Implement",
|
|
1881
|
-
detail: "Pi is applying PR review/CI feedback.",
|
|
1882
|
-
status: "running"
|
|
1883
|
-
});
|
|
1884
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1885
|
-
id: `log:${input.runId}:pr-feedback-pi:${Date.now()}`,
|
|
1886
|
-
title: "Steering Pi from PR feedback",
|
|
1887
|
-
detail: message,
|
|
1888
|
-
tone: "info",
|
|
1889
|
-
status: "reviewing",
|
|
1890
|
-
createdAt: new Date().toISOString()
|
|
1891
|
-
});
|
|
1892
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
|
|
1893
|
-
const feedbackCommand = buildHostAgentCommand(message);
|
|
1894
|
-
const child = spawn(feedbackCommand[0], feedbackCommand.slice(1), {
|
|
1895
|
-
cwd: latestRuntimeWorkspace ?? context.projectRoot,
|
|
1896
|
-
env: childEnv,
|
|
1897
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1898
|
-
});
|
|
1899
|
-
if (input.runtimeAdapter === "claude-code" || input.runtimeAdapter === "pi") {
|
|
1900
|
-
child.stdin.write(message);
|
|
1901
|
-
}
|
|
1902
|
-
child.stdin.end();
|
|
1903
|
-
const stdout = createLineInterface({ input: child.stdout });
|
|
1904
|
-
stdout.on("line", (line) => {
|
|
1905
|
-
const trimmed = line.trim();
|
|
1906
|
-
if (!trimmed)
|
|
1907
|
-
return;
|
|
1908
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1909
|
-
id: nextRunLogId(),
|
|
1910
|
-
title: "Pi PR feedback fix output",
|
|
1911
|
-
detail: trimmed,
|
|
1912
|
-
tone: "info",
|
|
1913
|
-
status: "reviewing",
|
|
1914
|
-
createdAt: new Date().toISOString()
|
|
1915
|
-
});
|
|
1916
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix output" });
|
|
1917
|
-
});
|
|
1918
|
-
const stderr = createLineInterface({ input: child.stderr });
|
|
1919
|
-
stderr.on("line", (line) => {
|
|
1920
|
-
const trimmed = line.trim();
|
|
1921
|
-
if (!trimmed)
|
|
1922
|
-
return;
|
|
1923
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1924
|
-
id: nextRunLogId(),
|
|
1925
|
-
title: "Pi PR feedback fix stderr",
|
|
1926
|
-
detail: trimmed,
|
|
1927
|
-
tone: "error",
|
|
1928
|
-
status: "reviewing",
|
|
1929
|
-
createdAt: new Date().toISOString()
|
|
1930
|
-
});
|
|
1931
|
-
emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
|
|
1932
|
-
});
|
|
1933
|
-
const exitCode = await new Promise((resolve5) => {
|
|
1934
|
-
child.once("error", () => resolve5(1));
|
|
1935
|
-
child.once("close", (code) => resolve5(code ?? 1));
|
|
1936
|
-
});
|
|
1937
|
-
if (exitCode !== 0) {
|
|
1938
|
-
throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
|
|
1939
|
-
}
|
|
1940
|
-
};
|
|
1941
|
-
try {
|
|
1942
|
-
appendPiStageLog({
|
|
1943
|
-
projectRoot: context.projectRoot,
|
|
1944
|
-
runId: input.runId,
|
|
1945
|
-
stage: "Validate",
|
|
1946
|
-
detail: "Rig validation accepted the task run.",
|
|
1947
|
-
status: "completed"
|
|
1948
|
-
});
|
|
1949
|
-
const postValidation = await runTaskRunPostValidationLifecycle({
|
|
1950
|
-
projectRoot: context.projectRoot,
|
|
1951
|
-
runId: input.runId,
|
|
1952
|
-
taskId: input.taskId,
|
|
1953
|
-
runtimeTaskId,
|
|
1954
|
-
runtimeWorkspace: latestRuntimeWorkspace,
|
|
1955
|
-
branch: latestRuntimeBranch,
|
|
1956
|
-
config: automationConfig,
|
|
1957
|
-
sourceTask,
|
|
1958
|
-
steerPi: input.runtimeAdapter === "pi" ? runPiPrFeedbackFix : undefined,
|
|
1959
|
-
appendStage: (stage, detail, status, tone) => appendPiStageLog({
|
|
1960
|
-
projectRoot: context.projectRoot,
|
|
1961
|
-
runId: input.runId,
|
|
1962
|
-
stage,
|
|
1963
|
-
detail,
|
|
1964
|
-
status,
|
|
1965
|
-
tone
|
|
1966
|
-
})
|
|
1967
|
-
});
|
|
1968
|
-
if (postValidation.status === "needs_attention") {
|
|
1969
|
-
const failureDetail = postValidation.pr?.actionableFeedback.join(`
|
|
1970
|
-
`) || "PR automation needs attention.";
|
|
1971
|
-
const failedAt = new Date().toISOString();
|
|
1972
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1973
|
-
status: "needs_attention",
|
|
1974
|
-
completedAt: failedAt,
|
|
1975
|
-
errorText: failureDetail
|
|
1976
|
-
});
|
|
1977
|
-
emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
|
|
1978
|
-
throw new CliError2(failureDetail, 1);
|
|
1979
|
-
}
|
|
1980
|
-
} catch (error) {
|
|
1981
|
-
if (error instanceof CliError2) {
|
|
1982
|
-
throw error;
|
|
163
|
+
await updateRunTaskSourceLifecycle(projectRoot, {
|
|
164
|
+
runId,
|
|
165
|
+
taskId,
|
|
166
|
+
sourceTask,
|
|
167
|
+
worktreePath: workspace,
|
|
168
|
+
logRoot: cleanString(input.logRoot),
|
|
169
|
+
sessionPath: cleanString(input.sessionPath)
|
|
170
|
+
}, status, summary, options);
|
|
1983
171
|
}
|
|
1984
|
-
const failureDetail = `Failed to complete PR/issue closeout for ${input.taskId ?? runtimeTaskId}: ${error instanceof Error ? error.message : String(error)}`;
|
|
1985
|
-
const failedAt = new Date().toISOString();
|
|
1986
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1987
|
-
status: "failed",
|
|
1988
|
-
completedAt: failedAt,
|
|
1989
|
-
errorText: failureDetail
|
|
1990
|
-
});
|
|
1991
|
-
appendRunLog(context.projectRoot, input.runId, {
|
|
1992
|
-
id: `log:${input.runId}:pr-closeout-failed`,
|
|
1993
|
-
title: "PR/issue closeout failed",
|
|
1994
|
-
detail: failureDetail,
|
|
1995
|
-
tone: "error",
|
|
1996
|
-
status: "failed",
|
|
1997
|
-
createdAt: failedAt
|
|
1998
|
-
});
|
|
1999
|
-
emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
|
|
2000
|
-
throw new CliError2(failureDetail, 1);
|
|
2001
|
-
}
|
|
2002
|
-
if (verificationAction && !reviewStarted) {
|
|
2003
|
-
verificationAction.complete("Completion verification checks finished.");
|
|
2004
|
-
}
|
|
2005
|
-
if (!reviewAction) {
|
|
2006
|
-
promoteToReviewing("Task finished; finalizing run state.");
|
|
2007
|
-
}
|
|
2008
|
-
if (reviewAction) {
|
|
2009
|
-
reviewAction.complete("Run marked completed.");
|
|
2010
|
-
}
|
|
2011
|
-
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
2012
|
-
status: "completed",
|
|
2013
|
-
completedAt: new Date().toISOString()
|
|
2014
|
-
});
|
|
2015
|
-
emitServerRunEvent({ type: "completed", runId: input.runId });
|
|
2016
|
-
await context.emitEvent("command.finished", {
|
|
2017
|
-
command: [
|
|
2018
|
-
"rig",
|
|
2019
|
-
"server",
|
|
2020
|
-
"task-run",
|
|
2021
|
-
...input.taskId ? ["--task", input.taskId] : [],
|
|
2022
|
-
...input.title ? ["--title", input.title] : []
|
|
2023
|
-
],
|
|
2024
|
-
formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
|
|
2025
|
-
exitCode: 0,
|
|
2026
|
-
durationMs: 0,
|
|
2027
|
-
startedAt,
|
|
2028
|
-
finishedAt: new Date().toISOString()
|
|
2029
172
|
});
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
runId,
|
|
176
|
+
taskId,
|
|
177
|
+
branch,
|
|
178
|
+
merged: result.status === "merged",
|
|
179
|
+
closedIssue: result.status === "merged",
|
|
180
|
+
prUrl: result.prUrl,
|
|
181
|
+
status: result.status
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function cleanString(value) {
|
|
185
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2030
186
|
}
|
|
2031
187
|
export {
|
|
2032
188
|
taskRunStageLogId,
|
|
@@ -2034,7 +190,6 @@ export {
|
|
|
2034
190
|
runTaskRunPostValidationLifecycle,
|
|
2035
191
|
resolveTaskRunAutomationLimits,
|
|
2036
192
|
readTaskRunAcceptedArtifactState,
|
|
2037
|
-
executeRigOwnedTaskRun,
|
|
2038
193
|
createRunLogIdFactory,
|
|
2039
194
|
classifyValidationRetryOutcome,
|
|
2040
195
|
classifyPlanningNeed,
|