@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.92

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.
Files changed (118) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.js +22 -10
  3. package/dist/bin/rig.d.ts +71 -1
  4. package/dist/bin/rig.js +15078 -11169
  5. package/dist/config/rig-default-config.yml +5 -0
  6. package/dist/src/app/drone-ui.d.ts +11 -14
  7. package/dist/src/app/drone-ui.js +70 -86
  8. package/dist/src/commands/_async-ui.d.ts +1 -4
  9. package/dist/src/commands/_async-ui.js +9 -111
  10. package/dist/src/commands/_cli-format.d.ts +16 -9
  11. package/dist/src/commands/_cli-format.js +167 -295
  12. package/dist/src/commands/_connection-state.d.ts +11 -1
  13. package/dist/src/commands/_connection-state.js +50 -5
  14. package/dist/src/commands/_doctor-checks.d.ts +0 -6
  15. package/dist/src/commands/_doctor-checks.js +79 -382
  16. package/dist/src/commands/_help-catalog.d.ts +1 -1
  17. package/dist/src/commands/_help-catalog.js +217 -157
  18. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  19. package/dist/src/commands/_inprocess-services.js +102 -0
  20. package/dist/src/commands/_json-output.js +4 -0
  21. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  22. package/dist/src/commands/_lazy-reconcile.js +102 -0
  23. package/dist/src/commands/_paths.js +1 -1
  24. package/dist/src/commands/_pi-frontend.d.ts +18 -10
  25. package/dist/src/commands/_pi-frontend.js +37 -715
  26. package/dist/src/commands/_pi-install.js +18 -36
  27. package/dist/src/commands/_policy.d.ts +1 -1
  28. package/dist/src/commands/_policy.js +56 -15
  29. package/dist/src/commands/_run-bridge.d.ts +114 -0
  30. package/dist/src/commands/_run-bridge.js +387 -0
  31. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  32. package/dist/src/commands/_run-diagnostics.js +51 -0
  33. package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
  34. package/dist/src/commands/_run-driver-helpers.js +79 -283
  35. package/dist/src/commands/_run-projection.d.ts +50 -0
  36. package/dist/src/commands/_run-projection.js +349 -0
  37. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  38. package/dist/src/commands/_run-subcommands.js +31 -0
  39. package/dist/src/commands/_spinner.js +1 -1
  40. package/dist/src/commands/agent.d.ts +1 -1
  41. package/dist/src/commands/agent.js +8559 -239
  42. package/dist/src/commands/dist.d.ts +1 -1
  43. package/dist/src/commands/dist.js +27 -19
  44. package/dist/src/commands/doctor.d.ts +1 -1
  45. package/dist/src/commands/doctor.js +93 -475
  46. package/dist/src/commands/github.d.ts +1 -1
  47. package/dist/src/commands/github.js +113 -387
  48. package/dist/src/commands/inbox.d.ts +22 -24
  49. package/dist/src/commands/inbox.js +420 -691
  50. package/dist/src/commands/init.d.ts +6 -16
  51. package/dist/src/commands/init.js +334 -971
  52. package/dist/src/commands/inspect.d.ts +19 -2
  53. package/dist/src/commands/inspect.js +644 -610
  54. package/dist/src/commands/pi.d.ts +1 -1
  55. package/dist/src/commands/plugin.d.ts +1 -1
  56. package/dist/src/commands/plugin.js +486 -7
  57. package/dist/src/commands/profile-and-review.d.ts +1 -1
  58. package/dist/src/commands/profile-and-review.js +94 -56
  59. package/dist/src/commands/queue.js +1 -21
  60. package/dist/src/commands/remote.d.ts +1 -1
  61. package/dist/src/commands/remote.js +837 -14
  62. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  63. package/dist/src/commands/repo-git-harness.js +57 -14
  64. package/dist/src/commands/run.d.ts +20 -2
  65. package/dist/src/commands/run.js +17579 -1759
  66. package/dist/src/commands/server.d.ts +2 -6
  67. package/dist/src/commands/server.js +141 -723
  68. package/dist/src/commands/setup.d.ts +1 -1
  69. package/dist/src/commands/setup.js +102 -484
  70. package/dist/src/commands/stats.d.ts +13 -10
  71. package/dist/src/commands/stats.js +689 -761
  72. package/dist/src/commands/task-run-driver.d.ts +50 -88
  73. package/dist/src/commands/task-run-driver.js +116 -2717
  74. package/dist/src/commands/task.d.ts +34 -13
  75. package/dist/src/commands/task.js +668 -2523
  76. package/dist/src/commands/test.d.ts +1 -1
  77. package/dist/src/commands/triage.d.ts +11 -0
  78. package/dist/src/commands/triage.js +227 -0
  79. package/dist/src/commands/workspace.d.ts +1 -1
  80. package/dist/src/commands.d.ts +0 -16
  81. package/dist/src/commands.js +16657 -12250
  82. package/dist/src/index.js +16528 -12497
  83. package/dist/src/launcher.js +4 -0
  84. package/dist/src/operator-cli.d.ts +2 -0
  85. package/dist/src/operator-cli.js +17837 -0
  86. package/dist/src/operator-entry.d.ts +1 -0
  87. package/dist/src/operator-entry.js +3 -0
  88. package/package.json +18 -12
  89. package/dist/src/app/board.d.ts +0 -23
  90. package/dist/src/app/board.js +0 -1786
  91. package/dist/src/app/theme.d.ts +0 -47
  92. package/dist/src/app/theme.js +0 -150
  93. package/dist/src/commands/_authority-runs.d.ts +0 -22
  94. package/dist/src/commands/_authority-runs.js +0 -110
  95. package/dist/src/commands/_operator-surface.d.ts +0 -34
  96. package/dist/src/commands/_operator-surface.js +0 -220
  97. package/dist/src/commands/_operator-view.d.ts +0 -30
  98. package/dist/src/commands/_operator-view.js +0 -1070
  99. package/dist/src/commands/_preflight.d.ts +0 -22
  100. package/dist/src/commands/_preflight.js +0 -540
  101. package/dist/src/commands/_run-replay.d.ts +0 -24
  102. package/dist/src/commands/_run-replay.js +0 -142
  103. package/dist/src/commands/_server-client.d.ts +0 -186
  104. package/dist/src/commands/_server-client.js +0 -681
  105. package/dist/src/commands/_snapshot-upload.d.ts +0 -39
  106. package/dist/src/commands/_snapshot-upload.js +0 -455
  107. package/dist/src/commands/_task-picker.d.ts +0 -9
  108. package/dist/src/commands/_task-picker.js +0 -201
  109. package/dist/src/commands/browser.d.ts +0 -65
  110. package/dist/src/commands/browser.js +0 -1173
  111. package/dist/src/commands/connect.d.ts +0 -7
  112. package/dist/src/commands/connect.js +0 -419
  113. package/dist/src/commands/inspector.d.ts +0 -3
  114. package/dist/src/commands/inspector.js +0 -263
  115. package/dist/src/commands/task-report-bug.d.ts +0 -19
  116. package/dist/src/commands/task-report-bug.js +0 -1281
  117. package/dist/src/report-bug.d.ts +0 -44
  118. package/dist/src/report-bug.js +0 -260
@@ -1,2788 +1,188 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task-run-driver.ts
3
- import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2 } from "fs";
4
- import { resolve as resolve6 } from "path";
5
- 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 as RuntimeCliError } from "@rig/runtime/control-plane/errors";
11
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
12
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
13
-
14
- class CliError extends RuntimeCliError {
15
- hint;
16
- constructor(message, exitCode = 1, options = {}) {
17
- super(message, exitCode);
18
- if (options.hint?.trim()) {
19
- this.hint = options.hint.trim();
20
- }
21
- }
22
- }
23
- function formatCommand(parts) {
24
- return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
25
- }
26
-
27
- // packages/cli/src/commands/task-run-driver.ts
28
- import { loadConfig } from "@rig/core/load-config";
29
- import {
30
- listAgentRuntimes,
31
- startRuntimeSnapshotSidecar
32
- } from "@rig/runtime/control-plane/runtime/isolation";
33
- import {
34
- finalizeTaskRunSnapshot,
35
- resolveTaskRunSnapshotSidecar
36
- } from "@rig/runtime/control-plane/runtime/snapshot/task-run";
37
- import { loadRuntimeSnapshotConfig } from "@rig/runtime/control-plane/runtime/guard";
38
- import {
39
- buildClaudeLogsFromRecord,
40
- flushPendingClaudeToolUseLogs
41
- } from "@rig/runtime/control-plane/provider/claude-stream-records";
42
- import {
43
- buildCodexLogsFromRecord,
44
- extractCodexAssistantMessageText,
45
- flushPendingCodexToolUseLogs,
46
- isCodexExecRecord
47
- } from "@rig/runtime/control-plane/provider/codex-exec-records";
48
- import { resolvePreferredShellBinary } from "@rig/runtime/control-plane/native/run-ops";
49
- import { readAuthorityRun as readAuthorityRun3, readJsonFile as readJsonFile2, resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
50
- import {
51
- buildTaskRunLifecycleComment
52
- } from "@rig/runtime/control-plane/tasks/source-lifecycle";
53
- import {
54
- closeIssueAfterMergedPr,
55
- commitRunChanges,
56
- pushBranchSyncedWithOrigin,
57
- runPrAutomation
58
- } from "@rig/runtime/control-plane/native/pr-automation";
59
-
60
- // packages/cli/src/commands/_run-driver-helpers.ts
61
- import { readFileSync } from "fs";
62
- import { resolve as resolve3 } from "path";
63
- import {
64
- appendRunJournalEvent,
65
- appendRunLogEntry,
66
- appendRunTimelineEntry,
67
- patchAuthorityRunRecord,
68
- readAuthorityRun as readAuthorityRun2,
69
- resolveAuthorityRunDir,
70
- setAuthorityRunStatus,
71
- writeJsonFile
72
- } from "@rig/runtime/control-plane/authority-files";
73
- import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
74
- import { readPriorPrProgressPromptSection } from "@rig/runtime/control-plane/prompt-context";
75
- import { buildProviderTaskRunInstructionLines } from "@rig/runtime/control-plane/provider/runtime-instructions";
76
- import { loadRigTaskRunSkillBody } from "@rig/runtime/control-plane/provider/rig-task-run-skill";
77
-
78
- // packages/cli/src/commands/_authority-runs.ts
79
- import { existsSync } from "fs";
80
- import { resolve as resolve2 } from "path";
81
- import {
82
- readAuthorityRun,
83
- readJsonlFile,
84
- writeAuthorityRunRecord
85
- } from "@rig/runtime/control-plane/authority-files";
86
-
87
- // packages/cli/src/commands/_paths.ts
3
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "fs";
88
4
  import { resolve } from "path";
89
- import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
90
- function resolveControlPlaneMonorepoRoot(projectRoot) {
91
- return resolveMonorepoRoot(projectRoot);
92
- }
93
- function resolveControlPlaneTaskConfigPath(projectRoot) {
94
- return resolve(resolveControlPlaneMonorepoRoot(projectRoot), ".rig", "task-config.json");
95
- }
96
-
97
- // packages/cli/src/commands/_authority-runs.ts
98
- var RIG_WORKSPACE_ID = "rig-local-workspace";
99
- function normalizeRuntimeAdapter(value) {
100
- const normalized = value?.trim().toLowerCase();
101
- if (!normalized) {
102
- return "pi";
103
- }
104
- if (normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || normalized === "gpt-codex") {
105
- return "codex";
106
- }
107
- if (normalized === "pi" || normalized === "rig-pi" || normalized === "@rig/pi") {
108
- return "pi";
109
- }
110
- return "claude-code";
111
- }
112
- function readLatestBeadRecord(projectRoot, taskId) {
113
- const issuesPath = resolve2(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");
114
- if (!existsSync(issuesPath)) {
115
- return null;
116
- }
117
- let latest = null;
118
- for (const issue of readJsonlFile(issuesPath)) {
119
- if (!issue || typeof issue !== "object") {
120
- continue;
121
- }
122
- const record = issue;
123
- if (record.id === taskId) {
124
- latest = record;
125
- }
126
- }
127
- return latest;
128
- }
129
- function resolveTaskTitleForAuthorityRun(projectRoot, taskId) {
130
- try {
131
- const record = readLatestBeadRecord(projectRoot, taskId);
132
- const title = record && typeof record.title === "string" ? record.title.trim() : "";
133
- if (title) {
134
- return title;
135
- }
136
- } catch {}
137
- return null;
138
- }
139
- function upsertAgentAuthorityRun(projectRoot, input) {
140
- const current = readAuthorityRun(projectRoot, input.runId);
141
- const existing = current;
142
- const createdAt = existing?.createdAt ?? input.createdAt;
143
- const runtimeMode = typeof existing?.runtimeMode === "string" ? existing.runtimeMode : "full-access";
144
- const interactionMode = typeof existing?.interactionMode === "string" ? existing.interactionMode : "default";
145
- const runMode = typeof existing?.runMode === "string" ? existing.runMode : "autonomous";
146
- const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
147
- const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
148
- const next = {
149
- ...existing ?? {},
150
- runId: input.runId,
151
- projectRoot,
152
- workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
153
- taskId: input.taskId,
154
- threadId: existing?.threadId ?? null,
155
- mode: "local",
156
- runtimeAdapter,
157
- status: input.status,
158
- createdAt,
159
- startedAt: input.startedAt ?? existing?.startedAt ?? null,
160
- completedAt: input.completedAt ?? existing?.completedAt ?? null,
161
- endpointId: existing?.endpointId ?? null,
162
- hostId: existing?.hostId ?? null,
163
- worktreePath: input.worktreePath ?? existing?.worktreePath ?? null,
164
- artifactRoot: input.artifactRoot ?? existing?.artifactRoot ?? null,
165
- logRoot: input.logRoot ?? existing?.logRoot ?? null,
166
- sessionPath: input.sessionPath ?? existing?.sessionPath ?? null,
167
- sessionLogPath: input.sessionLogPath ?? existing?.sessionLogPath ?? null,
168
- pid: input.pid ?? existing?.pid ?? null,
169
- updatedAt: input.createdAt,
170
- title,
171
- model: typeof existing?.model === "string" ? existing.model : null,
172
- runtimeMode,
173
- interactionMode,
174
- runMode,
175
- initialPrompt: typeof existing?.initialPrompt === "string" ? existing.initialPrompt : null
176
- };
177
- if (input.errorText !== undefined) {
178
- next.errorText = input.errorText;
179
- } else if ("errorText" in next) {
180
- delete next.errorText;
181
- }
182
- writeAuthorityRunRecord(projectRoot, input.runId, next);
183
- return next;
184
- }
185
-
186
- // packages/cli/src/commands/_run-driver-helpers.ts
187
- function patchAuthorityRun(projectRoot, runId, patch) {
188
- const next = patchAuthorityRunRecord(projectRoot, runId, patch);
189
- if (!next) {
190
- throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
191
- }
192
- return next;
193
- }
194
- function setRunStatusOrFail(projectRoot, runId, to, options = {}) {
195
- const next = setAuthorityRunStatus(projectRoot, runId, to, {
196
- ...options,
197
- actor: { kind: "agent" }
198
- });
199
- if (!next) {
200
- throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
201
- }
202
- return next;
203
- }
204
- function touchAuthorityRun(projectRoot, runId) {
205
- const current = readAuthorityRun2(projectRoot, runId);
206
- if (!current) {
207
- return;
208
- }
209
- writeJsonFile(resolve3(resolveAuthorityRunDir(projectRoot, runId), "run.json"), {
210
- ...current,
211
- updatedAt: new Date().toISOString()
212
- });
213
- }
214
- function appendRunTimeline(projectRoot, runId, value) {
215
- appendRunTimelineEntry(projectRoot, runId, value);
216
- touchAuthorityRun(projectRoot, runId);
217
- }
218
- function appendRunLog(projectRoot, runId, value) {
219
- appendRunLogEntry(projectRoot, runId, value);
220
- touchAuthorityRun(projectRoot, runId);
221
- }
222
- function appendRunAction(projectRoot, runId, value) {
223
- appendRunTimelineEntry(projectRoot, runId, {
224
- id: value.id,
225
- type: "action",
226
- actionType: value.actionType,
227
- title: value.title,
228
- detail: value.detail ?? null,
229
- state: value.state,
230
- createdAt: value.startedAt,
231
- completedAt: value.completedAt ?? null,
232
- payload: value.payload ?? {}
233
- });
234
- }
235
- function startRunAction(projectRoot, runId, input) {
236
- const startedAt = new Date().toISOString();
237
- appendRunAction(projectRoot, runId, {
238
- id: input.actionId,
239
- actionType: input.actionType,
240
- title: input.title,
241
- detail: input.detail,
242
- state: "running",
243
- startedAt,
244
- payload: input.payload
245
- });
246
- emitServerRunEvent({ type: "timeline", runId });
247
- return {
248
- startedAt,
249
- complete: (detail, payload) => {
250
- appendRunAction(projectRoot, runId, {
251
- id: input.actionId,
252
- actionType: input.actionType,
253
- title: input.title,
254
- detail: detail ?? input.detail ?? null,
255
- state: "completed",
256
- startedAt,
257
- completedAt: new Date().toISOString(),
258
- payload: payload ?? input.payload
259
- });
260
- emitServerRunEvent({ type: "timeline", runId });
261
- },
262
- fail: (detail, payload) => {
263
- appendRunAction(projectRoot, runId, {
264
- id: input.actionId,
265
- actionType: input.actionType,
266
- title: input.title,
267
- detail,
268
- state: "failed",
269
- startedAt,
270
- completedAt: new Date().toISOString(),
271
- payload: payload ?? input.payload
272
- });
273
- emitServerRunEvent({ type: "timeline", runId });
274
- }
275
- };
276
- }
277
- var runEventSinkProjectRoot = null;
278
- function configureRunEventSink(projectRoot) {
279
- runEventSinkProjectRoot = projectRoot;
280
- }
281
- var lastDoorbellRangAt = 0;
282
- function ringServerRunDoorbell(runId) {
283
- const serverUrl = process.env.RIG_SERVER_URL?.trim();
284
- if (!serverUrl)
285
- return;
286
- const now = Date.now();
287
- if (now - lastDoorbellRangAt < 250)
288
- return;
289
- lastDoorbellRangAt = now;
290
- const token = process.env.RIG_AUTH_TOKEN || process.env.RIG_GITHUB_TOKEN || "";
291
- fetch(`${serverUrl.replace(/\/+$/, "")}/api/runs/doorbell`, {
292
- method: "POST",
293
- headers: {
294
- "content-type": "application/json",
295
- authorization: `Bearer ${token}`
296
- },
297
- body: JSON.stringify({ runId }),
298
- signal: AbortSignal.timeout(1000)
299
- }).catch(() => {});
300
- }
301
- function emitServerRunEvent(event) {
302
- console.log(`__RIG_RUN_EVENT__${JSON.stringify({ ...event, at: new Date().toISOString() })}`);
303
- if (runEventSinkProjectRoot) {
304
- try {
305
- if (event.type === "status" || event.type === "completed" || event.type === "failed") {
306
- appendRunJournalEvent(runEventSinkProjectRoot, event.runId, {
307
- type: "timeline-entry",
308
- payload: { kind: "driver-event", ...event }
309
- });
310
- }
311
- } catch {}
312
- ringServerRunDoorbell(event.runId);
313
- }
314
- }
315
- function buildRunPrompt(input) {
316
- const initialPrompt = input.initialPrompt?.trim() || null;
317
- if (!input.taskId) {
318
- return initialPrompt ?? input.fallbackTitle?.trim() ?? "Continue the requested Rig run and complete the work.";
319
- }
320
- const issues = (() => {
321
- try {
322
- return taskLookup(input.projectRoot, input.taskId);
323
- } catch {
324
- return "";
325
- }
326
- })();
327
- const scopeText = (() => {
328
- try {
329
- const parsed = JSON.parse(readFileSync(resolveControlPlaneTaskConfigPath(input.projectRoot), "utf8"));
330
- const entry = parsed[input.taskId] ?? {};
331
- const scope = Array.isArray(entry.scope) ? entry.scope.filter((item) => typeof item === "string") : [];
332
- const validation = Array.isArray(entry.validation) ? entry.validation.filter((item) => typeof item === "string") : [];
333
- const lines = [];
334
- if (scope.length > 0)
335
- lines.push(`Scope:
336
- - ${scope.join(`
337
- - `)}`);
338
- if (validation.length > 0)
339
- lines.push(`Validation:
340
- - ${validation.join(`
341
- - `)}`);
342
- return lines.join(`
343
-
344
- `);
345
- } catch {
346
- return "";
347
- }
348
- })();
349
- const bead = readLatestBeadRecord(input.projectRoot, input.taskId);
350
- const sourceTask = input.sourceTask ?? null;
351
- const sourceDescription = firstPromptString(sourceTask?.description, sourceTask?.body);
352
- const sourceAcceptance = firstPromptString(sourceTask?.acceptanceCriteria, sourceTask?.acceptance_criteria);
353
- const sourceValidation = uniqueStrings([...sourceTask?.validation ?? [], ...sourceTask?.validators ?? []]);
354
- const title = (bead && typeof bead === "object" && typeof bead.title === "string" ? bead.title : null) ?? firstPromptString(sourceTask?.title) ?? input.fallbackTitle ?? issues ?? input.taskId;
355
- const description = (bead && typeof bead === "object" && typeof bead.description === "string" ? bead.description : null) ?? sourceDescription ?? input.fallbackDescription ?? "";
356
- const acceptance = bead && typeof bead === "object" && typeof bead.acceptance_criteria === "string" ? bead.acceptance_criteria : sourceAcceptance ?? input.fallbackAcceptanceCriteria ?? "";
357
- const sourceContractLines = renderSourceTaskContract(sourceTask, sourceValidation);
358
- const providerLines = buildProviderTaskRunInstructionLines(input.runtimeAdapter);
359
- const skillBody = loadRigTaskRunSkillBody();
360
- return [
361
- `You are working on task ${input.taskId}: ${title}.`,
362
- description ? `Description:
363
- ${description}` : null,
364
- acceptance ? `Acceptance criteria:
365
- ${acceptance}` : null,
366
- sourceContractLines.length > 0 ? `Source task contract:
367
- ${sourceContractLines.join(`
368
- `)}` : null,
369
- scopeText || renderSourceScopeValidation(sourceTask, sourceValidation) || null,
370
- readPriorPrProgressPromptSection(input.projectRoot, input.taskId),
371
- initialPrompt ? `Additional operator guidance:
372
- ${initialPrompt}` : null,
373
- providerLines.length > 0 ? `Provider-specific runtime tooling:
374
- ${providerLines.join(" ")}` : null,
375
- skillBody || null
376
- ].filter((value) => Boolean(value)).join(`
377
-
378
- `);
379
- }
380
- function firstPromptString(...values) {
381
- for (const value of values) {
382
- const trimmed = typeof value === "string" ? value.trim() : "";
383
- if (trimmed.length > 0) {
384
- return trimmed;
385
- }
386
- }
387
- return null;
388
- }
389
- function uniqueStrings(values) {
390
- return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
391
- }
392
- function renderSourceTaskContract(task, validation) {
393
- if (!task) {
394
- return [];
395
- }
396
- const lines = [];
397
- const sourceIssueId = firstPromptString(task.sourceIssueId);
398
- if (sourceIssueId)
399
- lines.push(`Source issue: ${sourceIssueId}`);
400
- const status = firstPromptString(task.status);
401
- if (status)
402
- lines.push(`Source status: ${status}`);
403
- const issueType = firstPromptString(task.issueType);
404
- if (issueType)
405
- lines.push(`Issue type: ${issueType}`);
406
- const role = firstPromptString(task.role);
407
- if (role)
408
- lines.push(`Role: ${role}`);
409
- const externalRef = firstPromptString(task.externalRef);
410
- if (externalRef)
411
- lines.push(`External ref: ${externalRef}`);
412
- const labels = uniqueStrings(task.labels ?? []);
413
- if (labels.length > 0)
414
- lines.push(`Labels:
415
- - ${labels.join(`
416
- - `)}`);
417
- if (validation.length > 0)
418
- lines.push(`Validators:
419
- - ${validation.join(`
420
- - `)}`);
421
- return lines;
422
- }
423
- function renderSourceScopeValidation(task, validation) {
424
- if (!task) {
425
- return "";
426
- }
427
- const lines = [];
428
- const scope = uniqueStrings(task.scope ?? []);
429
- if (scope.length > 0)
430
- lines.push(`Scope:
431
- - ${scope.join(`
432
- - `)}`);
433
- if (validation.length > 0)
434
- lines.push(`Validation:
435
- - ${validation.join(`
436
- - `)}`);
437
- return lines.join(`
438
-
439
- `);
440
- }
441
-
442
- // packages/cli/src/commands/_server-client.ts
443
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
444
- import { resolve as resolve5 } from "path";
445
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
446
-
447
- // packages/cli/src/commands/_connection-state.ts
448
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
449
- import { homedir } from "os";
450
- import { dirname, resolve as resolve4 } from "path";
451
- function resolveGlobalConnectionsPath(env = process.env) {
452
- const explicit = env.RIG_CONNECTIONS_FILE?.trim();
453
- if (explicit)
454
- return resolve4(explicit);
455
- const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
456
- if (stateDir)
457
- return resolve4(stateDir, "connections.json");
458
- return resolve4(homedir(), ".rig", "connections.json");
459
- }
460
- function resolveRepoConnectionPath(projectRoot) {
461
- return resolve4(projectRoot, ".rig", "state", "connection.json");
462
- }
463
- function readJsonFile(path) {
464
- if (!existsSync2(path))
465
- return null;
466
- try {
467
- return JSON.parse(readFileSync2(path, "utf8"));
468
- } catch (error) {
469
- throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
470
- }
471
- }
472
- function writeJsonFile2(path, value) {
473
- mkdirSync(dirname(path), { recursive: true });
474
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
475
- `, "utf8");
476
- }
477
- function normalizeConnection(value) {
478
- if (!value || typeof value !== "object" || Array.isArray(value))
479
- return null;
480
- const record = value;
481
- if (record.kind === "local")
482
- return { kind: "local", mode: "auto" };
483
- if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
484
- const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
485
- return { kind: "remote", baseUrl };
486
- }
487
- return null;
488
- }
489
- function readGlobalConnections(options = {}) {
490
- const path = resolveGlobalConnectionsPath(options.env ?? process.env);
491
- const payload = readJsonFile(path);
492
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
493
- return { connections: {} };
494
- }
495
- const rawConnections = payload.connections;
496
- const connections = {};
497
- if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
498
- for (const [alias, raw] of Object.entries(rawConnections)) {
499
- const connection = normalizeConnection(raw);
500
- if (connection)
501
- connections[alias] = connection;
502
- }
503
- }
504
- return { connections };
505
- }
506
- function readRepoConnection(projectRoot) {
507
- const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
508
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
509
- return null;
510
- const record = payload;
511
- const selected = typeof record.selected === "string" ? record.selected.trim() : "";
512
- if (!selected)
513
- return null;
514
- return {
515
- selected,
516
- project: typeof record.project === "string" ? record.project : undefined,
517
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
518
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
519
- };
520
- }
521
- function writeRepoConnection(projectRoot, state) {
522
- writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
523
- }
524
- function resolveSelectedConnection(projectRoot, options = {}) {
525
- const repo = readRepoConnection(projectRoot);
526
- if (!repo)
527
- return null;
528
- if (repo.selected === "local")
529
- return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
530
- const global = readGlobalConnections(options);
531
- const connection = global.connections[repo.selected];
532
- if (!connection) {
533
- throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
534
- }
535
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
536
- }
537
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
538
- const repo = readRepoConnection(projectRoot);
539
- if (!repo)
540
- return;
541
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
542
- }
543
-
544
- // packages/cli/src/commands/_server-client.ts
545
- var scopedGitHubBearerTokens = new Map;
546
- var serverPhaseListener = null;
547
- function reportServerPhase(label) {
548
- serverPhaseListener?.(label);
549
- }
550
- function cleanToken(value) {
551
- const trimmed = value?.trim();
552
- return trimmed ? trimmed : null;
553
- }
554
- function readPrivateRemoteSessionToken(projectRoot) {
555
- const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
556
- if (!existsSync3(path))
557
- return null;
558
- try {
559
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
560
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
561
- } catch {
562
- return null;
563
- }
564
- }
565
- function readGitHubBearerTokenForRemote(projectRoot) {
566
- const scopedKey = resolve5(projectRoot);
567
- if (scopedGitHubBearerTokens.has(scopedKey))
568
- return scopedGitHubBearerTokens.get(scopedKey) ?? null;
569
- const privateSession = readPrivateRemoteSessionToken(projectRoot);
570
- if (privateSession)
571
- return privateSession;
572
- return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
573
- }
574
- function readStoredGitHubAuthToken(projectRoot) {
575
- const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
576
- if (!existsSync3(path))
577
- return null;
578
- try {
579
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
580
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
581
- } catch {
582
- return null;
583
- }
584
- }
585
- function readLocalConnectionFallbackToken(projectRoot) {
586
- return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
587
- }
588
- async function ensureServerForCli(projectRoot) {
589
- try {
590
- const selected = resolveSelectedConnection(projectRoot);
591
- if (selected?.connection.kind === "remote") {
592
- reportServerPhase(`Connecting to ${selected.alias}\u2026`);
593
- const authToken = readGitHubBearerTokenForRemote(projectRoot);
594
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
595
- return {
596
- baseUrl: selected.connection.baseUrl,
597
- authToken,
598
- connectionKind: "remote",
599
- serverProjectRoot
600
- };
601
- }
602
- reportServerPhase("Starting local Rig server\u2026");
603
- const connection = await ensureLocalRigServerConnection(projectRoot);
604
- return {
605
- baseUrl: connection.baseUrl,
606
- authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
607
- connectionKind: "local",
608
- serverProjectRoot: resolve5(projectRoot)
609
- };
610
- } catch (error) {
611
- if (error instanceof Error) {
612
- throw new CliError(error.message, 1);
613
- }
614
- throw error;
615
- }
616
- }
617
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
618
- const repo = readRepoConnection(projectRoot);
619
- const slug = repo?.project?.trim();
620
- if (!slug)
621
- return null;
622
- try {
623
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
624
- headers: mergeHeaders(undefined, authToken)
625
- });
626
- if (!response.ok)
627
- return null;
628
- const payload = await response.json();
629
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
630
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
631
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
632
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
633
- if (path)
634
- writeRepoServerProjectRoot(projectRoot, path);
635
- return path;
636
- } catch {
637
- return null;
638
- }
639
- }
640
- function mergeHeaders(headers, authToken) {
641
- const merged = new Headers(headers);
642
- if (authToken) {
643
- merged.set("authorization", `Bearer ${authToken}`);
644
- }
645
- return merged;
646
- }
647
- function diagnosticMessage(payload) {
648
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
649
- return null;
650
- const record = payload;
651
- const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
652
- const messages = diagnostics.flatMap((entry) => {
653
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
654
- return [];
655
- const diagnostic = entry;
656
- const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
657
- const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
658
- return message ? [`${kind}: ${message}`] : [];
659
- });
660
- return messages.length > 0 ? messages.join("; ") : null;
661
- }
662
- var serverReachabilityCache = new Map;
663
- async function probeServerReachability(baseUrl, authToken) {
664
- try {
665
- const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
666
- headers: mergeHeaders(undefined, authToken),
667
- signal: AbortSignal.timeout(1500)
668
- });
669
- return response.ok;
670
- } catch {
671
- return false;
672
- }
673
- }
674
- function cachedServerReachability(projectRoot, baseUrl, authToken) {
675
- const key = resolve5(projectRoot);
676
- const cached = serverReachabilityCache.get(key);
677
- if (cached)
678
- return cached;
679
- const probe = probeServerReachability(baseUrl, authToken);
680
- serverReachabilityCache.set(key, probe);
681
- return probe;
682
- }
683
- function describeSelectedServer(projectRoot, server) {
684
- try {
685
- const selected = resolveSelectedConnection(projectRoot);
686
- if (selected) {
687
- return {
688
- alias: selected.alias,
689
- target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
690
- };
691
- }
692
- } catch {}
693
- return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
694
- }
695
- async function buildServerFailureContext(projectRoot, server) {
696
- const { alias, target } = describeSelectedServer(projectRoot, server);
697
- const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
698
- const reachability = reachable ? "server is reachable" : "server is unreachable";
699
- return {
700
- contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
701
- hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
702
- };
703
- }
704
- async function requestServerJson(context, pathname, init = {}) {
705
- const server = await ensureServerForCli(context.projectRoot);
706
- const headers = mergeHeaders(init.headers, server.authToken);
707
- if (server.serverProjectRoot)
708
- headers.set("x-rig-project-root", server.serverProjectRoot);
709
- reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
710
- let response;
711
- try {
712
- response = await fetch(`${server.baseUrl}${pathname}`, {
713
- ...init,
714
- headers
715
- });
716
- } catch (error) {
717
- const failure = await buildServerFailureContext(context.projectRoot, server);
718
- throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
719
- ${failure.contextLine}`, 1, { hint: failure.hint });
720
- }
721
- const text = await response.text();
722
- const payload = text.trim().length > 0 ? (() => {
723
- try {
724
- return JSON.parse(text);
725
- } catch {
726
- return null;
727
- }
728
- })() : null;
729
- if (!response.ok) {
730
- const diagnostics = diagnosticMessage(payload);
731
- const rawDetail = diagnostics ?? (text || response.statusText);
732
- const detail = diagnostics ? rawDetail : rawDetail.split(`
733
- `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
734
- const failure = await buildServerFailureContext(context.projectRoot, server);
735
- throw new CliError(`Rig server request failed (${response.status}): ${detail}
736
- ${failure.contextLine}`, 1, { hint: failure.hint });
737
- }
738
- return payload;
739
- }
740
- async function updateWorkspaceTaskViaServer(context, input) {
741
- const payload = await requestServerJson(context, "/api/tasks/update", {
742
- method: "POST",
743
- headers: { "content-type": "application/json" },
744
- body: JSON.stringify(input)
745
- });
746
- return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
747
- }
748
- var RESUMABLE_RUN_STATUSES = new Set([
749
- "created",
750
- "preparing",
751
- "running",
752
- "validating",
753
- "reviewing",
754
- "stopped",
755
- "failed",
756
- "needs-attention",
757
- "needs_attention"
758
- ]);
759
-
760
- // packages/cli/src/commands/task-run-driver.ts
761
- var PI_CANONICAL_RUN_STAGES = [
762
- "Connect",
763
- "GitHub/task sync",
764
- "Prepare workspace",
765
- "Launch Pi",
766
- "Plan",
767
- "Implement",
768
- "Validate",
769
- "Commit",
770
- "Open PR",
771
- "Review/CI",
772
- "Merge",
773
- "Complete"
774
- ];
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"];
775
11
  function canonicalPiRunStages() {
776
12
  return [...PI_CANONICAL_RUN_STAGES];
777
13
  }
778
- function looksLikeGitHubToken(value) {
779
- const token = value?.trim();
780
- if (!token)
781
- return false;
782
- return /^(gh[opusr]_|github_pat_)/.test(token);
783
- }
784
- function isSensitivePayloadKey(key) {
785
- return /token|secret|password|authorization|credential/i.test(key);
786
- }
787
- function redactSensitivePayload(value) {
788
- if (Array.isArray(value))
789
- return value.map(redactSensitivePayload);
790
- if (!value || typeof value !== "object")
791
- return value;
792
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [
793
- key,
794
- isSensitivePayloadKey(key) && typeof child === "string" && child.trim() ? "[redacted]" : redactSensitivePayload(child)
795
- ]));
796
- }
797
- function githubBridgeEnv(token) {
798
- const clean = token?.trim();
799
- if (!clean)
800
- return {};
801
- return {
802
- RIG_GITHUB_TOKEN: clean,
803
- GITHUB_TOKEN: clean,
804
- GH_TOKEN: clean
805
- };
806
- }
807
14
  function buildPiRigBridgeEnv(input) {
808
- const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
809
15
  return {
810
- RIG_PROJECT_ROOT: input.projectRoot,
811
16
  PROJECT_RIG_ROOT: input.projectRoot,
812
17
  RIG_RUN_ID: input.runId,
813
- RIG_SERVER_RUN_ID: input.runId,
814
- RIG_TASK_ID: input.taskId,
815
- RIG_RUNTIME_ADAPTER: "pi",
816
- RIG_STEERING_POLL_MS: "0",
817
- ...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
818
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
819
- ...githubBridgeEnv(githubToken)
820
- };
821
- }
822
- function runGitSync(cwd, args, input) {
823
- const result = spawnSync("git", [...args], {
824
- cwd,
825
- input,
826
- encoding: "utf8",
827
- stdio: input === undefined ? ["ignore", "pipe", "pipe"] : ["pipe", "pipe", "pipe"]
828
- });
829
- return {
830
- ok: result.status === 0,
831
- stdout: result.stdout || "",
832
- 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 } : {}
833
21
  };
834
22
  }
835
- function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
836
- const listed = runGitSync(sourceRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
837
- if (!listed.ok || !listed.stdout)
23
+ function copyTree(sourceRoot, targetRoot) {
24
+ if (!existsSync(sourceRoot))
838
25
  return 0;
839
26
  let copied = 0;
840
- for (const relativePath of listed.stdout.split("\x00").filter(Boolean)) {
841
- const sourcePath = resolve6(sourceRoot, relativePath);
842
- const targetPath = resolve6(targetRoot, relativePath);
843
- try {
844
- if (!statSync(sourcePath).isFile())
845
- continue;
846
- mkdirSync2(resolve6(targetPath, ".."), { recursive: true });
847
- copyFileSync(sourcePath, targetPath);
848
- copied += 1;
849
- } catch {}
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;
850
38
  }
851
39
  return copied;
852
40
  }
853
41
  function applyDirtyBaselineSnapshot(input) {
854
- const diff = runGitSync(input.sourceRoot, ["diff", "--binary", "HEAD"]);
855
- if (!diff.ok) {
856
- return { applied: false, copiedUntracked: 0, detail: diff.stderr || "git diff failed" };
857
- }
858
- let applied = false;
859
- if (diff.stdout.trim()) {
860
- const apply = runGitSync(input.targetRoot, ["apply", "--whitespace=nowarn", "-"], diff.stdout);
861
- if (!apply.ok) {
862
- return { applied: false, copiedUntracked: 0, detail: apply.stderr || "git apply failed" };
863
- }
864
- applied = true;
865
- }
866
- const copiedUntracked = copyUntrackedDirtyFiles(input.sourceRoot, input.targetRoot);
42
+ mkdirSync(input.targetRoot, { recursive: true });
43
+ const copied = copyTree(input.sourceRoot, input.targetRoot);
867
44
  return {
868
- applied: applied || copiedUntracked > 0,
869
- copiedUntracked,
870
- detail: applied || copiedUntracked > 0 ? `Applied dirty baseline snapshot (${copiedUntracked} untracked files).` : "No dirty baseline changes to apply."
45
+ applied: copied > 0,
46
+ copiedUntracked: copied,
47
+ detail: copied > 0 ? `Copied ${copied} file(s) into the runtime workspace.` : "No files copied."
871
48
  };
872
49
  }
873
50
  function buildTaskRunReviewEnv(config) {
874
- const review = config?.review;
875
51
  return {
876
- ...review?.mode ? { AI_REVIEW_MODE: review.mode } : {},
877
- ...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 } : {}
878
54
  };
879
55
  }
880
56
  function buildDirtyBaselineHandshakeEnv(input) {
881
- if (input.baselineMode !== "dirty-snapshot")
882
- return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
883
57
  return {
884
- RIG_BASELINE_MODE: "dirty-snapshot",
885
- RIG_DIRTY_BASELINE_READY_FILE: resolve6(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
58
+ RIG_DIRTY_BASELINE_MODE: input.baselineMode,
59
+ RIG_DIRTY_BASELINE_READY_FILE: resolve(input.projectRoot, ".rig", "state", `${input.runId}.dirty-ready`)
886
60
  };
887
61
  }
888
- function positiveInt(value, fallback) {
889
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
890
- }
891
62
  function resolveTaskRunAutomationLimits(config, env = process.env) {
892
- const configuredValidationAttempts = positiveInt(config?.automation?.maxValidationAttempts, 30);
893
- const configuredPrFixIterations = positiveInt(config?.automation?.maxPrFixIterations, 100500);
63
+ const maxValidationAttempts = Number.parseInt(env.RIG_MAX_VALIDATION_ATTEMPTS ?? "", 10);
64
+ const maxPrFixIterations = Number.parseInt(env.RIG_MAX_PR_FIX_ITERATIONS ?? "", 10);
894
65
  return {
895
- maxValidationAttempts: parsePositiveInt(env.RIG_TASK_RUN_MAX_ATTEMPTS, "RIG_TASK_RUN_MAX_ATTEMPTS", configuredValidationAttempts),
896
- maxPrFixIterations: configuredPrFixIterations
66
+ maxValidationAttempts: Number.isFinite(maxValidationAttempts) && maxValidationAttempts > 0 ? maxValidationAttempts : config?.automation?.maxValidationAttempts ?? 30,
67
+ maxPrFixIterations: Number.isFinite(maxPrFixIterations) && maxPrFixIterations > 0 ? maxPrFixIterations : config?.automation?.maxPrFixIterations ?? 100500
897
68
  };
898
69
  }
899
70
  function classifyPlanningNeed(input) {
900
- const planning = input.config?.planning ?? {};
901
- const mode = planning.mode ?? "auto";
902
- const labels = new Set((input.sourceTask?.labels ?? []).map((label) => label.trim().toLowerCase()).filter(Boolean));
903
- const requireLabels = planning.requireForLabels ?? ["needs-plan", "epic", "large", "risk:high"];
904
- const skipLabels = planning.skipForLabels ?? ["no-plan", "small", "trivial"];
905
- const bodyLength = `${input.sourceTask?.description ?? ""}
906
- ${input.sourceTask?.body ?? ""}
907
- ${input.sourceTask?.acceptanceCriteria ?? input.sourceTask?.acceptance_criteria ?? ""}`.trim().length;
908
- const forcedLabel = requireLabels.find((label) => labels.has(label.toLowerCase()));
909
- const skippedLabel = skipLabels.find((label) => labels.has(label.toLowerCase()));
910
- const highRisk = labels.has("risk:high") || labels.has("security") || labels.has("migration");
911
- const size = labels.has("large") || bodyLength > 4000 ? "large" : bodyLength > 1000 ? "medium" : "small";
912
- const risk = highRisk ? "high" : labels.has("risk:medium") || size !== "small" ? "medium" : "low";
913
- if (mode === "always") {
914
- return { planningRequired: true, size, risk, reason: "planning.mode=always", policy: "always" };
915
- }
916
- if (mode === "off") {
917
- return { planningRequired: false, size, risk, reason: "planning.mode=off", policy: "off" };
918
- }
919
- if (forcedLabel) {
920
- return { planningRequired: true, size, risk, reason: `label:${forcedLabel}`, policy: "auto" };
921
- }
922
- if (skippedLabel && !highRisk) {
923
- return { planningRequired: false, size, risk, reason: `label:${skippedLabel}`, policy: "auto" };
924
- }
925
- const planningRequired = size !== "small" || risk === "high";
926
- return {
927
- planningRequired,
928
- size,
929
- risk,
930
- reason: planningRequired ? `auto:${size}/${risk}` : "auto:small/low",
931
- policy: "auto"
932
- };
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" };
933
75
  }
934
76
  function buildPiValidationRetrySteeringPrompt(input) {
935
77
  return [
936
- `Retry attempt ${input.attempt + 1}/${input.maxAttempts} for task ${input.taskId}.`,
937
- "The previous attempt failed Rig validation. Continue from the existing runtime workspace and PR state instead of starting over.",
938
- "",
939
- `Latest blocker: ${input.failureDetail}`,
940
- ...input.validationOutput?.trim() ? ["", "Validation output:", input.validationOutput.trim()] : [],
941
- "",
942
- "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."
943
81
  ].join(`
82
+
944
83
  `);
945
84
  }
946
85
  function classifyValidationRetryOutcome(input) {
947
86
  return {
948
87
  stage: "Validate failed",
949
- status: input.attempt >= input.maxAttempts ? "needs_attention" : "retry",
88
+ status: input.attempt < input.maxAttempts ? "retry" : "needs_attention",
950
89
  failureDetail: input.failureDetail
951
90
  };
952
91
  }
953
- function configuredIssueUpdatesMode(config) {
954
- const github = config?.github;
955
- const mode = github && typeof github === "object" && !Array.isArray(github) ? github.issueUpdates : null;
956
- return mode === "off" || mode === "minimal" || mode === "lifecycle" ? mode : "lifecycle";
957
- }
958
- function shouldWriteDriverIssueUpdate(config, status) {
959
- const mode = configuredIssueUpdatesMode(config);
960
- if (mode === "off")
961
- return false;
962
- if (mode === "lifecycle")
963
- return true;
964
- return ["closed", "failed", "needs_attention"].includes(status);
965
- }
966
- async function loadTaskRunAutomationConfig(projectRoot) {
967
- try {
968
- return await loadConfig(projectRoot);
969
- } catch {
970
- return null;
971
- }
972
- }
973
92
  function taskRunStageLogId(runId, stage) {
974
93
  return `log:${runId}:stage:${stage.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
975
94
  }
976
- function appendPiStageLog(input) {
977
- appendRunLog(input.projectRoot, input.runId, {
978
- id: taskRunStageLogId(input.runId, input.stage),
979
- title: input.stage,
980
- detail: input.detail ?? null,
981
- tone: input.tone ?? "info",
982
- status: input.status ?? "running",
983
- createdAt: new Date().toISOString()
984
- });
985
- emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
986
- }
987
- function createCommandRunner(binary) {
988
- return async (args, options) => {
989
- const child = spawn(binary, [...args], {
990
- cwd: options?.cwd,
991
- stdio: ["ignore", "pipe", "pipe"]
992
- });
993
- const stdoutChunks = [];
994
- const stderrChunks = [];
995
- child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
996
- child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
997
- return await new Promise((resolve7) => {
998
- child.once("error", (error) => resolve7({ exitCode: 1, stderr: error.message }));
999
- child.once("close", (code) => resolve7({
1000
- exitCode: code ?? 1,
1001
- stdout: Buffer.concat(stdoutChunks).toString("utf8"),
1002
- stderr: Buffer.concat(stderrChunks).toString("utf8")
1003
- }));
1004
- });
1005
- };
1006
- }
1007
- async function resolvePostValidationBranch(input) {
1008
- if (!input.configuredBranch.startsWith("task-")) {
1009
- return input.configuredBranch;
1010
- }
1011
- const current = await input.gitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: input.workspace });
1012
- const currentBranch = current.stdout?.trim();
1013
- if (current.exitCode === 0 && currentBranch && currentBranch !== "HEAD") {
1014
- return currentBranch;
1015
- }
1016
- return input.configuredBranch;
1017
- }
1018
- async function runTaskRunPostValidationLifecycle(input) {
1019
- const taskId = input.taskId?.trim();
1020
- if (!taskId) {
1021
- return { status: "skipped" };
1022
- }
1023
- const configInput = input.config ?? null;
1024
- const config = configInput ?? {};
1025
- const prMode = configInput ? configInput.pr?.mode ?? "auto" : "off";
1026
- if (prMode === "off" || prMode === "ask") {
1027
- input.appendStage?.("Open PR", prMode === "off" ? "PR automation disabled by pr.mode=off." : "PR creation awaiting operator approval by pr.mode=ask.", "skipped", "info");
1028
- input.appendStage?.("Complete", "Validation completed; no PR was opened and the issue was left open.", "completed", "info");
1029
- return { status: "skipped" };
1030
- }
1031
- const workspace = input.runtimeWorkspace || input.projectRoot;
1032
- const ghCommand = input.ghCommand ?? createCommandRunner("gh");
1033
- const gitCommand = input.gitCommand ?? createCommandRunner("git");
1034
- const configuredBranch = input.branch || `rig/${input.runtimeTaskId}-${input.runId}`;
1035
- const branch = await resolvePostValidationBranch({
1036
- workspace,
1037
- configuredBranch,
1038
- gitCommand
1039
- });
1040
- const prAutomation = input.prAutomation ?? runPrAutomation;
1041
- const updateTaskSource = input.updateTaskSource ?? updateTaskSourceWithProjectSync;
1042
- const stage = input.appendStage ?? (() => {
1043
- return;
1044
- });
1045
- const steerPi = input.steerPi ?? (async (message) => {
1046
- appendRunLog(input.projectRoot, input.runId, {
1047
- id: `log:${input.runId}:pr-steering:${Date.now()}`,
1048
- title: "Steering Pi from PR feedback",
1049
- detail: message,
1050
- tone: "info",
1051
- status: "reviewing",
1052
- createdAt: new Date().toISOString()
1053
- });
1054
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
1055
- });
1056
- stage("Commit", `Committing changes in ${workspace}.`, "running", "tool");
1057
- const commit = await commitRunChanges({
1058
- cwd: workspace,
1059
- message: `rig: complete task ${taskId}`,
1060
- command: gitCommand
1061
- });
1062
- stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
1063
- await pushBranchSyncedWithOrigin({ projectRoot: workspace, branch, gitCommand });
1064
- stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
1065
- const pr = await prAutomation({
1066
- projectRoot: workspace,
1067
- taskId,
1068
- runId: input.runId,
1069
- branch,
1070
- config,
1071
- sourceTask: input.sourceTask,
1072
- uploadedSnapshot: input.uploadedSnapshot,
1073
- artifactRoot: resolve6(input.projectRoot, "artifacts", taskId),
1074
- command: ghCommand,
1075
- gitCommand,
1076
- steerPi,
1077
- lifecycle: {
1078
- onPrOpened: async ({ prUrl }) => {
1079
- stage("Open PR", prUrl, "completed", "tool");
1080
- try {
1081
- if (shouldWriteDriverIssueUpdate(config, "under_review")) {
1082
- await updateTaskSource(input.projectRoot, {
1083
- taskId,
1084
- sourceTask: runSourceTaskIdentity(input.sourceTask),
1085
- update: {
1086
- status: "under_review",
1087
- comment: buildTaskRunLifecycleComment({
1088
- runId: input.runId,
1089
- status: "under_review",
1090
- summary: "Rig opened a pull request for this task.",
1091
- runtimeWorkspace: input.runtimeWorkspace ?? null
1092
- })
1093
- }
1094
- });
1095
- }
1096
- } catch (error) {
1097
- appendRunLog(input.projectRoot, input.runId, {
1098
- id: `log:${input.runId}:task-source-under-review-update`,
1099
- title: "Task source PR status update failed",
1100
- detail: error instanceof Error ? error.message : String(error),
1101
- tone: "error",
1102
- status: "reviewing",
1103
- createdAt: new Date().toISOString()
1104
- });
1105
- }
1106
- },
1107
- onReviewCiStarted: ({ prUrl, iteration }) => stage("Review/CI", `${prUrl} (iteration ${iteration})`, "running", "info"),
1108
- onFeedback: async ({ feedback }) => {
1109
- stage("Review/CI", feedback.join(`
1110
- `), "reviewing", "error");
1111
- if (shouldWriteDriverIssueUpdate(config, "ci_fixing")) {
1112
- await updateTaskSource(input.projectRoot, {
1113
- taskId,
1114
- sourceTask: runSourceTaskIdentity(input.sourceTask),
1115
- update: {
1116
- status: "ci_fixing",
1117
- comment: buildTaskRunLifecycleComment({
1118
- runId: input.runId,
1119
- status: "ci_fixing",
1120
- summary: "Rig is fixing CI/review feedback for this task.",
1121
- runtimeWorkspace: input.runtimeWorkspace ?? null
1122
- })
1123
- }
1124
- });
1125
- }
1126
- },
1127
- onMergeStarted: async ({ prUrl }) => {
1128
- stage("Merge", prUrl, "running", "tool");
1129
- if (shouldWriteDriverIssueUpdate(config, "merging")) {
1130
- await updateTaskSource(input.projectRoot, {
1131
- taskId,
1132
- sourceTask: runSourceTaskIdentity(input.sourceTask),
1133
- update: {
1134
- status: "merging",
1135
- comment: buildTaskRunLifecycleComment({
1136
- runId: input.runId,
1137
- status: "merging",
1138
- summary: "Rig is merging the pull request for this task.",
1139
- runtimeWorkspace: input.runtimeWorkspace ?? null
1140
- })
1141
- }
1142
- });
1143
- }
1144
- },
1145
- onMerged: ({ prUrl }) => stage("Merge", prUrl, "completed", "tool")
1146
- }
1147
- });
1148
- if (pr.status === "opened" && pr.prUrl) {
1149
- stage("Complete", `PR ready without merge: ${pr.prUrl}`, "completed", "info");
1150
- return { status: "completed", pr };
1151
- }
1152
- if (pr.status !== "merged" || !pr.prUrl) {
1153
- const detail = pr.actionableFeedback.join(`
1154
- `) || "PR automation did not merge the PR.";
1155
- stage("Needs attention", detail, "needs_attention", "error");
1156
- if (shouldWriteDriverIssueUpdate(config, "needs_attention")) {
1157
- await updateTaskSource(input.projectRoot, {
1158
- taskId,
1159
- sourceTask: runSourceTaskIdentity(input.sourceTask),
1160
- update: {
1161
- status: "needs_attention",
1162
- comment: buildTaskRunLifecycleComment({
1163
- runId: input.runId,
1164
- status: "needs_attention",
1165
- summary: "Rig needs operator attention before this task can proceed.",
1166
- runtimeWorkspace: input.runtimeWorkspace ?? null,
1167
- errorText: detail
1168
- })
1169
- }
1170
- }).catch((error) => {
1171
- try {
1172
- appendRunLog(input.projectRoot, input.runId, {
1173
- id: `log:${input.runId}:task-source-needs-attention-update`,
1174
- title: "Task source needs-attention update failed",
1175
- detail: error instanceof Error ? error.message : String(error),
1176
- tone: "error",
1177
- status: "needs_attention",
1178
- createdAt: new Date().toISOString()
1179
- });
1180
- } catch {}
1181
- });
1182
- }
1183
- return { status: "needs_attention", pr };
1184
- }
1185
- if (shouldWriteDriverIssueUpdate(config, "closed")) {
1186
- await closeIssueAfterMergedPr({
1187
- projectRoot: input.projectRoot,
1188
- taskId,
1189
- runId: input.runId,
1190
- prUrl: pr.prUrl,
1191
- sourceTask: input.sourceTask,
1192
- updateTaskSource: async (projectRoot, closeInput) => updateTaskSource(projectRoot, closeInput)
1193
- });
1194
- }
1195
- stage("Complete", `PR merged and issue closed: ${pr.prUrl}`, "completed", "info");
1196
- return { status: "completed", pr };
1197
- }
1198
- function summarizeValidationFailure(projectRoot, taskId) {
1199
- if (!taskId) {
1200
- return null;
1201
- }
1202
- for (const artifactDir of resolveTaskArtifactDirs(projectRoot, taskId)) {
1203
- const summary = readJsonFile2(resolve6(artifactDir, "validation-summary.json"), null);
1204
- if (!summary || summary.status !== "fail") {
1205
- continue;
1206
- }
1207
- const failedCategories = Array.isArray(summary.categories) ? summary.categories.filter((entry) => String(entry?.status ?? "") !== "pass").map((entry) => String(entry?.category ?? "").trim()).filter(Boolean) : [];
1208
- if (failedCategories.length > 0) {
1209
- return `Task validation failed: ${failedCategories.join(", ")}`;
1210
- }
1211
- return "Task validation failed.";
1212
- }
1213
- return null;
1214
- }
1215
- function classifyCompletionVerificationLine(line) {
1216
- const trimmed = line.trim();
1217
- if (/^=== Completion Verification: /.test(trimmed)) {
1218
- return { isCompletionLine: true, isVerifierReview: false };
1219
- }
1220
- if (/^\[\d+\/\d+\]/.test(trimmed)) {
1221
- return {
1222
- isCompletionLine: true,
1223
- isVerifierReview: /Verifier review/i.test(trimmed)
1224
- };
1225
- }
1226
- return { isCompletionLine: false, isVerifierReview: false };
1227
- }
1228
95
  function createRunLogIdFactory(runId) {
1229
- let sequence = 0;
1230
- return () => {
1231
- sequence += 1;
1232
- return `log:${runId}:${Date.now()}:${sequence}`;
1233
- };
96
+ let next = 0;
97
+ return () => `${runId}:log:${++next}`;
1234
98
  }
1235
99
  function summarizeTaskRunProcessFailure(input) {
1236
- const fallback = `Task run failed (${String(input.exitCode ?? "unknown")}${input.signal ? ` ${input.signal}` : ""})`;
1237
- const stderrLines = input.stderrLines ?? [];
1238
- const meaningfulLines = [...stderrLines].map((line) => line.trim()).filter(Boolean).filter((line) => !/^Task run failed \([^)]*\)$/i.test(line));
1239
- const taskSourceFailure = meaningfulLines.find((line) => /failed to update task source/i.test(line));
1240
- if (taskSourceFailure) {
1241
- return `Task source update failed: ${taskSourceFailure}`;
1242
- }
1243
- const moduleFailure = meaningfulLines.find((line) => /cannot find module/i.test(line));
1244
- if (moduleFailure) {
1245
- return `Runtime module resolution failed: ${moduleFailure}`;
1246
- }
1247
- for (const line of [...meaningfulLines].reverse()) {
1248
- const trimmed = line.trim();
1249
- if (/server-managed task run cannot finish successfully/i.test(trimmed)) {
1250
- return `Task source remained open: ${trimmed}`;
1251
- }
1252
- if (/no api key found/i.test(trimmed)) {
1253
- return `Missing provider credentials: ${trimmed}`;
1254
- }
1255
- if (/no claude credentials found/i.test(trimmed)) {
1256
- return `Missing provider credentials: ${trimmed}`;
1257
- }
1258
- if (/unauthorized|authentication failed|invalid api key|api key is invalid/i.test(trimmed)) {
1259
- return `Provider authentication failed: ${trimmed}`;
1260
- }
1261
- if (/command not found|no such file or directory|enoent/i.test(trimmed)) {
1262
- return `Missing runtime executable: ${trimmed}`;
1263
- }
1264
- if (/bubblewrap|\bbwrap\b|sandbox-exec|sandbox/i.test(trimmed) && /missing|not found|failed|denied/i.test(trimmed)) {
1265
- return `Sandbox backend unavailable: ${trimmed}`;
1266
- }
1267
- }
1268
- 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}` : ""}`;
1269
102
  }
1270
- function parsePositiveInt(value, option, fallback) {
1271
- if (!value) {
1272
- return fallback;
1273
- }
1274
- const parsed = Number.parseInt(value, 10);
1275
- if (!Number.isFinite(parsed) || parsed <= 0) {
1276
- throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
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" };
1277
106
  }
1278
- return parsed;
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 };
1279
112
  }
1280
113
  function readTaskRunAcceptedArtifactState(input) {
1281
- if (!input.taskId || !input.workspaceDir) {
1282
- return { accepted: false, reason: null };
1283
- }
1284
- const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1285
- const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1286
- const taskResultPath = resolve6(artifactDir, "task-result.json");
1287
- const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
1288
- if (reviewStatus !== "APPROVED") {
114
+ if (!existsSync(input.artifactPath))
1289
115
  return { accepted: false, reason: null };
1290
- }
1291
- const taskResult = readJsonFile2(taskResultPath, null);
1292
- if (taskResult?.status !== "completed") {
1293
- return { accepted: false, reason: null };
1294
- }
1295
- if (typeof input.artifactNotBeforeMs === "number" && Number.isFinite(input.artifactNotBeforeMs)) {
1296
- try {
1297
- const reviewStatusMtime = statSync(reviewStatusPath).mtimeMs;
1298
- if (reviewStatusMtime + 1000 < input.artifactNotBeforeMs) {
1299
- return { accepted: false, reason: null };
1300
- }
1301
- } catch {
1302
- return { accepted: false, reason: null };
1303
- }
1304
- }
1305
- return {
1306
- accepted: true,
1307
- reason: `task-result.json is completed and review-status.txt is APPROVED for task ${input.taskId}`
1308
- };
1309
- }
1310
- function resolvePiAcceptedArtifactExitGraceMs(env = process.env) {
1311
- const raw = env.RIG_PI_ACCEPTED_ARTIFACT_EXIT_GRACE_MS;
1312
- if (raw === undefined || raw.trim() === "")
1313
- return 30000;
1314
- const parsed = Number.parseInt(raw, 10);
1315
- if (!Number.isFinite(parsed) || parsed < 0)
1316
- return 30000;
1317
- return parsed;
1318
- }
1319
- function resolveTaskRunRetryContext(input) {
1320
- if (!input.taskId || !input.workspaceDir) {
1321
- return { shouldRetry: false, failureDetail: null, nextPrompt: null };
1322
- }
1323
- const artifactDir = resolve6(input.workspaceDir, "artifacts", input.taskId);
1324
- const reviewStatePath = resolve6(artifactDir, "review-state.json");
1325
- const reviewFeedbackPath = resolve6(artifactDir, "review-feedback.md");
1326
- const reviewStatusPath = resolve6(artifactDir, "review-status.txt");
1327
- const failedApproachesPath = resolve6(input.workspaceDir, ".rig", "state", "failed_approaches.md");
1328
- const validationSummaryPath = resolve6(artifactDir, "validation-summary.json");
1329
- const reviewState = readJsonFile2(reviewStatePath, null);
1330
- const reviewStatus = readTaskRunReviewStatus(reviewStatusPath);
1331
- const reviewRejected = isTaskRunReviewRejected(reviewState);
1332
- if (reviewStatus === "APPROVED") {
1333
- return { shouldRetry: false, failureDetail: null, nextPrompt: null };
1334
- }
1335
- if (reviewStatus !== "REJECTED" && !reviewRejected) {
1336
- return { shouldRetry: false, failureDetail: null, nextPrompt: null };
1337
- }
1338
- const failureDetail = summarizeTaskRunReviewFailure(reviewState);
1339
- if (input.attempt >= input.maxAttempts) {
1340
- return { shouldRetry: false, failureDetail, nextPrompt: null };
1341
- }
1342
- return {
1343
- shouldRetry: true,
1344
- failureDetail,
1345
- nextPrompt: [
1346
- input.basePrompt,
1347
- "",
1348
- `Retry attempt ${input.attempt + 1}/${input.maxAttempts} for task ${input.taskId}.`,
1349
- "The previous attempt did not satisfy completion verification. Continue from the existing runtime workspace and PR state instead of starting over.",
1350
- "",
1351
- `Latest blocker: ${failureDetail}`,
1352
- "",
1353
- "Inspect these artifacts first:",
1354
- `- ${reviewStatusPath}`,
1355
- `- ${reviewStatePath}`,
1356
- `- ${reviewFeedbackPath}`,
1357
- `- ${failedApproachesPath}`,
1358
- `- ${validationSummaryPath}`,
1359
- "",
1360
- "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."
1361
- ].join(`
1362
- `)
1363
- };
1364
- }
1365
- function summarizeTaskRunReviewFailure(reviewState) {
1366
- if (!reviewState) {
1367
- return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
1368
- }
1369
- const reasons = [
1370
- ...reviewState.local_reasons ?? [],
1371
- ...reviewState.ai_reasons ?? [],
1372
- ...reviewState.ai_warnings ?? []
1373
- ].map((value) => value.trim()).filter(Boolean);
1374
- if (reasons.length > 0) {
1375
- return reasons[0];
1376
- }
1377
- if (reviewState.mode === "required" && reviewState.verdict && reviewState.verdict !== "APPROVE") {
1378
- return `[AI Review] Required mode needs a completed Greptile approval; current verdict is ${reviewState.verdict}.`;
1379
- }
1380
- return "Completion verification rejected the task. Read review-feedback.md for required fixes.";
1381
- }
1382
- function appendAssistantTimelineFromRecord(input) {
1383
- let nextAssistantText = input.assistantText;
1384
- if (input.record.type === "message_update") {
1385
- const assistantMessageEvent = input.record.assistantMessageEvent && typeof input.record.assistantMessageEvent === "object" ? input.record.assistantMessageEvent : null;
1386
- if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
1387
- nextAssistantText += assistantMessageEvent.delta;
1388
- }
1389
- } else if (input.record.type === "stream_event") {
1390
- const event = input.record.event && typeof input.record.event === "object" ? input.record.event : null;
1391
- const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
1392
- if (delta?.type === "text_delta" && typeof delta.text === "string") {
1393
- nextAssistantText += delta.text;
1394
- }
1395
- } else if (input.record.type === "assistant") {
1396
- const message = input.record.message && typeof input.record.message === "object" ? input.record.message : input.record;
1397
- const content = Array.isArray(message.content) ? message.content : [];
1398
- const fullText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
1399
- if (fullText.length > nextAssistantText.length)
1400
- nextAssistantText = fullText;
1401
- }
1402
- if (nextAssistantText !== input.assistantText) {
1403
- appendRunTimeline(input.projectRoot, input.runId, {
1404
- id: input.messageId,
1405
- type: "assistant_message",
1406
- text: nextAssistantText,
1407
- state: "streaming",
1408
- createdAt: new Date().toISOString()
1409
- });
1410
- }
1411
- return nextAssistantText;
1412
- }
1413
- function appendPiRpcProtocolLogFromRecord(input) {
1414
- const type = typeof input.record.type === "string" ? input.record.type : "";
1415
- if (type === "response") {
1416
- const command = typeof input.record.command === "string" ? input.record.command : "rpc";
1417
- const success = input.record.success !== false;
1418
- if (success && command !== "prompt" && command !== "steer" && command !== "follow_up" && command !== "set_session_name") {
1419
- return true;
1420
- }
1421
- appendRunLog(input.projectRoot, input.runId, {
1422
- id: input.nextRunLogId(),
1423
- title: success ? "Pi RPC response" : "Pi RPC error",
1424
- detail: success ? `${command}: accepted` : `${command}: ${String(input.record.error ?? "failed")}`,
1425
- tone: success ? "tool" : "error",
1426
- status: input.status,
1427
- payload: input.record,
1428
- createdAt: new Date().toISOString()
1429
- });
1430
- emitServerRunEvent({ type: "log", runId: input.runId, title: success ? "Pi RPC response" : "Pi RPC error" });
1431
- return true;
1432
- }
1433
- if (type !== "extension_ui_request")
1434
- return false;
1435
- const method = typeof input.record.method === "string" ? input.record.method : "ui";
1436
- let title = "Pi UI event";
1437
- let detail = method;
1438
- let tone = "info";
1439
- if (method === "notify") {
1440
- title = "Pi notification";
1441
- detail = String(input.record.message ?? "");
1442
- tone = input.record.notifyType === "error" ? "error" : "info";
1443
- } else if (method === "setStatus") {
1444
- title = "Pi UI status";
1445
- detail = `${String(input.record.statusKey ?? "status")}: ${String(input.record.statusText ?? "cleared")}`;
1446
- tone = "tool";
1447
- } else if (method === "setWidget") {
1448
- title = "Pi UI widget";
1449
- const lines = Array.isArray(input.record.widgetLines) ? input.record.widgetLines.map((line) => String(line)).join(" | ") : "cleared";
1450
- detail = `${String(input.record.widgetKey ?? "widget")}: ${lines}`.slice(0, 500);
1451
- tone = "tool";
1452
- } else if (method === "setTitle") {
1453
- title = "Pi UI title";
1454
- detail = String(input.record.title ?? "");
1455
- tone = "tool";
1456
- } else if (method === "set_editor_text") {
1457
- title = "Pi editor update";
1458
- detail = String(input.record.text ?? "").slice(0, 500);
1459
- tone = "tool";
1460
- } else {
1461
- title = "Pi UI request";
1462
- detail = `${method}: ${String(input.record.title ?? input.record.message ?? "")}`.trim();
1463
- }
1464
- appendRunLog(input.projectRoot, input.runId, {
1465
- id: input.nextRunLogId(),
1466
- title,
1467
- detail,
1468
- tone,
1469
- status: input.status,
1470
- payload: input.record,
1471
- createdAt: new Date().toISOString()
1472
- });
1473
- emitServerRunEvent({ type: "log", runId: input.runId, title });
1474
- return true;
1475
- }
1476
- function appendPiToolTimelineFromRecord(input) {
1477
- const type = typeof input.record.type === "string" ? input.record.type : "";
1478
- if (type !== "tool_execution_start" && type !== "tool_execution_update" && type !== "tool_execution_end")
1479
- return false;
1480
- const toolCallId = typeof input.record.toolCallId === "string" && input.record.toolCallId.trim() ? input.record.toolCallId.trim() : `${Date.now()}`;
1481
- const toolName = typeof input.record.toolName === "string" && input.record.toolName.trim() ? input.record.toolName.trim() : "tool";
1482
- const result = input.record.result && typeof input.record.result === "object" && !Array.isArray(input.record.result) ? input.record.result : null;
1483
- appendRunTimeline(input.projectRoot, input.runId, {
1484
- id: `tool:${toolCallId}:${type}`,
1485
- type,
1486
- toolName,
1487
- status: type === "tool_execution_end" ? input.record.isError === true || result?.isError === true ? "failed" : "completed" : "running",
1488
- createdAt: new Date().toISOString()
1489
- });
1490
- return true;
1491
- }
1492
- function isNonRenderablePiProtocolRecord(record) {
1493
- const type = typeof record.type === "string" ? record.type : "";
1494
- return type === "pi.event" || type === "status.update" || type === "activity.update" || type === "ready" || type === "agent_start" || type === "agent_end" || type === "message_start" || type === "message_end" || type === "turn_start" || type === "turn_end" || type === "tool_result" || type === "message_update" && (!record.assistantMessageEvent || typeof record.assistantMessageEvent !== "object" || Array.isArray(record.assistantMessageEvent) || record.assistantMessageEvent.type !== "text_delta");
1495
- }
1496
- function appendToolTimelineFromLog(input) {
1497
- const title = typeof input.log.title === "string" ? input.log.title : "";
1498
- if (title !== "Tool activity")
1499
- return;
1500
- const payload = input.log.payload && typeof input.log.payload === "object" && !Array.isArray(input.log.payload) ? input.log.payload : {};
1501
- const toolName = typeof payload.toolName === "string" && payload.toolName.trim() ? payload.toolName.trim() : typeof payload.tool_name === "string" && payload.tool_name.trim() ? payload.tool_name.trim() : null;
1502
- const logId = typeof input.log.id === "string" && input.log.id.trim() ? input.log.id.trim() : `${Date.now()}`;
1503
- appendRunTimeline(input.projectRoot, input.runId, {
1504
- id: `tool:${logId}`,
1505
- type: "tool_execution_update",
1506
- toolName,
1507
- status: typeof input.log.status === "string" ? input.log.status : "running",
1508
- detail: typeof input.log.detail === "string" ? input.log.detail : null,
1509
- payload,
1510
- createdAt: typeof input.log.createdAt === "string" ? input.log.createdAt : new Date().toISOString()
1511
- });
1512
- }
1513
- function readTaskRunReviewStatus(reviewStatusPath) {
1514
- if (!existsSync4(reviewStatusPath)) {
1515
- return null;
1516
- }
1517
116
  try {
1518
- const status = readFileSync4(reviewStatusPath, "utf8").trim().toUpperCase();
1519
- return status === "APPROVED" || status === "REJECTED" ? status : null;
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
+ };
1520
122
  } catch {
1521
- return null;
1522
- }
1523
- }
1524
- function isTaskRunReviewRejected(reviewState) {
1525
- if (!reviewState) {
1526
- return false;
1527
- }
1528
- if (reviewState.approved === false) {
1529
- return true;
123
+ return { accepted: false, reason: null };
1530
124
  }
1531
- return reviewState.mode === "required" && !!reviewState.verdict && reviewState.verdict !== "APPROVE";
1532
125
  }
1533
- function runSourceTaskIdentity(sourceTask) {
1534
- return sourceTask;
1535
- }
1536
- function sourceTaskIssueNodeId(sourceTask) {
1537
- if (!sourceTask || typeof sourceTask !== "object" || Array.isArray(sourceTask))
1538
- return null;
1539
- const record = sourceTask;
1540
- const direct = typeof record.issueNodeId === "string" ? record.issueNodeId : typeof record.nodeId === "string" ? record.nodeId : typeof record.node_id === "string" ? record.node_id : null;
1541
- if (direct?.trim())
1542
- return direct.trim();
1543
- const raw = record.raw && typeof record.raw === "object" && !Array.isArray(record.raw) ? record.raw : null;
1544
- return typeof raw?.id === "string" && raw.id.trim() ? raw.id.trim() : null;
1545
- }
1546
- var updateTaskSourceWithProjectSync = async (projectRoot, input) => {
1547
- const serverResult = await updateWorkspaceTaskViaServer({ projectRoot }, {
1548
- id: input.taskId,
1549
- ...input.update.status ? { status: input.update.status } : {},
1550
- ...input.update.comment ? { comment: input.update.comment } : {},
1551
- ...input.update.title ? { title: input.update.title } : {},
1552
- ...typeof input.update.body === "string" ? { body: input.update.body } : {},
1553
- issueNodeId: sourceTaskIssueNodeId(input.sourceTask)
1554
- });
1555
- if (serverResult.ok === false) {
1556
- throw new Error(typeof serverResult.error === "string" ? serverResult.error : "Rig server task update failed.");
1557
- }
1558
- return {
1559
- updated: serverResult.ok !== false,
1560
- taskId: input.taskId,
1561
- status: input.update.status,
1562
- source: "server",
1563
- sourceKind: "server",
1564
- projectSync: serverResult.projectSync
1565
- };
1566
- };
1567
- async function updateTaskSourceAfterDriverRun(projectRoot, runId, taskId, sourceTask, status, summary, input, updateTaskSource = updateTaskSourceWithProjectSync) {
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);
1568
130
  if (!taskId)
1569
- return;
1570
- const config = await loadTaskRunAutomationConfig(projectRoot);
1571
- if (!shouldWriteDriverIssueUpdate(config, status))
1572
- return;
1573
- const result = await updateTaskSource(projectRoot, {
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,
1574
141
  taskId,
1575
- sourceTask: runSourceTaskIdentity(sourceTask),
1576
- update: {
1577
- status,
1578
- comment: buildTaskRunLifecycleComment({
1579
- runId,
1580
- status,
1581
- summary,
1582
- runtimeWorkspace: input.latestRuntimeWorkspace,
1583
- logsDir: input.latestLogsDir,
1584
- sessionDir: input.latestSessionDir,
1585
- errorText: input.errorText
1586
- })
1587
- }
1588
- });
1589
- if (!result.updated && (result.source === "plugin" || result.sourceKind)) {
1590
- throw new Error(`Configured task source${result.sourceKind ? ` (${result.sourceKind})` : ""} did not accept lifecycle update for ${result.taskId}.`);
1591
- }
1592
- }
1593
- function readRunSourceTaskContract(projectRoot, runId, taskId) {
1594
- const run = readAuthorityRun3(projectRoot, runId);
1595
- const raw = run?.sourceTask;
1596
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1597
- return null;
1598
- }
1599
- const record = raw;
1600
- const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : taskId?.trim();
1601
- if (!id) {
1602
- return null;
1603
- }
1604
- return {
1605
- id,
1606
- ...optionalStringField(record, "title"),
1607
- ...optionalStringField(record, "description"),
1608
- ...optionalStringField(record, "body"),
1609
- ...optionalStringField(record, "acceptanceCriteria"),
1610
- ...optionalStringField(record, "acceptance_criteria"),
1611
- ...optionalStringField(record, "sourceIssueId"),
1612
- ...optionalStringField(record, "status"),
1613
- ...optionalStringField(record, "issueType"),
1614
- ...optionalStringField(record, "role"),
1615
- ...optionalStringField(record, "externalRef"),
1616
- scope: stringArrayField(record, "scope"),
1617
- validation: stringArrayField(record, "validation"),
1618
- validators: stringArrayField(record, "validators"),
1619
- labels: stringArrayField(record, "labels")
1620
- };
1621
- }
1622
- function optionalStringField(record, key) {
1623
- const value = record[String(key)];
1624
- return typeof value === "string" ? { [key]: value } : {};
1625
- }
1626
- function stringArrayField(record, key) {
1627
- const value = record[key];
1628
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1629
- }
1630
- async function executeRigOwnedTaskRun(context, input) {
1631
- process.stdout.on("error", () => {});
1632
- process.stderr.on("error", () => {});
1633
- configureRunEventSink(context.projectRoot);
1634
- const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1635
- const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1636
- const resumeMode = process.env.RIG_RUN_RESUME === "1";
1637
- let latestPiSessionId = typeof existingRunRecord?.piSession?.sessionId === "string" && existingRunRecord.piSession.sessionId.trim() ? existingRunRecord.piSession.sessionId : null;
1638
- const sessionAnchor = () => latestPiSessionId ? { sessionId: latestPiSessionId } : {};
1639
- const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1640
- const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1641
- let prompt = buildRunPrompt({
1642
- projectRoot: context.projectRoot,
1643
- taskId: input.taskId,
1644
- fallbackTitle: input.title,
1645
- fallbackDescription: sourceTask?.description ?? sourceTask?.body ?? null,
1646
- fallbackAcceptanceCriteria: sourceTask?.acceptanceCriteria ?? sourceTask?.acceptance_criteria ?? null,
142
+ branch,
143
+ workspace,
144
+ artifactRoot: cleanString(input.artifactRoot),
145
+ config,
1647
146
  sourceTask,
1648
- initialPrompt: input.initialPrompt,
1649
- runtimeAdapter: input.runtimeAdapter
1650
- });
1651
- const startedAt = new Date().toISOString();
1652
- const hostAgentBinary = await resolvePreferredShellBinary(context.projectRoot);
1653
- const baseAgentArgs = input.runtimeAdapter === "codex" ? [
1654
- "__rig_codex_app_server_task_run__",
1655
- "--runtime-mode",
1656
- input.runtimeMode,
1657
- "--interaction-mode",
1658
- input.interactionMode,
1659
- ...input.model ? ["--model", input.model] : [],
1660
- "--prompt"
1661
- ] : input.runtimeAdapter === "pi" ? [
1662
- "__rig_pi_session_daemon__",
1663
- ...input.model ? ["--model", input.model] : []
1664
- ] : [
1665
- "--print",
1666
- "--verbose",
1667
- "--output-format",
1668
- "stream-json",
1669
- "--dangerously-skip-permissions"
1670
- ];
1671
- const buildHostAgentCommand = (attemptPrompt) => input.runtimeAdapter === "codex" ? [hostAgentBinary, ...baseAgentArgs, attemptPrompt] : [hostAgentBinary, ...baseAgentArgs];
1672
- const hostAgentCommand = buildHostAgentCommand(prompt);
1673
- const hostAgentCommandText = formatCommand(hostAgentCommand);
1674
- const provisioningAction = startRunAction(context.projectRoot, input.runId, {
1675
- actionId: `action:${input.runId}:runtime-provision`,
1676
- actionType: "engine.runtime.provision",
1677
- title: "Provision dedicated runtime",
1678
- detail: input.taskId ? `Preparing isolated worktree for ${input.taskId}` : "Preparing isolated runtime",
1679
- payload: {
1680
- hostAgentCommand,
1681
- formattedCommand: hostAgentCommandText,
1682
- taskId: input.taskId ?? null,
1683
- mode: "worktree"
1684
- }
1685
- });
1686
- upsertAgentAuthorityRun(context.projectRoot, {
1687
- runId: input.runId,
1688
- taskId: runtimeTaskId,
1689
- createdAt: startedAt,
1690
- runtimeAdapter: input.runtimeAdapter,
1691
- status: resumeMode ? "preparing" : "created"
1692
- });
1693
- setRunStatusOrFail(context.projectRoot, input.runId, "preparing", { reason: "driver-start" });
1694
- patchAuthorityRun(context.projectRoot, input.runId, {
1695
- startedAt,
1696
- completedAt: null,
1697
- errorText: null,
1698
- artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
1699
- runtimeAdapter: input.runtimeAdapter,
1700
- runtimeMode: input.runtimeMode,
1701
- interactionMode: input.interactionMode,
1702
- runMode: input.runtimeMode === "full-access" ? "autonomous" : "interactive",
1703
- initialPrompt: input.initialPrompt ?? null,
1704
- title: input.taskId ? undefined : input.title ?? `Run ${input.runId}`,
1705
- pid: process.pid
1706
- });
1707
- emitServerRunEvent({
1708
- type: "status",
1709
- runId: input.runId,
1710
- status: "preparing",
1711
- detail: input.taskId ?? input.title ?? runtimeTaskId,
1712
- ...sessionAnchor()
1713
- });
1714
- appendRunLog(context.projectRoot, input.runId, {
1715
- id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
1716
- title: resumeMode ? "Rig task run resumed" : "Rig task run started",
1717
- detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
1718
- tone: "info",
1719
- status: "preparing",
1720
- createdAt: startedAt
1721
- });
1722
- appendRunLog(context.projectRoot, input.runId, {
1723
- id: `log:${input.runId}:wrapper-command`,
1724
- title: "Agent wrapper command",
1725
- detail: hostAgentCommandText,
1726
- tone: "tool",
1727
- status: "preparing",
1728
- payload: {
1729
- command: hostAgentCommand,
1730
- 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
+ }
1731
154
  },
1732
- createdAt: new Date().toISOString()
1733
- });
1734
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Agent wrapper command" });
1735
- const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1736
- const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1737
- const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1738
- const planningArtifactPath = resolve6("artifacts", runtimeTaskId, "implementation-plan.md");
1739
- const persistedPlanning = {
1740
- ...planningClassification,
1741
- classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1742
- artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1743
- classifiedAt: new Date().toISOString()
1744
- };
1745
- mkdirSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1746
- writeFileSync2(resolve6(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1747
- `, "utf8");
1748
- patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1749
- prompt = `${prompt}
1750
-
1751
- Rig planning classification:
1752
- ${JSON.stringify(persistedPlanning, null, 2)}
1753
- ${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."}`;
1754
- if (input.runtimeAdapter === "pi") {
1755
- for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
1756
- appendPiStageLog({
1757
- projectRoot: context.projectRoot,
1758
- runId: input.runId,
1759
- stage,
1760
- detail: stage === "Launch Pi" ? "Worker Pi SDK session daemon starting; local frontend will attach over Rig-proxied WebSocket." : stage === "Plan" ? `${planningClassification.planningRequired ? "recorded" : "skipped"} (${planningClassification.reason}; size=${planningClassification.size}; risk=${planningClassification.risk})` : stage === "Implement" ? "Pi implementation pass is running in the worker runtime." : null,
1761
- status: stage === "Implement" || stage === "Launch Pi" ? "running" : "completed"
1762
- });
1763
- }
1764
- }
1765
- appendRunTimeline(context.projectRoot, input.runId, {
1766
- id: `message:${input.runId}:user`,
1767
- type: "user_message",
1768
- text: prompt,
1769
- createdAt: startedAt,
1770
- state: "completed",
1771
- completedAt: startedAt
1772
- });
1773
- emitServerRunEvent({ type: "timeline", runId: input.runId });
1774
- await context.emitEvent("command.started", {
1775
- command: [
1776
- "rig",
1777
- "server",
1778
- "task-run",
1779
- ...input.taskId ? ["--task", input.taskId] : [],
1780
- ...input.title ? ["--title", input.title] : []
1781
- ],
1782
- formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
1783
- startedAt,
1784
- dryRun: false
1785
- });
1786
- const assistantMessageId = `message:${input.runId}:assistant`;
1787
- let assistantText = "";
1788
- const pendingClaudeToolUses = new Map;
1789
- const pendingCodexToolUses = new Map;
1790
- let launchAction;
1791
- let verificationAction;
1792
- let reviewAction;
1793
- let verificationStarted = false;
1794
- let reviewStarted = false;
1795
- let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1796
- let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve6(existingRunRecord.sessionPath, "..") : null;
1797
- let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1798
- let latestProviderCommand = null;
1799
- let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1800
- let snapshotSidecarPromise = null;
1801
- let wrapperManagesRuntimeSnapshot = false;
1802
- let dirtyBaselineApplied = false;
1803
- const childEnv = {
1804
- ...process.env,
1805
- ...input.runtimeAdapter === "pi" ? buildPiRigBridgeEnv({
1806
- projectRoot: context.projectRoot,
1807
- runId: input.runId,
1808
- taskId: runtimeTaskId,
1809
- serverUrl: process.env.RIG_SERVER_URL ?? null,
1810
- authToken: process.env.RIG_AUTH_TOKEN ?? null,
1811
- githubToken: process.env.RIG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? null
1812
- }) : {
1813
- PROJECT_RIG_ROOT: context.projectRoot,
1814
- RIG_TASK_ID: runtimeTaskId,
1815
- RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
1816
- 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);
1817
158
  },
1818
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
1819
- ...resumeMode ? {
1820
- RIG_RUN_RESUME: "1",
1821
- RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1822
- } : {}
1823
- };
1824
- Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1825
- Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
1826
- const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
1827
- const maxAttempts = automationLimits.maxValidationAttempts;
1828
- const promoteToValidating = (detail) => {
1829
- if (verificationStarted)
1830
- return;
1831
- verificationStarted = true;
1832
- setRunStatusOrFail(context.projectRoot, input.runId, "validating");
1833
- emitServerRunEvent({ type: "status", runId: input.runId, status: "validating", detail: detail ?? null, ...sessionAnchor() });
1834
- verificationAction = startRunAction(context.projectRoot, input.runId, {
1835
- actionId: `action:${input.runId}:completion-verification`,
1836
- actionType: "completion-verification",
1837
- title: "Run completion verification",
1838
- detail: detail ?? "Rig completion-verification hook is running."
1839
- });
1840
- };
1841
- const promoteToReviewing = (detail) => {
1842
- if (reviewStarted)
1843
- return;
1844
- reviewStarted = true;
1845
- setRunStatusOrFail(context.projectRoot, input.runId, "reviewing");
1846
- emitServerRunEvent({ type: "status", runId: input.runId, status: "reviewing", detail: detail ?? null, ...sessionAnchor() });
1847
- reviewAction = startRunAction(context.projectRoot, input.runId, {
1848
- actionId: `action:${input.runId}:review-closeout`,
1849
- actionType: "run.review",
1850
- title: "Finalize run review",
1851
- detail: detail ?? "Verifier review is running."
1852
- });
1853
- };
1854
- const nextRunLogId = createRunLogIdFactory(input.runId);
1855
- const handleWrapperEvent = (rawPayload) => {
1856
- let event = null;
1857
- try {
1858
- event = JSON.parse(rawPayload);
1859
- } catch {
1860
- return false;
1861
- }
1862
- if (!event?.type)
1863
- return false;
1864
- const payload = event.payload ?? {};
1865
- if (event.type === "runtime.provision.completed") {
1866
- latestRuntimeWorkspace = typeof payload.workspaceDir === "string" ? payload.workspaceDir : latestRuntimeWorkspace;
1867
- latestSessionDir = typeof payload.sessionDir === "string" ? payload.sessionDir : latestSessionDir;
1868
- latestLogsDir = typeof payload.logsDir === "string" ? payload.logsDir : latestLogsDir;
1869
- const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : null;
1870
- wrapperManagesRuntimeSnapshot = payload.snapshotManaged === true;
1871
- latestRuntimeBranch = runtimeId;
1872
- provisioningAction.complete(latestRuntimeWorkspace ?? "Runtime ready.", {
1873
- runtimeId: runtimeId ?? null,
1874
- workspaceDir: latestRuntimeWorkspace,
1875
- sessionDir: latestSessionDir,
1876
- logsDir: latestLogsDir,
1877
- contextFile: payload.contextFile ?? null
1878
- });
1879
- setRunStatusOrFail(context.projectRoot, input.runId, "running");
1880
- patchAuthorityRun(context.projectRoot, input.runId, {
1881
- worktreePath: latestRuntimeWorkspace,
1882
- artifactRoot: latestRuntimeWorkspace && input.taskId ? resolve6(latestRuntimeWorkspace, "artifacts", input.taskId) : null,
1883
- logRoot: latestLogsDir,
1884
- sessionPath: latestSessionDir ? resolve6(latestSessionDir, "session.json") : null,
1885
- sessionLogPath: latestLogsDir ? resolve6(latestLogsDir, "agent-stdout.log") : null,
1886
- branch: runtimeId
1887
- });
1888
- if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
1889
- dirtyBaselineApplied = true;
1890
- const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1891
- const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1892
- if (readyFile) {
1893
- mkdirSync2(resolve6(readyFile, ".."), { recursive: true });
1894
- writeFileSync2(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1895
- `, "utf8");
1896
- }
1897
- appendRunLog(context.projectRoot, input.runId, {
1898
- id: `log:${input.runId}:dirty-baseline`,
1899
- title: "Dirty baseline snapshot",
1900
- detail: dirty.detail,
1901
- tone: dirty.applied ? "tool" : "info",
1902
- status: dirty.applied ? "completed" : "skipped",
1903
- createdAt: new Date().toISOString(),
1904
- payload: readyFile ? { readyFile } : undefined
1905
- });
1906
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1907
- }
1908
- if (!wrapperManagesRuntimeSnapshot && !snapshotSidecarPromise && runtimeId && loadRuntimeSnapshotConfig(context.projectRoot).enabled) {
1909
- snapshotSidecarPromise = (async () => {
1910
- const { sidecar, error } = await resolveTaskRunSnapshotSidecar({
1911
- projectRoot: context.projectRoot,
1912
- runtimeId,
1913
- workspaceDir: latestRuntimeWorkspace,
1914
- listRuntimes: listAgentRuntimes,
1915
- startSidecar: (runtime) => startRuntimeSnapshotSidecar(runtime, { instanceId: `${runtime.id}-run-${input.runId}` })
1916
- });
1917
- if (!sidecar) {
1918
- appendRunLog(context.projectRoot, input.runId, {
1919
- id: `log:${input.runId}:snapshot-unavailable`,
1920
- title: "Runtime snapshotting unavailable",
1921
- detail: error ?? `Could not locate runtime metadata for ${runtimeId}.`,
1922
- tone: "error",
1923
- status: "running",
1924
- createdAt: new Date().toISOString()
1925
- });
1926
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Runtime snapshotting unavailable" });
1927
- return null;
1928
- }
1929
- return sidecar;
1930
- })();
1931
- }
1932
- emitServerRunEvent({
1933
- type: "status",
1934
- runId: input.runId,
1935
- status: "running",
1936
- detail: latestRuntimeWorkspace,
1937
- ...sessionAnchor()
1938
- });
1939
- return true;
1940
- }
1941
- if (event.type === "runtime.provision.failed") {
1942
- const detail = typeof payload.error === "string" ? payload.error : "Runtime provisioning failed.";
1943
- provisioningAction.fail(detail, payload);
1944
- emitServerRunEvent({ type: "failed", runId: input.runId, error: detail });
1945
- return true;
1946
- }
1947
- if (event.type === "provider.launch") {
1948
- const command = Array.isArray(payload.command) ? payload.command.filter((value) => typeof value === "string") : [];
1949
- latestProviderCommand = command.length > 0 ? command : null;
1950
- const commandText = command.length > 0 ? formatCommand(command) : String(payload.provider ?? "provider");
1951
- launchAction = startRunAction(context.projectRoot, input.runId, {
1952
- actionId: `action:${input.runId}:provider-launch`,
1953
- actionType: "agent.launch",
1954
- title: "Launch agent runtime",
1955
- detail: commandText,
1956
- payload: {
1957
- runtimeAdapter: input.runtimeAdapter,
1958
- runtimeMode: input.runtimeMode,
1959
- interactionMode: input.interactionMode,
1960
- workspaceDir: payload.workspaceDir ?? latestRuntimeWorkspace,
1961
- command,
1962
- formattedCommand: commandText
1963
- }
1964
- });
1965
- appendRunLog(context.projectRoot, input.runId, {
1966
- id: `log:${input.runId}:provider-command`,
1967
- title: "Agent launch command",
1968
- detail: commandText,
1969
- tone: "tool",
1970
- status: "running",
1971
- payload: {
1972
- command,
1973
- formattedCommand: commandText
1974
- },
1975
- createdAt: new Date().toISOString()
1976
- });
1977
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Agent launch command" });
1978
- return true;
1979
- }
1980
- if (event.type === "provider.completed") {
1981
- const exitCode = typeof payload.exitCode === "number" ? payload.exitCode : null;
1982
- if (launchAction) {
1983
- if (exitCode === 0) {
1984
- launchAction.complete("Agent runtime finished.");
1985
- } else {
1986
- launchAction.fail(`Agent runtime failed (${String(exitCode ?? "unknown")})`, payload);
1987
- }
1988
- }
1989
- return true;
1990
- }
1991
- if (event.type === "pi.sessiond.starting" || event.type === "pi.sessiond.ready" || event.type === "pi.session.event_stream.connected" || event.type === "pi.prompt.sent" || event.type === "pi.prompt.waiting" || event.type === "pi.session.agent_end" || event.type === "pi.session.error") {
1992
- const title = event.type === "pi.sessiond.starting" ? "Starting worker Pi session daemon" : event.type === "pi.sessiond.ready" ? "Worker Pi daemon ready" : event.type === "pi.session.event_stream.connected" ? "Worker Pi event stream connected" : event.type === "pi.prompt.sent" ? "Delivered initial prompt to worker Pi" : event.type === "pi.prompt.waiting" ? "Worker Pi prompt waiting" : event.type === "pi.session.agent_end" ? "Worker Pi turn complete" : "Worker Pi session error";
1993
- const detail = event.type === "pi.sessiond.ready" ? "Daemon accepted control connection; waiting for SDK session metadata." : event.type === "pi.sessiond.starting" ? String(payload.workspaceDir ?? "worker runtime") : event.type === "pi.prompt.sent" ? `${String(payload.bytes ?? "unknown")} prompt bytes sent.` : event.type === "pi.prompt.waiting" ? String(payload.reason ?? "empty prompt") : event.type === "pi.session.error" ? String(payload.message ?? "session error") : String(payload.sessionId ?? payload.runId ?? "worker Pi session");
1994
- appendRunLog(context.projectRoot, input.runId, {
1995
- id: nextRunLogId(),
1996
- title,
1997
- detail,
1998
- tone: event.type === "pi.session.error" ? "error" : "info",
1999
- status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
2000
- payload: {
2001
- eventType: event.type,
2002
- runId: typeof payload.runId === "string" ? payload.runId : null,
2003
- runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null,
2004
- sessionId: typeof payload.sessionId === "string" ? payload.sessionId : null
2005
- },
2006
- createdAt: new Date().toISOString()
2007
- });
2008
- emitServerRunEvent({ type: "log", runId: input.runId, title });
2009
- return true;
2010
- }
2011
- if (event.type === "pi.session.ready") {
2012
- const privateMetadata = payload.privateMetadata && typeof payload.privateMetadata === "object" && !Array.isArray(payload.privateMetadata) ? payload.privateMetadata : null;
2013
- const publicMetadata = payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata) ? payload.metadata : null;
2014
- if (privateMetadata) {
2015
- patchAuthorityRun(context.projectRoot, input.runId, {
2016
- piSession: publicMetadata,
2017
- piSessionPrivate: privateMetadata
2018
- });
2019
- const sessionId = typeof publicMetadata?.sessionId === "string" ? publicMetadata.sessionId : typeof privateMetadata.public === "object" && privateMetadata.public && !Array.isArray(privateMetadata.public) ? String(privateMetadata.public.sessionId ?? "") : "";
2020
- if (sessionId) {
2021
- latestPiSessionId = sessionId;
2022
- }
2023
- appendRunLog(context.projectRoot, input.runId, {
2024
- id: `log:${input.runId}:pi-session-ready`,
2025
- title: "Worker Pi session ready",
2026
- detail: sessionId ? `Session ${sessionId} is ready for WebSocket attach.` : "Worker Pi SDK session daemon is ready for WebSocket attach.",
2027
- tone: "info",
2028
- status: "running",
2029
- payload: {
2030
- sessionId: sessionId || null,
2031
- runtimeId: typeof payload.runtimeId === "string" ? payload.runtimeId : null
2032
- },
2033
- createdAt: new Date().toISOString()
2034
- });
2035
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Worker Pi session ready" });
2036
- }
2037
- return true;
2038
- }
2039
- if (event.type === "pi.rpc.prompt.sent" || event.type === "pi.rpc.steering.delivered" || event.type === "pi.rpc.steering.poll.failed" || event.type === "pi.rpc.extension_ui.cancelled") {
2040
- const title = event.type === "pi.rpc.prompt.sent" ? "Delivered initial prompt to worker Pi" : event.type === "pi.rpc.steering.delivered" ? "Delivered steering to worker Pi" : event.type === "pi.rpc.steering.poll.failed" ? "Worker Pi steering poll failed" : "Pi RPC UI request auto-cancelled";
2041
- const detail = event.type === "pi.rpc.prompt.sent" ? `${String(payload.kind ?? "prompt")} prompt (${String(payload.bytes ?? "unknown")} bytes)` : event.type === "pi.rpc.steering.delivered" ? `${String(payload.actor ?? "operator")}: ${String(payload.message ?? "")}`.slice(0, 500) : event.type === "pi.rpc.steering.poll.failed" ? String(payload.error ?? "steering poll failed") : `${String(payload.method ?? "ui")}: ${String(payload.reason ?? "noninteractive worker session")}`;
2042
- appendRunLog(context.projectRoot, input.runId, {
2043
- id: nextRunLogId(),
2044
- title,
2045
- detail,
2046
- tone: event.type === "pi.rpc.steering.poll.failed" ? "error" : "info",
2047
- status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
2048
- payload,
2049
- createdAt: new Date().toISOString()
2050
- });
2051
- emitServerRunEvent({ type: "log", runId: input.runId, title });
2052
- return true;
2053
- }
2054
- appendRunTimeline(context.projectRoot, input.runId, {
2055
- id: `timeline:${input.runId}:${Date.now()}:wrapper:${event.type}`,
2056
- type: "wrapper-event",
2057
- eventType: event.type,
2058
- payload: redactSensitivePayload(payload),
2059
- createdAt: new Date().toISOString()
2060
- });
2061
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2062
- return true;
2063
- };
2064
- const handleAgentStdoutLine = (line) => {
2065
- const trimmed = line.trim();
2066
- if (!trimmed)
2067
- return;
2068
- if (trimmed.startsWith("__RIG_WRAPPER_EVENT__")) {
2069
- if (handleWrapperEvent(trimmed.slice("__RIG_WRAPPER_EVENT__".length))) {
2070
- emitServerRunEvent({ type: "timeline", runId: input.runId });
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")
2071
162
  return;
2072
- }
2073
- }
2074
- const completionLine = classifyCompletionVerificationLine(trimmed);
2075
- if (completionLine.isCompletionLine) {
2076
- promoteToValidating(trimmed);
2077
- if (completionLine.isVerifierReview) {
2078
- if (verificationAction) {
2079
- verificationAction.complete("Completion verification checks finished.");
2080
- }
2081
- promoteToReviewing(trimmed);
2082
- }
2083
- appendRunLog(context.projectRoot, input.runId, {
2084
- id: nextRunLogId(),
2085
- title: verificationStarted ? "Completion verification" : "Agent output",
2086
- detail: trimmed,
2087
- tone: "info",
2088
- status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
2089
- createdAt: new Date().toISOString()
2090
- });
2091
- emitServerRunEvent({ type: "log", runId: input.runId, title: verificationStarted ? "Completion verification" : "Agent output" });
2092
- return;
2093
- }
2094
- try {
2095
- const record = JSON.parse(trimmed);
2096
- const liveLogStatus = reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running";
2097
- if (input.runtimeAdapter === "pi" && appendPiRpcProtocolLogFromRecord({
2098
- projectRoot: context.projectRoot,
2099
- runId: input.runId,
2100
- record,
2101
- status: liveLogStatus,
2102
- nextRunLogId
2103
- })) {
2104
- return;
2105
- }
2106
- if (input.runtimeAdapter === "pi" && appendPiToolTimelineFromRecord({ projectRoot: context.projectRoot, runId: input.runId, record })) {
2107
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2108
- return;
2109
- }
2110
- const providerLogs = input.runtimeAdapter === "codex" ? buildCodexLogsFromRecord({
2111
- runId: input.runId,
2112
- record,
2113
- createdAtFallback: new Date().toISOString(),
2114
- status: liveLogStatus,
2115
- pendingToolUses: pendingCodexToolUses
2116
- }) : buildClaudeLogsFromRecord({
2117
- runId: input.runId,
2118
- record,
2119
- createdAtFallback: new Date().toISOString(),
2120
- status: liveLogStatus,
2121
- pendingToolUses: pendingClaudeToolUses
2122
- });
2123
- if (providerLogs.length > 0) {
2124
- for (const providerLog of providerLogs) {
2125
- appendRunLog(context.projectRoot, input.runId, providerLog);
2126
- appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2127
- emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2128
- if (providerLog.title === "Tool activity")
2129
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2130
- }
2131
- }
2132
- if (input.runtimeAdapter === "codex") {
2133
- const nextAssistantText = extractCodexAssistantMessageText(record);
2134
- if (nextAssistantText) {
2135
- assistantText = nextAssistantText;
2136
- appendRunTimeline(context.projectRoot, input.runId, {
2137
- id: assistantMessageId,
2138
- type: "assistant_message",
2139
- text: assistantText,
2140
- state: "streaming",
2141
- createdAt: new Date().toISOString()
2142
- });
2143
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2144
- return;
2145
- }
2146
- if (providerLogs.length > 0 || isCodexExecRecord(record)) {
2147
- return;
2148
- }
2149
- }
2150
- if (record.type === "message_update") {
2151
- const assistantMessageEvent = record.assistantMessageEvent && typeof record.assistantMessageEvent === "object" ? record.assistantMessageEvent : null;
2152
- if (assistantMessageEvent?.type === "text_delta" && typeof assistantMessageEvent.delta === "string") {
2153
- assistantText += assistantMessageEvent.delta;
2154
- appendRunTimeline(context.projectRoot, input.runId, {
2155
- id: assistantMessageId,
2156
- type: "assistant_message",
2157
- text: assistantText,
2158
- state: "streaming",
2159
- createdAt: new Date().toISOString()
2160
- });
2161
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2162
- return;
2163
- }
2164
- }
2165
- if (record.type === "stream_event") {
2166
- const event = record.event && typeof record.event === "object" ? record.event : null;
2167
- const delta = event?.delta && typeof event.delta === "object" ? event.delta : null;
2168
- if (delta?.type === "text_delta" && typeof delta.text === "string") {
2169
- assistantText += delta.text;
2170
- appendRunTimeline(context.projectRoot, input.runId, {
2171
- id: assistantMessageId,
2172
- type: "assistant_message",
2173
- text: assistantText,
2174
- state: "streaming",
2175
- createdAt: new Date().toISOString()
2176
- });
2177
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2178
- return;
2179
- }
2180
- }
2181
- if (input.runtimeAdapter === "pi" && isNonRenderablePiProtocolRecord(record)) {
2182
- return;
2183
- }
2184
- if (record.type === "assistant") {
2185
- const message = record.message && typeof record.message === "object" ? record.message : record;
2186
- const content = Array.isArray(message.content) ? message.content : [];
2187
- const nextText = content.map((entry) => entry && typeof entry === "object" && entry.type === "text" ? String(entry.text ?? "") : "").join("");
2188
- if (nextText.length > assistantText.length) {
2189
- assistantText = nextText;
2190
- appendRunTimeline(context.projectRoot, input.runId, {
2191
- id: assistantMessageId,
2192
- type: "assistant_message",
2193
- text: assistantText,
2194
- state: "streaming",
2195
- createdAt: new Date().toISOString()
2196
- });
2197
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2198
- }
2199
- return;
2200
- }
2201
- if (providerLogs.length > 0) {
2202
- return;
2203
- }
2204
- } catch {}
2205
- appendRunLog(context.projectRoot, input.runId, {
2206
- id: nextRunLogId(),
2207
- title: verificationStarted ? "Completion verification" : "Agent output",
2208
- detail: trimmed,
2209
- tone: "info",
2210
- status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
2211
- createdAt: new Date().toISOString()
2212
- });
2213
- emitServerRunEvent({ type: "log", runId: input.runId, title: verificationStarted ? "Completion verification" : "Agent output" });
2214
- };
2215
- let currentPrompt = prompt;
2216
- let exit = null;
2217
- let reviewFailureDetail = null;
2218
- const stderrLines = [];
2219
- const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
2220
- if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
2221
- appendRunLog(context.projectRoot, input.runId, {
2222
- id: `log:${input.runId}:resume-closeout-phase`,
2223
- title: "Resume continuing from closeout phase",
2224
- detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
2225
- tone: "info",
2226
- status: resumePreviousStatus,
2227
- createdAt: new Date().toISOString()
2228
- });
2229
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
2230
- exit = { code: 0, signal: null };
2231
- } else if (resumeMode && latestRuntimeWorkspace) {
2232
- const acceptedArtifactState = readTaskRunAcceptedArtifactState({
2233
- taskId: input.taskId ?? runtimeTaskId,
2234
- workspaceDir: latestRuntimeWorkspace
2235
- });
2236
- if (acceptedArtifactState.accepted) {
2237
- appendRunLog(context.projectRoot, input.runId, {
2238
- id: `log:${input.runId}:resume-accepted-artifacts`,
2239
- title: "Resume found accepted artifacts; continuing closeout",
2240
- detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
2241
- tone: "info",
2242
- status: "validating",
2243
- createdAt: new Date().toISOString()
2244
- });
2245
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
2246
- exit = { code: 0, signal: null };
2247
- }
2248
- }
2249
- for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
2250
- const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
2251
- const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
2252
- cwd: context.projectRoot,
2253
- env: childEnv,
2254
- stdio: ["pipe", "pipe", "pipe"]
2255
- });
2256
- const forwardSigterm = () => {
2257
- try {
2258
- child.kill("SIGTERM");
2259
- } catch {}
2260
- };
2261
- process.once("SIGTERM", forwardSigterm);
2262
- if (input.runtimeAdapter === "claude-code" || input.runtimeAdapter === "pi") {
2263
- child.stdin.write(currentPrompt);
2264
- child.stdin.end();
2265
- } else {
2266
- child.stdin.end();
2267
- }
2268
- const stdout = createLineInterface({ input: child.stdout });
2269
- stdout.on("line", handleAgentStdoutLine);
2270
- const stderr = createLineInterface({ input: child.stderr });
2271
- stderr.on("line", (line) => {
2272
- const trimmed = line.trim();
2273
- if (!trimmed)
2274
- return;
2275
- stderrLines.push(trimmed);
2276
- appendRunLog(context.projectRoot, input.runId, {
2277
- id: nextRunLogId(),
2278
- title: "Agent stderr",
2279
- detail: trimmed,
2280
- tone: "error",
2281
- status: reviewStarted ? "reviewing" : verificationStarted ? "validating" : "running",
2282
- createdAt: new Date().toISOString()
2283
- });
2284
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Agent stderr" });
2285
- });
2286
- let acceptedArtifactExit = false;
2287
- let acceptedArtifactObservedAt = null;
2288
- let acceptedArtifactPollTimer = null;
2289
- let acceptedArtifactKillTimer = null;
2290
- const attemptExit = await new Promise((resolve7) => {
2291
- let settled = false;
2292
- const settle = (result) => {
2293
- if (settled)
2294
- return;
2295
- settled = true;
2296
- if (acceptedArtifactPollTimer)
2297
- clearInterval(acceptedArtifactPollTimer);
2298
- resolve7(result);
2299
- };
2300
- const pollAcceptedArtifacts = () => {
2301
- const artifactState = readTaskRunAcceptedArtifactState({
2302
- taskId: input.taskId ?? runtimeTaskId,
2303
- workspaceDir: latestRuntimeWorkspace,
2304
- artifactNotBeforeMs: Date.parse(startedAt)
2305
- });
2306
- if (!artifactState.accepted) {
2307
- acceptedArtifactObservedAt = null;
2308
- return;
2309
- }
2310
- acceptedArtifactObservedAt ??= Date.now();
2311
- if (Date.now() - acceptedArtifactObservedAt < piAcceptedArtifactExitGraceMs || acceptedArtifactExit) {
2312
- return;
2313
- }
2314
- acceptedArtifactExit = true;
2315
- const detail = [
2316
- artifactState.reason ?? "Rig accepted task artifacts are present.",
2317
- `Pi has not exited after ${piAcceptedArtifactExitGraceMs}ms; terminating the stale Pi process so Rig can continue PR/issue closeout.`
2318
- ].join(" ");
2319
- appendRunLog(context.projectRoot, input.runId, {
2320
- id: `log:${input.runId}:pi-accepted-artifacts-exit`,
2321
- title: "Pi accepted artifacts; continuing closeout",
2322
- detail,
2323
- tone: "info",
2324
- status: "running",
2325
- createdAt: new Date().toISOString()
2326
- });
2327
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi accepted artifacts; continuing closeout" });
2328
- try {
2329
- child.kill("SIGTERM");
2330
- } catch {}
2331
- acceptedArtifactKillTimer = setTimeout(() => {
2332
- try {
2333
- child.kill("SIGKILL");
2334
- } catch {}
2335
- settle({ code: 0, signal: "SIGKILL" });
2336
- }, 5000);
2337
- };
2338
- child.once("error", (error) => {
2339
- if (acceptedArtifactKillTimer)
2340
- clearTimeout(acceptedArtifactKillTimer);
2341
- console.error(`[rig] agent process error: ${error.message}`);
2342
- settle({ code: 1, signal: null, error });
2343
- });
2344
- child.once("close", (code, signal) => {
2345
- if (acceptedArtifactKillTimer)
2346
- clearTimeout(acceptedArtifactKillTimer);
2347
- settle(acceptedArtifactExit ? { code: 0, signal: signal ?? "SIGTERM" } : { code, signal });
2348
- });
2349
- if (input.runtimeAdapter === "pi") {
2350
- acceptedArtifactPollTimer = setInterval(pollAcceptedArtifacts, 5000);
2351
- acceptedArtifactPollTimer.unref?.();
2352
- }
2353
- });
2354
- await finalizeTaskRunSnapshot({
2355
- snapshotSidecarPromise,
2356
- latestProviderCommand,
2357
- hostAgentCommand: attemptHostAgentCommand,
2358
- exit: attemptExit
2359
- });
2360
- const pendingLogs = input.runtimeAdapter === "codex" ? flushPendingCodexToolUseLogs({
2361
- runId: input.runId,
2362
- status: typeof attemptExit.code === "number" && attemptExit.code === 0 ? "completed" : "failed",
2363
- pendingToolUses: pendingCodexToolUses
2364
- }) : flushPendingClaudeToolUseLogs({
2365
- runId: input.runId,
2366
- status: typeof attemptExit.code === "number" && attemptExit.code === 0 ? "completed" : "failed",
2367
- pendingToolUses: pendingClaudeToolUses
2368
- });
2369
- for (const pendingLog of pendingLogs) {
2370
- appendRunLog(context.projectRoot, input.runId, pendingLog);
2371
- appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2372
- emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2373
- if (pendingLog.title === "Tool activity")
2374
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2375
- }
2376
- process.off("SIGTERM", forwardSigterm);
2377
- if (attemptExit.error) {
2378
- throw new CliError(`Task run failed to start: ${attemptExit.error.message}`, 1);
2379
- }
2380
- const retryContext = resolveTaskRunRetryContext({
2381
- projectRoot: context.projectRoot,
2382
- taskId: input.taskId ?? null,
2383
- workspaceDir: latestRuntimeWorkspace,
2384
- attempt,
2385
- maxAttempts,
2386
- basePrompt: prompt
2387
- });
2388
- if (retryContext.shouldRetry && retryContext.nextPrompt) {
2389
- const failureDetail = retryContext.failureDetail ?? summarizeValidationFailure(context.projectRoot, input.taskId ?? null) ?? "Rig validation failed.";
2390
- const retrySteering = buildPiValidationRetrySteeringPrompt({
2391
- taskId: input.taskId ?? runtimeTaskId,
2392
- attempt,
2393
- maxAttempts,
2394
- failureDetail,
2395
- validationOutput: summarizeValidationFailure(context.projectRoot, input.taskId ?? null)
2396
- });
2397
- currentPrompt = [retryContext.nextPrompt, "", "Rig Pi steering message:", retrySteering].join(`
2398
- `);
2399
- reviewFailureDetail = failureDetail;
2400
- setRunStatusOrFail(context.projectRoot, input.runId, "validating", { reason: "validation-retry" });
2401
- patchAuthorityRun(context.projectRoot, input.runId, {
2402
- completedAt: null,
2403
- errorText: null
2404
- });
2405
- appendPiStageLog({
2406
- projectRoot: context.projectRoot,
2407
- runId: input.runId,
2408
- stage: "Validate",
2409
- detail: failureDetail,
2410
- tone: "error",
2411
- status: "validating"
2412
- });
2413
- appendRunLog(context.projectRoot, input.runId, {
2414
- id: `log:${input.runId}:retry:${attempt + 1}`,
2415
- title: `Steering Pi to retry validation (attempt ${attempt + 1}/${maxAttempts})`,
2416
- detail: retrySteering,
2417
- tone: "info",
2418
- status: "running",
2419
- createdAt: new Date().toISOString()
2420
- });
2421
- emitServerRunEvent({
2422
- type: "status",
2423
- runId: input.runId,
2424
- status: "validating",
2425
- detail: failureDetail,
2426
- ...sessionAnchor()
2427
- });
2428
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Validate failed" });
2429
- emitServerRunEvent({ type: "log", runId: input.runId, title: `Steering Pi to retry validation (attempt ${attempt + 1}/${maxAttempts})` });
2430
- continue;
2431
- }
2432
- exit = attemptExit;
2433
- reviewFailureDetail = retryContext.failureDetail;
2434
- break;
2435
- }
2436
- if (!exit) {
2437
- throw new CliError(`Task run for ${runtimeTaskId} exhausted ${maxAttempts} attempts without a terminal result.`, 1);
2438
- }
2439
- if (exit.error) {
2440
- throw new CliError(`Task run failed to start: ${exit.error.message}`, 1);
2441
- }
2442
- if (assistantText.trim()) {
2443
- appendRunTimeline(context.projectRoot, input.runId, {
2444
- id: assistantMessageId,
2445
- type: "assistant_message",
2446
- text: assistantText,
2447
- state: "completed",
2448
- createdAt: new Date().toISOString(),
2449
- completedAt: new Date().toISOString()
2450
- });
2451
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2452
- }
2453
- if (exit.code !== 0 || reviewFailureDetail) {
2454
- const failedAt = new Date().toISOString();
2455
- const failureDetail = reviewFailureDetail ?? summarizeValidationFailure(context.projectRoot, input.taskId ?? null) ?? summarizeTaskRunProcessFailure({
2456
- exitCode: exit.code,
2457
- signal: exit.signal,
2458
- stderrLines
2459
- });
2460
- if (verificationAction && !reviewStarted) {
2461
- verificationAction.fail(failureDetail);
2462
- }
2463
- if (reviewAction) {
2464
- reviewAction.fail(failureDetail);
2465
- }
2466
- let terminalFailureDetail = failureDetail;
2467
- const terminalRunStatus = reviewFailureDetail ? "needs-attention" : "failed";
2468
- try {
2469
- await updateTaskSourceAfterDriverRun(context.projectRoot, input.runId, input.taskId, sourceTask, "failed", terminalRunStatus === "needs-attention" ? "Rig task run needs attention." : "Rig task run failed.", {
2470
- latestRuntimeWorkspace,
2471
- latestLogsDir,
2472
- latestSessionDir,
2473
- errorText: failureDetail
2474
- });
2475
- } catch (error) {
2476
- terminalFailureDetail = `${failureDetail}
2477
- Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${error instanceof Error ? error.message : String(error)}`;
2478
- }
2479
- setRunStatusOrFail(context.projectRoot, input.runId, terminalRunStatus, {
2480
- reason: "driver-terminal-failure",
2481
- errorText: terminalFailureDetail
2482
- });
2483
- patchAuthorityRun(context.projectRoot, input.runId, { completedAt: failedAt });
2484
- appendRunLog(context.projectRoot, input.runId, {
2485
- id: `log:${input.runId}:${terminalRunStatus}`,
2486
- title: terminalRunStatus === "needs-attention" ? "Task run needs attention" : "Task run failed",
2487
- detail: terminalFailureDetail,
2488
- tone: "error",
2489
- status: terminalRunStatus,
2490
- createdAt: failedAt
2491
- });
2492
- emitServerRunEvent({
2493
- type: "failed",
2494
- runId: input.runId,
2495
- error: terminalFailureDetail
2496
- });
2497
- throw new CliError(terminalFailureDetail, exit.code ?? 1);
2498
- }
2499
- if (planningClassification.planningRequired) {
2500
- const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
2501
- const expectedPlanPath = resolve6(planWorkspace, planningArtifactPath);
2502
- if (!existsSync4(expectedPlanPath)) {
2503
- const failedAt = new Date().toISOString();
2504
- const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
2505
- setRunStatusOrFail(context.projectRoot, input.runId, "needs-attention", {
2506
- reason: "plan-artifact-missing",
2507
- errorText: failureDetail
2508
- });
2509
- patchAuthorityRun(context.projectRoot, input.runId, { completedAt: failedAt });
2510
- appendRunLog(context.projectRoot, input.runId, {
2511
- id: `log:${input.runId}:plan-artifact-missing`,
2512
- title: "Required plan artifact missing",
2513
- detail: failureDetail,
2514
- tone: "error",
2515
- status: "needs_attention",
2516
- createdAt: failedAt
2517
- });
2518
- emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
2519
- throw new CliError(failureDetail, 1);
2520
- }
2521
- }
2522
- if (process.env.RIG_SERVER_OWNS_CLOSEOUT === "1") {
2523
- appendPiStageLog({
2524
- projectRoot: context.projectRoot,
2525
- runId: input.runId,
2526
- stage: "Validate",
2527
- detail: "Rig validation accepted the task run; server will continue PR/review/merge closeout.",
2528
- status: "completed"
2529
- });
2530
- if (verificationAction && !reviewStarted) {
2531
- verificationAction.complete("Completion verification checks finished.");
2532
- }
2533
- if (!reviewAction) {
2534
- promoteToReviewing("Server-owned closeout is queued.");
2535
- }
2536
- if (reviewAction) {
2537
- reviewAction.complete("Provider work accepted; server-owned closeout requested.");
2538
- }
2539
- const requestedAt = new Date().toISOString();
2540
- setRunStatusOrFail(context.projectRoot, input.runId, "closing-out", { reason: "server-closeout-requested" });
2541
- patchAuthorityRun(context.projectRoot, input.runId, {
2542
- completedAt: null,
2543
- errorText: null,
2544
- serverCloseout: {
2545
- status: "pending",
2546
- phase: "queued",
2547
- updatedAt: requestedAt,
2548
- runtimeWorkspace: latestRuntimeWorkspace,
2549
- branch: latestRuntimeBranch,
2550
- taskId: input.taskId ?? runtimeTaskId
2551
- }
2552
- });
2553
- appendRunLog(context.projectRoot, input.runId, {
2554
- id: `log:${input.runId}:server-closeout-requested`,
2555
- title: "Server-owned closeout requested",
2556
- detail: "The CLI provider worker finished validation and handed commit/PR/review/merge closeout back to the Rig server.",
2557
- tone: "info",
2558
- status: "reviewing",
2559
- createdAt: requestedAt,
2560
- payload: { runtimeWorkspace: latestRuntimeWorkspace, branch: latestRuntimeBranch }
2561
- });
2562
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Server-owned closeout requested" });
2563
- emitServerRunEvent({ type: "status", runId: input.runId, status: "closing-out", detail: "Server-owned closeout requested.", ...sessionAnchor() });
2564
- await context.emitEvent("command.finished", {
2565
- command: [
2566
- "rig",
2567
- "server",
2568
- "task-run",
2569
- ...input.taskId ? ["--task", input.taskId] : [],
2570
- ...input.title ? ["--title", input.title] : []
2571
- ],
2572
- formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2573
- exitCode: 0,
2574
- durationMs: 0,
2575
- startedAt,
2576
- finishedAt: requestedAt
2577
- });
2578
- process.exit(0);
2579
- }
2580
- const runPiPrFeedbackFix = async (message) => {
2581
- appendPiStageLog({
2582
- projectRoot: context.projectRoot,
2583
- runId: input.runId,
2584
- stage: "Implement",
2585
- detail: "Pi is applying PR review/CI feedback.",
2586
- status: "running"
2587
- });
2588
- appendRunLog(context.projectRoot, input.runId, {
2589
- id: `log:${input.runId}:pr-feedback-pi:${Date.now()}`,
2590
- title: "Steering Pi from PR feedback",
2591
- detail: message,
2592
- tone: "info",
2593
- status: "reviewing",
2594
- createdAt: new Date().toISOString()
2595
- });
2596
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Steering Pi from PR feedback" });
2597
- const feedbackCommand = buildHostAgentCommand(message);
2598
- const child = spawn(feedbackCommand[0], feedbackCommand.slice(1), {
2599
- cwd: latestRuntimeWorkspace ?? context.projectRoot,
2600
- env: childEnv,
2601
- stdio: ["pipe", "pipe", "pipe"]
2602
- });
2603
- if (input.runtimeAdapter === "claude-code" || input.runtimeAdapter === "pi") {
2604
- child.stdin.write(message);
2605
- }
2606
- child.stdin.end();
2607
- const feedbackAssistantMessageId = `message:${input.runId}:pr-feedback:${Date.now()}:assistant`;
2608
- let feedbackAssistantText = "";
2609
- const feedbackPendingToolUses = new Map;
2610
- const stdout = createLineInterface({ input: child.stdout });
2611
- stdout.on("line", (line) => {
2612
- const trimmed = line.trim();
2613
- if (!trimmed)
2614
- return;
2615
- try {
2616
- const record = JSON.parse(trimmed);
2617
- const providerLogs = buildClaudeLogsFromRecord({
2618
- runId: input.runId,
2619
- record,
2620
- createdAtFallback: new Date().toISOString(),
2621
- status: "reviewing",
2622
- pendingToolUses: feedbackPendingToolUses
2623
- });
2624
- for (const providerLog of providerLogs) {
2625
- appendRunLog(context.projectRoot, input.runId, providerLog);
2626
- appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: providerLog });
2627
- emitServerRunEvent({ type: "log", runId: input.runId, title: providerLog.title });
2628
- if (providerLog.title === "Tool activity")
2629
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2630
- }
2631
- const nextFeedbackAssistantText = appendAssistantTimelineFromRecord({
2632
- projectRoot: context.projectRoot,
2633
- runId: input.runId,
2634
- messageId: feedbackAssistantMessageId,
2635
- record,
2636
- assistantText: feedbackAssistantText
2637
- });
2638
- const hadAssistantDelta = nextFeedbackAssistantText !== feedbackAssistantText;
2639
- if (hadAssistantDelta) {
2640
- feedbackAssistantText = nextFeedbackAssistantText;
2641
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2642
- }
2643
- if (providerLogs.length > 0 || hadAssistantDelta)
2644
- return;
2645
- } catch {}
2646
- appendRunLog(context.projectRoot, input.runId, {
2647
- id: nextRunLogId(),
2648
- title: "Pi PR feedback fix output",
2649
- detail: trimmed,
2650
- tone: "info",
2651
- status: "reviewing",
2652
- createdAt: new Date().toISOString()
2653
- });
2654
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix output" });
2655
- });
2656
- const stderr = createLineInterface({ input: child.stderr });
2657
- stderr.on("line", (line) => {
2658
- const trimmed = line.trim();
2659
- if (!trimmed)
2660
- return;
2661
- appendRunLog(context.projectRoot, input.runId, {
2662
- id: nextRunLogId(),
2663
- title: "Pi PR feedback fix stderr",
2664
- detail: trimmed,
2665
- tone: "error",
2666
- status: "reviewing",
2667
- createdAt: new Date().toISOString()
2668
- });
2669
- emitServerRunEvent({ type: "log", runId: input.runId, title: "Pi PR feedback fix stderr" });
2670
- });
2671
- const exitCode = await new Promise((resolve7) => {
2672
- child.once("error", () => resolve7(1));
2673
- child.once("close", (code) => resolve7(code ?? 1));
2674
- });
2675
- for (const pendingLog of flushPendingClaudeToolUseLogs({
2676
- runId: input.runId,
2677
- status: exitCode === 0 ? "completed" : "failed",
2678
- pendingToolUses: feedbackPendingToolUses
2679
- })) {
2680
- appendRunLog(context.projectRoot, input.runId, pendingLog);
2681
- appendToolTimelineFromLog({ projectRoot: context.projectRoot, runId: input.runId, log: pendingLog });
2682
- emitServerRunEvent({ type: "log", runId: input.runId, title: pendingLog.title });
2683
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2684
- }
2685
- if (feedbackAssistantText.trim()) {
2686
- appendRunTimeline(context.projectRoot, input.runId, {
2687
- id: feedbackAssistantMessageId,
2688
- type: "assistant_message",
2689
- text: feedbackAssistantText,
2690
- state: "completed",
2691
- createdAt: new Date().toISOString(),
2692
- completedAt: new Date().toISOString()
2693
- });
2694
- emitServerRunEvent({ type: "timeline", runId: input.runId });
2695
- }
2696
- if (exitCode !== 0) {
2697
- throw new Error(`Pi PR feedback fix failed with exit code ${exitCode}.`);
2698
- }
2699
- };
2700
- try {
2701
- appendPiStageLog({
2702
- projectRoot: context.projectRoot,
2703
- runId: input.runId,
2704
- stage: "Validate",
2705
- detail: "Rig validation accepted the task run.",
2706
- status: "completed"
2707
- });
2708
- const postValidation = await runTaskRunPostValidationLifecycle({
2709
- projectRoot: context.projectRoot,
2710
- runId: input.runId,
2711
- taskId: input.taskId,
2712
- runtimeTaskId,
2713
- runtimeWorkspace: latestRuntimeWorkspace,
2714
- branch: latestRuntimeBranch,
2715
- config: automationConfig,
2716
- sourceTask,
2717
- steerPi: input.runtimeAdapter === "pi" ? runPiPrFeedbackFix : undefined,
2718
- appendStage: (stage, detail, status, tone) => appendPiStageLog({
2719
- projectRoot: context.projectRoot,
2720
- runId: input.runId,
2721
- stage,
2722
- detail,
2723
- status,
2724
- tone
2725
- })
2726
- });
2727
- if (postValidation.status === "needs_attention") {
2728
- const failureDetail = postValidation.pr?.actionableFeedback.join(`
2729
- `) || "PR automation needs attention.";
2730
- const failedAt = new Date().toISOString();
2731
- setRunStatusOrFail(context.projectRoot, input.runId, "needs-attention", {
2732
- reason: "pr-automation-needs-attention",
2733
- errorText: failureDetail
2734
- });
2735
- patchAuthorityRun(context.projectRoot, input.runId, { completedAt: failedAt });
2736
- emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
2737
- throw new CliError(failureDetail, 1);
2738
- }
2739
- } catch (error) {
2740
- if (error instanceof CliError) {
2741
- 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);
2742
171
  }
2743
- const failureDetail = `Failed to complete PR/issue closeout for ${input.taskId ?? runtimeTaskId}: ${error instanceof Error ? error.message : String(error)}`;
2744
- const failedAt = new Date().toISOString();
2745
- setRunStatusOrFail(context.projectRoot, input.runId, "failed", {
2746
- reason: "pr-closeout-failed",
2747
- errorText: failureDetail
2748
- });
2749
- patchAuthorityRun(context.projectRoot, input.runId, { completedAt: failedAt });
2750
- appendRunLog(context.projectRoot, input.runId, {
2751
- id: `log:${input.runId}:pr-closeout-failed`,
2752
- title: "PR/issue closeout failed",
2753
- detail: failureDetail,
2754
- tone: "error",
2755
- status: "failed",
2756
- createdAt: failedAt
2757
- });
2758
- emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
2759
- throw new CliError(failureDetail, 1);
2760
- }
2761
- if (verificationAction && !reviewStarted) {
2762
- verificationAction.complete("Completion verification checks finished.");
2763
- }
2764
- if (!reviewAction) {
2765
- promoteToReviewing("Task finished; finalizing run state.");
2766
- }
2767
- if (reviewAction) {
2768
- reviewAction.complete("Run marked completed.");
2769
- }
2770
- setRunStatusOrFail(context.projectRoot, input.runId, "completed", { reason: "driver-completed" });
2771
- emitServerRunEvent({ type: "completed", runId: input.runId });
2772
- await context.emitEvent("command.finished", {
2773
- command: [
2774
- "rig",
2775
- "server",
2776
- "task-run",
2777
- ...input.taskId ? ["--task", input.taskId] : [],
2778
- ...input.title ? ["--title", input.title] : []
2779
- ],
2780
- formattedCommand: input.taskId ? `rig server task-run --task ${input.taskId}` : `rig server task-run --title ${JSON.stringify(input.title ?? `Run ${input.runId}`)}`,
2781
- exitCode: 0,
2782
- durationMs: 0,
2783
- startedAt,
2784
- finishedAt: new Date().toISOString()
2785
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;
2786
186
  }
2787
187
  export {
2788
188
  taskRunStageLogId,
@@ -2790,7 +190,6 @@ export {
2790
190
  runTaskRunPostValidationLifecycle,
2791
191
  resolveTaskRunAutomationLimits,
2792
192
  readTaskRunAcceptedArtifactState,
2793
- executeRigOwnedTaskRun,
2794
193
  createRunLogIdFactory,
2795
194
  classifyValidationRetryOutcome,
2796
195
  classifyPlanningNeed,