@h-rig/run-worker 0.0.6-alpha.155 → 0.0.6-alpha.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/autohost.js +10 -10
- package/dist/src/extension.js +10 -10
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +108 -33
- package/dist/src/notifications.js +1 -1
- package/dist/src/panel-plugin.d.ts +4 -5
- package/dist/src/panel-plugin.js +2 -2
- package/dist/src/plugin.d.ts +14 -0
- package/dist/src/plugin.js +851 -0
- package/dist/src/runs/control.d.ts +24 -0
- package/dist/src/runs/control.js +398 -0
- package/dist/src/runs/diagnostics.d.ts +10 -0
- package/dist/src/runs/diagnostics.js +53 -0
- package/dist/src/runs/guard.d.ts +4 -0
- package/dist/src/runs/guard.js +26 -0
- package/dist/src/runs/inbox.d.ts +44 -0
- package/dist/src/runs/inbox.js +499 -0
- package/dist/src/runs/index.d.ts +9 -0
- package/dist/src/runs/index.js +990 -0
- package/dist/src/runs/inspect.d.ts +27 -0
- package/dist/src/runs/inspect.js +459 -0
- package/dist/src/runs/projection.d.ts +24 -0
- package/dist/src/runs/projection.js +356 -0
- package/dist/src/runs/run-status.d.ts +20 -0
- package/dist/src/runs/run-status.js +181 -0
- package/dist/src/runs/stats.d.ts +13 -0
- package/dist/src/runs/stats.js +485 -0
- package/package.json +13 -8
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// packages/run-worker/src/journal.ts
|
|
19
|
+
import { createJournalSessionProvider } from "@rig/kernel/journal-session-provider";
|
|
20
|
+
import { RunSessionJournal } from "@rig/runtime/control-plane/run-session-writer";
|
|
21
|
+
async function createRunJournal(sessionManager, runId) {
|
|
22
|
+
try {
|
|
23
|
+
const journal = new RunSessionJournal(sessionManager, runId);
|
|
24
|
+
const kernel = createJournalSessionProvider({
|
|
25
|
+
runId,
|
|
26
|
+
store: {
|
|
27
|
+
appendCustomEntry: (customType, data) => sessionManager.appendCustomEntry(customType, data),
|
|
28
|
+
getEntries: () => sessionManager.getEntries?.() ?? sessionManager.getBranch?.() ?? []
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
kernel,
|
|
33
|
+
appendStatus: journal.appendStatus.bind(journal),
|
|
34
|
+
appendTimeline: journal.appendTimeline.bind(journal),
|
|
35
|
+
appendCloseoutPhase: journal.appendCloseoutPhase.bind(journal),
|
|
36
|
+
appendApprovalResolved: journal.appendApprovalResolved.bind(journal),
|
|
37
|
+
appendInputResolved: journal.appendInputResolved.bind(journal),
|
|
38
|
+
appendStall: journal.appendStall.bind(journal)
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(`[rig-run] RunSessionJournal unavailable; run-state arming deferred: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var init_journal = () => {};
|
|
46
|
+
|
|
47
|
+
// packages/run-worker/src/notifications.ts
|
|
48
|
+
import { resolve } from "path";
|
|
49
|
+
import { dispatchEventToTargets, loadNotificationConfig } from "@rig/notifications-plugin/notifications";
|
|
50
|
+
async function dispatchRunNotifications(projectRoot, runId, taskId, outcome, detail) {
|
|
51
|
+
try {
|
|
52
|
+
const config = await loadNotificationConfig(resolve(projectRoot, ".rig", "notifications.json"));
|
|
53
|
+
if (config.targets.length === 0)
|
|
54
|
+
return;
|
|
55
|
+
const event = { runId, type: `run.${outcome}`, timestamp: new Date().toISOString(), payload: { taskId, detail } };
|
|
56
|
+
await Promise.race([
|
|
57
|
+
dispatchEventToTargets(event, config.targets),
|
|
58
|
+
new Promise((resolveCap) => setTimeout(resolveCap, 5000))
|
|
59
|
+
]);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
var init_notifications = () => {};
|
|
63
|
+
|
|
64
|
+
// packages/run-worker/src/constants.ts
|
|
65
|
+
var RUN_PROCESS_STEER_TIMEOUT_MS, TRACKED_RUN_STALL_MS, RUN_PROCESS_STALL_SWEEP_MS, RUN_PROCESS_STALL_DETAIL = "Run process made no OMP session progress for 20+ minutes; recording a stall so recovery can requeue or resume the session.";
|
|
66
|
+
var init_constants = __esm(() => {
|
|
67
|
+
RUN_PROCESS_STEER_TIMEOUT_MS = 10 * 60 * 1000;
|
|
68
|
+
TRACKED_RUN_STALL_MS = 20 * 60 * 1000;
|
|
69
|
+
RUN_PROCESS_STALL_SWEEP_MS = 60 * 1000;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// packages/run-worker/src/stall.ts
|
|
73
|
+
function timestampMs(value) {
|
|
74
|
+
if (value === null || value === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
const ms = value instanceof Date ? value.getTime() : typeof value === "number" ? value : Date.parse(value);
|
|
77
|
+
return Number.isFinite(ms) ? ms : null;
|
|
78
|
+
}
|
|
79
|
+
function computeRunStall(input) {
|
|
80
|
+
const lastActivityAt = timestampMs(input.lastActivityAt);
|
|
81
|
+
const now = timestampMs(input.now);
|
|
82
|
+
if (lastActivityAt === null || now === null || input.thresholdMs <= 0)
|
|
83
|
+
return false;
|
|
84
|
+
return now - lastActivityAt >= input.thresholdMs;
|
|
85
|
+
}
|
|
86
|
+
function appendRunStallDetected(journal, detail = RUN_PROCESS_STALL_DETAIL) {
|
|
87
|
+
if (!journal)
|
|
88
|
+
return false;
|
|
89
|
+
try {
|
|
90
|
+
journal.appendStall({ detail });
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn(`[rig-run] stall-detected append failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function startRunProcessStallMonitor(opts) {
|
|
98
|
+
if (!opts.journal)
|
|
99
|
+
return () => {};
|
|
100
|
+
let stallDetected = opts.alreadyStalled === true;
|
|
101
|
+
const thresholdMs = opts.thresholdMs ?? TRACKED_RUN_STALL_MS;
|
|
102
|
+
const now = opts.now ?? (() => Date.now());
|
|
103
|
+
const timer = setInterval(() => {
|
|
104
|
+
if (stallDetected)
|
|
105
|
+
return;
|
|
106
|
+
if (!computeRunStall({ lastActivityAt: opts.lastActivityAt(), now: now(), thresholdMs }))
|
|
107
|
+
return;
|
|
108
|
+
stallDetected = true;
|
|
109
|
+
appendRunStallDetected(opts.journal);
|
|
110
|
+
}, opts.intervalMs ?? RUN_PROCESS_STALL_SWEEP_MS);
|
|
111
|
+
if (typeof timer.unref === "function")
|
|
112
|
+
timer.unref();
|
|
113
|
+
return () => clearInterval(timer);
|
|
114
|
+
}
|
|
115
|
+
var init_stall = __esm(() => {
|
|
116
|
+
init_constants();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// packages/run-worker/src/panel-plugin.ts
|
|
120
|
+
import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/core/project-plugins";
|
|
121
|
+
import { RIG_CAPABILITY_PANEL_SLOT, RIG_RUN_STOP_PANEL_ACTION, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
|
|
122
|
+
async function produceWorkerPanelPayload(producer, context) {
|
|
123
|
+
if (!producer.produce)
|
|
124
|
+
return;
|
|
125
|
+
let timeout;
|
|
126
|
+
try {
|
|
127
|
+
return await Promise.race([
|
|
128
|
+
Promise.resolve(producer.produce(context)),
|
|
129
|
+
new Promise((resolve2) => {
|
|
130
|
+
timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
|
|
131
|
+
})
|
|
132
|
+
]);
|
|
133
|
+
} finally {
|
|
134
|
+
clearTimeout(timeout);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function loadWorkerPanelRegistry(projectRoot) {
|
|
138
|
+
const { host, resolved } = await createProjectPluginHost(projectRoot, {
|
|
139
|
+
mode: "strict-config-only",
|
|
140
|
+
surfaceName: "run-worker-panel-registry"
|
|
141
|
+
});
|
|
142
|
+
for (const message of projectPluginResolutionWarningMessages(resolved)) {
|
|
143
|
+
console.warn(`[rig-run] ${message}`);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
|
|
147
|
+
producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels", PANEL_PRODUCER_TIMEOUT_MS = 2000;
|
|
151
|
+
var init_panel_plugin = () => {};
|
|
152
|
+
|
|
153
|
+
// packages/run-worker/src/utils.ts
|
|
154
|
+
import { createWorkflowStatusChanged, RIG_WORKFLOW_STATUS_CHANGED } from "@rig/contracts";
|
|
155
|
+
import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/runtime/control-plane/remote-config";
|
|
156
|
+
function customEntries(entries) {
|
|
157
|
+
const customs = [];
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (entry.type === "custom")
|
|
160
|
+
customs.push(entry);
|
|
161
|
+
}
|
|
162
|
+
return customs;
|
|
163
|
+
}
|
|
164
|
+
function rigProjectRoot() {
|
|
165
|
+
return process.env.PROJECT_RIG_ROOT?.trim() || process.env.RIG_HOST_PROJECT_ROOT?.trim() || process.cwd();
|
|
166
|
+
}
|
|
167
|
+
function registryBaseUrl() {
|
|
168
|
+
return resolveRegistryBaseUrl(rigProjectRoot());
|
|
169
|
+
}
|
|
170
|
+
function rigRelayUrl() {
|
|
171
|
+
return resolveRelayUrl();
|
|
172
|
+
}
|
|
173
|
+
var init_utils = () => {};
|
|
174
|
+
|
|
175
|
+
// packages/run-worker/src/autohost.ts
|
|
176
|
+
import { runPipelineCloseout } from "@rig/bundle-default-lifecycle/pipeline-closeout";
|
|
177
|
+
import { adoptPlacementKernel } from "@rig/kernel/kernel-entrypoint";
|
|
178
|
+
import { latestTimelineEntriesFromCustomEntries, parseInboxResolutionSentinel, parsePauseSentinel, parseResumeSentinel, parseStopSentinel, RIG_RUN_STOP_PANEL_ACTION as RIG_RUN_STOP_PANEL_ACTION2, RIG_SUPERVISOR_PANEL_ID as RIG_SUPERVISOR_PANEL_ID2, sessionIdFromSessionFile } from "@rig/contracts";
|
|
179
|
+
import { Duration, Effect, Fiber, Stream } from "effect";
|
|
180
|
+
import { createEnvCloseoutRunners } from "@rig/bundle-default-lifecycle/native/closeout-runners";
|
|
181
|
+
import { CloseoutValidationError } from "@rig/bundle-default-lifecycle/native/in-process-closeout";
|
|
182
|
+
import { projectRunFromSession } from "@rig/runtime/control-plane/run-session-projection";
|
|
183
|
+
import { localRunChanges } from "@rig/runtime/control-plane/run-discovery-stream";
|
|
184
|
+
import { updateRunTaskSourceLifecycle } from "@rig/runtime/control-plane/tasks/source-lifecycle";
|
|
185
|
+
import { resolveOwnerNamespaceKey } from "@rig/runtime/control-plane/remote-config";
|
|
186
|
+
import { resolveRigIdentity } from "@rig/runtime/control-plane/identity";
|
|
187
|
+
import { connectWorkerProjection, createRegistryClient } from "@rig/relay-registry";
|
|
188
|
+
import { coerceRegistryStatus } from "@rig/relay-registry/schema";
|
|
189
|
+
function syntheticRegistryOwner(namespaceKey) {
|
|
190
|
+
const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
|
|
191
|
+
return {
|
|
192
|
+
githubUserId: githubUserId || "anonymous",
|
|
193
|
+
login: "anonymous",
|
|
194
|
+
namespaceKey
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function reflectStoppedRunTaskSource(input) {
|
|
198
|
+
const taskId = input.taskId?.trim();
|
|
199
|
+
if (!taskId)
|
|
200
|
+
return false;
|
|
201
|
+
const reason = input.reason?.trim();
|
|
202
|
+
await (input.updateLifecycle ?? updateRunTaskSourceLifecycle)(input.projectRoot, {
|
|
203
|
+
runId: input.runId,
|
|
204
|
+
taskId,
|
|
205
|
+
sourceTask: input.sourceTask,
|
|
206
|
+
worktreePath: input.worktreePath,
|
|
207
|
+
logRoot: input.logRoot,
|
|
208
|
+
sessionPath: input.sessionPath
|
|
209
|
+
}, "cancelled", reason ? `Rig stopped by operator: ${reason}` : "Rig stopped by operator.");
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
function registryRunProjection(input) {
|
|
213
|
+
const timeline = input.timeline ?? latestTimelineEntriesFromCustomEntries(input.entries, REGISTRY_PROJECTION_TIMELINE_LIMIT);
|
|
214
|
+
return {
|
|
215
|
+
runId: input.runId,
|
|
216
|
+
taskId: typeof input.folded.record.taskId === "string" ? input.folded.record.taskId : null,
|
|
217
|
+
title: typeof input.folded.record.title === "string" ? input.folded.record.title : input.title,
|
|
218
|
+
status: coerceRegistryStatus(input.folded.status, "running"),
|
|
219
|
+
source: "local",
|
|
220
|
+
startedAt: typeof input.folded.record.startedAt === "string" ? input.folded.record.startedAt : null,
|
|
221
|
+
updatedAt: input.folded.lastEventAt ?? (typeof input.folded.record.updatedAt === "string" ? input.folded.record.updatedAt : new Date().toISOString()),
|
|
222
|
+
completedAt: typeof input.folded.record.completedAt === "string" ? input.folded.record.completedAt : null,
|
|
223
|
+
joinLink: input.joinLink ?? null,
|
|
224
|
+
webLink: input.webLink ?? null,
|
|
225
|
+
relayUrl: input.relayUrl ?? null,
|
|
226
|
+
sessionPath: typeof input.folded.record.sessionPath === "string" ? input.folded.record.sessionPath : input.sessionPath ?? null,
|
|
227
|
+
prUrl: typeof input.folded.record.prUrl === "string" ? input.folded.record.prUrl : null,
|
|
228
|
+
worktreePath: typeof input.folded.record.worktreePath === "string" ? input.folded.record.worktreePath : process.cwd(),
|
|
229
|
+
collabCwd: process.cwd(),
|
|
230
|
+
cwd: process.cwd(),
|
|
231
|
+
dispatchHandle: process.env.RIG_RUN_ID?.trim() ?? null,
|
|
232
|
+
pendingApprovals: input.folded.pendingApprovals.length,
|
|
233
|
+
pendingInputs: input.folded.pendingUserInputs.length,
|
|
234
|
+
steeringCount: input.folded.steeringCount,
|
|
235
|
+
stallCount: input.folded.stallCount,
|
|
236
|
+
...typeof input.folded.record.errorText === "string" ? { errorSummary: input.folded.record.errorText } : { errorSummary: null },
|
|
237
|
+
timeline: [...timeline]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function textFromContent(content) {
|
|
241
|
+
if (typeof content === "string")
|
|
242
|
+
return content;
|
|
243
|
+
if (!Array.isArray(content))
|
|
244
|
+
return null;
|
|
245
|
+
const text = content.map((part) => part && typeof part === "object" && ("text" in part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join(`
|
|
246
|
+
`).trim();
|
|
247
|
+
return text || null;
|
|
248
|
+
}
|
|
249
|
+
function textFromSessionEntry(entry) {
|
|
250
|
+
if (!entry || typeof entry !== "object")
|
|
251
|
+
return null;
|
|
252
|
+
const record = entry;
|
|
253
|
+
if (record.type === "custom_message")
|
|
254
|
+
return textFromContent(record.content);
|
|
255
|
+
if (record.type === "message")
|
|
256
|
+
return textFromAgentMessage(record.message);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
function textFromAgentMessage(message) {
|
|
260
|
+
if (!message || typeof message !== "object")
|
|
261
|
+
return null;
|
|
262
|
+
return textFromContent(message.content);
|
|
263
|
+
}
|
|
264
|
+
function detectRunControlText(text, runId) {
|
|
265
|
+
const stop = parseStopSentinel(text, runId);
|
|
266
|
+
if (stop)
|
|
267
|
+
return { kind: "stop", reason: stop.reason };
|
|
268
|
+
if (parsePauseSentinel(text, runId))
|
|
269
|
+
return { kind: "pause" };
|
|
270
|
+
if (parseResumeSentinel(text, runId))
|
|
271
|
+
return { kind: "resume" };
|
|
272
|
+
const resolution = parseInboxResolutionSentinel(text, runId);
|
|
273
|
+
if (resolution)
|
|
274
|
+
return { kind: "inbox", resolution };
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
async function maybeStartRunProcessAutohost(api, ctx) {
|
|
278
|
+
if (process.env.RIG_RUN_PROCESS !== "1")
|
|
279
|
+
return null;
|
|
280
|
+
const envRunId = process.env.RIG_RUN_ID?.trim();
|
|
281
|
+
if (!envRunId)
|
|
282
|
+
throw new Error("RIG_RUN_ID is required when RIG_RUN_PROCESS=1");
|
|
283
|
+
const sessionManagerRunId = typeof ctx.sessionManager.getSessionId === "function" ? ctx.sessionManager.getSessionId().trim() : "";
|
|
284
|
+
const runId = sessionIdFromSessionFile(ctx.sessionManager.getSessionFile()) || sessionManagerRunId;
|
|
285
|
+
if (!runId)
|
|
286
|
+
throw new Error("OMP session id is required when RIG_RUN_PROCESS=1; RIG_RUN_ID is only a dispatch handle.");
|
|
287
|
+
const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
|
|
288
|
+
const journal = await createRunJournal(ctx.sessionManager, runId);
|
|
289
|
+
await adoptPlacementKernel(projectRoot, {
|
|
290
|
+
entrypoint: "run-worker",
|
|
291
|
+
...journal ? { journal: journal.kernel } : {}
|
|
292
|
+
});
|
|
293
|
+
if (!ctx.hasUI)
|
|
294
|
+
return null;
|
|
295
|
+
const collab = ctx.collab;
|
|
296
|
+
const startHost = collab?.startCollabHost ?? collab?.startHost;
|
|
297
|
+
if (!startHost)
|
|
298
|
+
throw new Error("OMP collab host facade is unavailable for Rig run process.");
|
|
299
|
+
const identity = resolveRigIdentity(ctx);
|
|
300
|
+
const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
|
|
301
|
+
let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
|
|
302
|
+
const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
|
|
303
|
+
const runDisplayTitle = (() => {
|
|
304
|
+
const base = process.env.RIG_RUN_TITLE?.trim() || `Rig run ${runId}`;
|
|
305
|
+
return taskIdAtStart && !base.includes(taskIdAtStart) ? `[${taskIdAtStart}] ${base}` : base;
|
|
306
|
+
})();
|
|
307
|
+
collab?.stopHost;
|
|
308
|
+
let closeoutStarted = false;
|
|
309
|
+
let forcedSteerUsed = false;
|
|
310
|
+
let lastRunActivityAt = Date.now();
|
|
311
|
+
const markRunActivity = () => {
|
|
312
|
+
lastRunActivityAt = Date.now();
|
|
313
|
+
};
|
|
314
|
+
let runRegistryHeartbeat = null;
|
|
315
|
+
let runRegistryRoomId = null;
|
|
316
|
+
let workerProjectionConnection = null;
|
|
317
|
+
let workerProjectionFiber = null;
|
|
318
|
+
const runRegistryNamespace = identity?.owner?.namespaceKey ?? resolveOwnerNamespaceKey(rigProjectRoot()) ?? "anonymous";
|
|
319
|
+
const runRegistryOwner = identity?.owner ?? syntheticRegistryOwner(runRegistryNamespace);
|
|
320
|
+
const runRegistryRepo = identity?.selectedRepo ?? "";
|
|
321
|
+
const runRegistry = createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace });
|
|
322
|
+
let runRegistryLinks = {};
|
|
323
|
+
const processedControlEntryIds = new Set((ctx.sessionManager.getEntries?.() ?? []).map((entry) => entry && typeof entry === "object" && typeof entry.id === "string" ? entry.id : null).filter((id) => id !== null));
|
|
324
|
+
const processedControlEntryObjects = new WeakSet;
|
|
325
|
+
let pendingPause = false;
|
|
326
|
+
let pendingStopReason;
|
|
327
|
+
let cachedProjectionParts = null;
|
|
328
|
+
const currentProjectionParts = () => {
|
|
329
|
+
const branch = ctx.sessionManager.getBranch();
|
|
330
|
+
const lastBranchEntry = branch.at(-1);
|
|
331
|
+
const lastBranchObject = lastBranchEntry && typeof lastBranchEntry === "object" ? lastBranchEntry : null;
|
|
332
|
+
if (!cachedProjectionParts || cachedProjectionParts.branchLength !== branch.length || cachedProjectionParts.lastBranchEntry !== lastBranchObject) {
|
|
333
|
+
const entries = customEntries(branch);
|
|
334
|
+
cachedProjectionParts = {
|
|
335
|
+
branchLength: branch.length,
|
|
336
|
+
lastBranchEntry: lastBranchObject,
|
|
337
|
+
entries,
|
|
338
|
+
folded: projectRunFromSession(entries, runId),
|
|
339
|
+
timeline: latestTimelineEntriesFromCustomEntries(entries, REGISTRY_PROJECTION_TIMELINE_LIMIT)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
runProjection = cachedProjectionParts.folded;
|
|
343
|
+
return { entries: cachedProjectionParts.entries, folded: cachedProjectionParts.folded, timeline: cachedProjectionParts.timeline };
|
|
344
|
+
};
|
|
345
|
+
const buildCurrentRegistryProjection = (links = runRegistryLinks) => {
|
|
346
|
+
const { entries, folded, timeline } = currentProjectionParts();
|
|
347
|
+
return registryRunProjection({
|
|
348
|
+
runId,
|
|
349
|
+
folded,
|
|
350
|
+
entries,
|
|
351
|
+
title: runDisplayTitle,
|
|
352
|
+
joinLink: links.joinLink,
|
|
353
|
+
webLink: links.webLink,
|
|
354
|
+
relayUrl: links.relayUrl,
|
|
355
|
+
sessionPath: ctx.sessionManager.getSessionFile(),
|
|
356
|
+
timeline
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
let lastRigPanelsSnapshotJson = "";
|
|
360
|
+
const publishRigPanelsSnapshot = async () => {
|
|
361
|
+
const appendCustomMessageEntry = ctx.sessionManager.appendCustomMessageEntry?.bind(ctx.sessionManager);
|
|
362
|
+
if (!appendCustomMessageEntry)
|
|
363
|
+
return;
|
|
364
|
+
const { folded } = currentProjectionParts();
|
|
365
|
+
const producerContext = {
|
|
366
|
+
projectRoot,
|
|
367
|
+
runId,
|
|
368
|
+
folded,
|
|
369
|
+
taskIdAtStart,
|
|
370
|
+
runDisplayTitle
|
|
371
|
+
};
|
|
372
|
+
const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
|
|
373
|
+
try {
|
|
374
|
+
const payload = await produceWorkerPanelPayload(producer, producerContext);
|
|
375
|
+
return payload === undefined ? null : [producer.id, payload];
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error(`[rig-run] panel-producer-failed panel=${producer.id} ${error instanceof Error ? error.message : String(error)}`);
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
const payloads = Object.fromEntries(payloadEntries.filter((entry) => entry !== null));
|
|
382
|
+
const registrations = panelRegistry.registrations.map((panel) => ({
|
|
383
|
+
id: panel.id,
|
|
384
|
+
slot: panel.slot,
|
|
385
|
+
title: panel.title,
|
|
386
|
+
capabilityId: panel.capabilityId,
|
|
387
|
+
description: panel.description,
|
|
388
|
+
badge: panel.badge,
|
|
389
|
+
disabled: panel.disabled
|
|
390
|
+
}));
|
|
391
|
+
const activePanel = registrations.find((panel) => panel.id === RIG_SUPERVISOR_PANEL_ID2)?.id ?? registrations.find((panel) => !panel.disabled)?.id ?? registrations[0]?.id ?? null;
|
|
392
|
+
const frameBody = {
|
|
393
|
+
kind: "snapshot",
|
|
394
|
+
activePanel,
|
|
395
|
+
registrations,
|
|
396
|
+
payloads
|
|
397
|
+
};
|
|
398
|
+
const serialized = JSON.stringify(frameBody);
|
|
399
|
+
if (serialized === lastRigPanelsSnapshotJson)
|
|
400
|
+
return;
|
|
401
|
+
lastRigPanelsSnapshotJson = serialized;
|
|
402
|
+
appendCustomMessageEntry(RIG_PANELS_CUSTOM_MESSAGE_TYPE, "", false, { ...frameBody, updatedAt: new Date().toISOString() }, "agent");
|
|
403
|
+
};
|
|
404
|
+
const publishRigPanelsSnapshotBestEffort = () => {
|
|
405
|
+
publishRigPanelsSnapshot().catch((error) => {
|
|
406
|
+
console.error(`[rig-run] panel-snapshot-publish-failed ${error instanceof Error ? error.message : String(error)}`);
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
const pushWorkerProjection = (links = runRegistryLinks) => {
|
|
410
|
+
if (!workerProjectionConnection)
|
|
411
|
+
return;
|
|
412
|
+
workerProjectionConnection.push(buildCurrentRegistryProjection(links));
|
|
413
|
+
};
|
|
414
|
+
const publishRunProjection = async (status) => {
|
|
415
|
+
const projection = buildCurrentRegistryProjection();
|
|
416
|
+
if (workerProjectionConnection)
|
|
417
|
+
workerProjectionConnection.push(projection);
|
|
418
|
+
if (runRegistry && runRegistryRoomId) {
|
|
419
|
+
await runRegistry.heartbeatRoom(runRegistryRoomId, status, projection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
|
|
420
|
+
}
|
|
421
|
+
publishRigPanelsSnapshotBestEffort();
|
|
422
|
+
};
|
|
423
|
+
const stopRunRegistry = (opts = {}) => {
|
|
424
|
+
if (runRegistryHeartbeat) {
|
|
425
|
+
clearInterval(runRegistryHeartbeat);
|
|
426
|
+
runRegistryHeartbeat = null;
|
|
427
|
+
}
|
|
428
|
+
if (workerProjectionFiber) {
|
|
429
|
+
Effect.runFork(Fiber.interrupt(workerProjectionFiber));
|
|
430
|
+
workerProjectionFiber = null;
|
|
431
|
+
}
|
|
432
|
+
if (workerProjectionConnection) {
|
|
433
|
+
workerProjectionConnection.close();
|
|
434
|
+
workerProjectionConnection = null;
|
|
435
|
+
}
|
|
436
|
+
if ((opts.removeRoom ?? true) && runRegistry && runRegistryRoomId) {
|
|
437
|
+
runRegistry.removeRoom(runRegistryRoomId).catch(() => {});
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const stopRunStallMonitor = startRunProcessStallMonitor({
|
|
441
|
+
journal,
|
|
442
|
+
lastActivityAt: () => lastRunActivityAt,
|
|
443
|
+
alreadyStalled: runProjection.stallCount > 0
|
|
444
|
+
});
|
|
445
|
+
const appendRunStatus = (status, reason, force = false) => {
|
|
446
|
+
journal?.appendStatus(status, { actor: OPERATOR_ACTOR, reason, force });
|
|
447
|
+
};
|
|
448
|
+
const applyInboxResolution = (resolution) => {
|
|
449
|
+
if (resolution.kind === "approval") {
|
|
450
|
+
journal?.appendApprovalResolved({
|
|
451
|
+
requestId: resolution.requestId,
|
|
452
|
+
decision: resolution.decision,
|
|
453
|
+
...resolution.note !== undefined ? { note: resolution.note } : {},
|
|
454
|
+
actor: OPERATOR_ACTOR
|
|
455
|
+
});
|
|
456
|
+
journal?.appendTimeline({ type: "inbox-resolution", kind: "approval", requestId: resolution.requestId, decision: resolution.decision });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
journal?.appendInputResolved({ requestId: resolution.requestId, answers: resolution.answers, actor: OPERATOR_ACTOR });
|
|
460
|
+
journal?.appendTimeline({ type: "inbox-resolution", kind: "input", requestId: resolution.requestId });
|
|
461
|
+
};
|
|
462
|
+
const applyRunControlText = (text) => {
|
|
463
|
+
const control = detectRunControlText(text, runId);
|
|
464
|
+
if (!control)
|
|
465
|
+
return;
|
|
466
|
+
markRunActivity();
|
|
467
|
+
if (control.kind === "pause") {
|
|
468
|
+
pendingPause = true;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (control.kind === "resume") {
|
|
472
|
+
pendingPause = false;
|
|
473
|
+
pendingStopReason = undefined;
|
|
474
|
+
appendRunStatus("running", "operator resumed run", true);
|
|
475
|
+
publishRunProjection("running");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (control.kind === "stop") {
|
|
479
|
+
pendingStopReason = control.reason ?? "operator requested stop";
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
applyInboxResolution(control.resolution);
|
|
483
|
+
};
|
|
484
|
+
const processRunControlMessages = () => {
|
|
485
|
+
for (const entry of ctx.sessionManager.getEntries?.() ?? []) {
|
|
486
|
+
const entryObject = entry && typeof entry === "object" ? entry : null;
|
|
487
|
+
const id = entryObject && typeof entryObject.id === "string" ? entryObject.id : null;
|
|
488
|
+
if (id && processedControlEntryIds.has(id))
|
|
489
|
+
continue;
|
|
490
|
+
if (!id && entryObject && processedControlEntryObjects.has(entryObject))
|
|
491
|
+
continue;
|
|
492
|
+
const text = textFromSessionEntry(entry);
|
|
493
|
+
if (id)
|
|
494
|
+
processedControlEntryIds.add(id);
|
|
495
|
+
else if (entryObject)
|
|
496
|
+
processedControlEntryObjects.add(entryObject);
|
|
497
|
+
if (text)
|
|
498
|
+
applyRunControlText(text);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
let fatalTerminalPublishing = false;
|
|
502
|
+
const publishFatalTerminal = (error) => {
|
|
503
|
+
if (fatalTerminalPublishing)
|
|
504
|
+
return;
|
|
505
|
+
fatalTerminalPublishing = true;
|
|
506
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
507
|
+
journal?.appendStatus("failed", { actor: { kind: "agent" }, errorText: detail, force: true });
|
|
508
|
+
publishRunProjection("failed").catch((publishError) => {
|
|
509
|
+
console.error(`[rig-run] fatal-terminal-publish-failed ${publishError instanceof Error ? publishError.message : String(publishError)}`);
|
|
510
|
+
}).finally(() => {
|
|
511
|
+
stopRunStallMonitor();
|
|
512
|
+
stopRunRegistry({ removeRoom: false });
|
|
513
|
+
process.exitCode = 1;
|
|
514
|
+
setTimeout(() => process.exit(1), 0);
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
process.once("uncaughtException", publishFatalTerminal);
|
|
518
|
+
process.once("unhandledRejection", publishFatalTerminal);
|
|
519
|
+
const handlePanelAction = async (input) => {
|
|
520
|
+
if (input.actionId !== RIG_RUN_STOP_PANEL_ACTION2 && input.actionId !== "stop-supervisor") {
|
|
521
|
+
throw new Error(`Unsupported Rig panel action: ${input.actionId}`);
|
|
522
|
+
}
|
|
523
|
+
pendingStopReason = `operator ${input.from.name} requested stop`;
|
|
524
|
+
journal?.appendTimeline({ type: "interrupted", stage: "panel-action", status: "running", detail: pendingStopReason });
|
|
525
|
+
ctx.abort();
|
|
526
|
+
await publishRunProjection("running");
|
|
527
|
+
};
|
|
528
|
+
const startRunCollabHost = async () => {
|
|
529
|
+
try {
|
|
530
|
+
const projection = await startHost.call(collab, {
|
|
531
|
+
title: runDisplayTitle,
|
|
532
|
+
...identity?.owner ? { owner: identity.owner } : {},
|
|
533
|
+
...identity?.selectedRepo ? { selectedRepo: identity.selectedRepo } : {},
|
|
534
|
+
relayUrl: rigRelayUrl(),
|
|
535
|
+
onPanelAction: handlePanelAction
|
|
536
|
+
});
|
|
537
|
+
const roomId = projection.sessionId?.trim() || runId;
|
|
538
|
+
if (roomId !== runId) {
|
|
539
|
+
throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
|
|
540
|
+
}
|
|
541
|
+
runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
|
|
542
|
+
const timeline = {
|
|
543
|
+
type: "collab-host-started",
|
|
544
|
+
roomId,
|
|
545
|
+
joinLink: projection.joinLink,
|
|
546
|
+
webLink: projection.webLink,
|
|
547
|
+
relayUrl: projection.relayUrl
|
|
548
|
+
};
|
|
549
|
+
journal?.appendTimeline(timeline);
|
|
550
|
+
console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
|
|
551
|
+
ctx.ui.notify("Rig run collab host started.", "info");
|
|
552
|
+
publishRigPanelsSnapshotBestEffort();
|
|
553
|
+
if (runRegistry) {
|
|
554
|
+
try {
|
|
555
|
+
runRegistryRoomId = roomId;
|
|
556
|
+
const initialProjection = buildCurrentRegistryProjection();
|
|
557
|
+
await runRegistry.registerRoom({
|
|
558
|
+
roomId,
|
|
559
|
+
owner: runRegistryOwner,
|
|
560
|
+
repo: runRegistryRepo,
|
|
561
|
+
title: runDisplayTitle,
|
|
562
|
+
status: "running",
|
|
563
|
+
joinLink: projection.joinLink ?? "",
|
|
564
|
+
webLink: projection.webLink ?? "",
|
|
565
|
+
relayUrl: projection.relayUrl ?? rigRelayUrl(),
|
|
566
|
+
startedAt: new Date().toISOString(),
|
|
567
|
+
cwd: process.cwd(),
|
|
568
|
+
sessionPath: ctx.sessionManager.getSessionFile() ?? undefined,
|
|
569
|
+
pid: process.pid,
|
|
570
|
+
projection: initialProjection
|
|
571
|
+
});
|
|
572
|
+
workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
|
|
573
|
+
workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
|
|
574
|
+
pushWorkerProjection();
|
|
575
|
+
workerProjectionFiber = Effect.runFork(localRunChanges(projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.sync(() => pushWorkerProjection()))));
|
|
576
|
+
journal?.appendTimeline({ type: "registry-registered", roomId });
|
|
577
|
+
console.log(`[rig-run] registry-registered roomId=${roomId}`);
|
|
578
|
+
runRegistryHeartbeat = setInterval(() => {
|
|
579
|
+
const heartbeatProjection = buildCurrentRegistryProjection();
|
|
580
|
+
runRegistry.heartbeatRoom(roomId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
|
|
581
|
+
}, 15000);
|
|
582
|
+
if (typeof runRegistryHeartbeat.unref === "function")
|
|
583
|
+
runRegistryHeartbeat.unref();
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.error(`[rig-run] registry-register-failed ${error instanceof Error ? error.message : String(error)}`);
|
|
586
|
+
ctx.ui.notify("Rig run could not register to the discovery registry; it may not appear in Runs.", "warning");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return true;
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (error instanceof Error && error.message.startsWith("Collab host session id mismatch:")) {
|
|
592
|
+
publishFatalTerminal(error);
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
console.error(`[rig-run] collab-host-start-failed ${error instanceof Error ? error.message : String(error)}`);
|
|
596
|
+
ctx.ui.notify("Rig run collab host could not reach the relay; session remains local.", "warning");
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
journal?.appendTimeline({ type: "stage", stage: "Connect", status: "running", detail: "run process session_start" });
|
|
601
|
+
journal?.appendTimeline({ type: "stage", stage: "Prepare", status: "completed", detail: "run process prepared" });
|
|
602
|
+
journal?.appendStatus("running", { actor: { kind: "agent" }, reason: "run process session started", force: true });
|
|
603
|
+
if (!await startRunCollabHost())
|
|
604
|
+
return null;
|
|
605
|
+
if (taskIdAtStart) {
|
|
606
|
+
journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
|
|
607
|
+
try {
|
|
608
|
+
await updateRunTaskSourceLifecycle(projectRoot, {
|
|
609
|
+
runId,
|
|
610
|
+
taskId: taskIdAtStart,
|
|
611
|
+
sourceTask: runProjection.record.sourceTask,
|
|
612
|
+
worktreePath: process.cwd(),
|
|
613
|
+
logRoot: runProjection.record.logRoot ?? null,
|
|
614
|
+
sessionPath: runProjection.record.sessionPath ?? null
|
|
615
|
+
}, "running", "Rig started work on this task.");
|
|
616
|
+
journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "completed", detail: "reflected running" });
|
|
617
|
+
} catch (error) {
|
|
618
|
+
journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "failed", detail: error instanceof Error ? error.message : String(error) });
|
|
619
|
+
console.error(`[rig-run] running-reflection-failed ${error instanceof Error ? error.message : String(error)}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const steerPi = async (message) => {
|
|
623
|
+
journal?.appendTimeline({ type: "closeout-steer", message });
|
|
624
|
+
api.sendUserMessage(message, { deliverAs: "steer" });
|
|
625
|
+
};
|
|
626
|
+
const agentEnd = async (event) => {
|
|
627
|
+
markRunActivity();
|
|
628
|
+
processRunControlMessages();
|
|
629
|
+
for (const message of event.messages) {
|
|
630
|
+
const text = textFromAgentMessage(message);
|
|
631
|
+
if (text)
|
|
632
|
+
applyRunControlText(text);
|
|
633
|
+
}
|
|
634
|
+
const aborted = event.messages.some((message) => message.role === "assistant" && message.stopReason === "aborted");
|
|
635
|
+
if (pendingStopReason !== undefined) {
|
|
636
|
+
const reason = pendingStopReason ?? "operator requested stop";
|
|
637
|
+
pendingStopReason = undefined;
|
|
638
|
+
closeoutStarted = true;
|
|
639
|
+
journal?.appendTimeline({ type: "interrupted", stage: "stopped", status: "stopped", detail: reason });
|
|
640
|
+
journal?.appendStatus("stopped", { actor: OPERATOR_ACTOR, reason, force: true });
|
|
641
|
+
if (taskIdAtStart) {
|
|
642
|
+
journal?.appendTimeline({ type: "stage", stage: "Task-source", status: "running", detail: "reflecting operator stop to task source" });
|
|
643
|
+
try {
|
|
644
|
+
await reflectStoppedRunTaskSource({
|
|
645
|
+
projectRoot,
|
|
646
|
+
runId,
|
|
647
|
+
taskId: taskIdAtStart,
|
|
648
|
+
sourceTask: runProjection.record.sourceTask,
|
|
649
|
+
worktreePath: process.cwd(),
|
|
650
|
+
logRoot: runProjection.record.logRoot ?? null,
|
|
651
|
+
sessionPath: runProjection.record.sessionPath ?? null,
|
|
652
|
+
reason
|
|
653
|
+
});
|
|
654
|
+
journal?.appendTimeline({ type: "stage", stage: "Task-source", status: "completed", detail: "reflected operator stop" });
|
|
655
|
+
} catch (error) {
|
|
656
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
657
|
+
journal?.appendTimeline({ type: "stage", stage: "Task-source", status: "failed", detail });
|
|
658
|
+
console.error(`[rig-run] stopped-reflection-failed ${detail}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
await publishRunProjection("stopped");
|
|
662
|
+
stopRunStallMonitor();
|
|
663
|
+
stopRunRegistry({ removeRoom: false });
|
|
664
|
+
process.exitCode = 0;
|
|
665
|
+
setTimeout(() => process.exit(0), 0);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (aborted) {
|
|
669
|
+
if (pendingPause) {
|
|
670
|
+
pendingPause = false;
|
|
671
|
+
appendRunStatus("paused", "operator paused run", true);
|
|
672
|
+
journal?.appendTimeline({ type: "interrupted", stage: "paused", status: "paused", detail: "operator paused run; parked for native collab resume" });
|
|
673
|
+
await publishRunProjection("paused");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
journal?.appendTimeline({ type: "interrupted", stage: "aborted", status: "running", detail: "operator interrupted run without a Rig control sentinel" });
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (closeoutStarted)
|
|
680
|
+
return;
|
|
681
|
+
if (process.env.RIG_RUN_FORCE_STEER_ONCE === "1" && !forcedSteerUsed) {
|
|
682
|
+
forcedSteerUsed = true;
|
|
683
|
+
journal?.appendTimeline({ type: "force-steer-once-started" });
|
|
684
|
+
await steerPi("now reply with the word FIXED");
|
|
685
|
+
journal?.appendTimeline({ type: "force-steer-once-completed" });
|
|
686
|
+
}
|
|
687
|
+
closeoutStarted = true;
|
|
688
|
+
runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
|
|
689
|
+
const taskId = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
|
|
690
|
+
if (!taskId) {
|
|
691
|
+
const reason = "missing task id for closeout";
|
|
692
|
+
journal?.appendTimeline({ type: "closeout-skipped", reason: "missing-task-id" });
|
|
693
|
+
journal?.appendStatus("failed", { actor: { kind: "agent" }, reason, errorText: reason, force: true });
|
|
694
|
+
await publishRunProjection("failed");
|
|
695
|
+
stopRunStallMonitor();
|
|
696
|
+
stopRunRegistry({ removeRoom: false });
|
|
697
|
+
process.exitCode = 1;
|
|
698
|
+
setTimeout(() => process.exit(1), 0);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const { command, gitCommand } = createEnvCloseoutRunners(process.env);
|
|
702
|
+
let closeoutStatusAdvanced = false;
|
|
703
|
+
try {
|
|
704
|
+
await runPipelineCloseout({
|
|
705
|
+
projectRoot,
|
|
706
|
+
runId,
|
|
707
|
+
taskId,
|
|
708
|
+
branch: runProjection.record.branch ?? `rig/${taskId}-${runId}`,
|
|
709
|
+
workspace: process.cwd(),
|
|
710
|
+
artifactRoot: runProjection.record.artifactRoot ?? null,
|
|
711
|
+
sourceTask: runProjection.record.sourceTask && typeof runProjection.record.sourceTask === "object" && !Array.isArray(runProjection.record.sourceTask) ? runProjection.record.sourceTask : null,
|
|
712
|
+
command,
|
|
713
|
+
gitCommand,
|
|
714
|
+
steerPi,
|
|
715
|
+
onValidationStart: async () => {
|
|
716
|
+
journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
|
|
717
|
+
await publishRunProjection("validating");
|
|
718
|
+
},
|
|
719
|
+
kernelJournal: journal?.kernel ?? null,
|
|
720
|
+
journalPhase: async (phase, outcome, detail) => {
|
|
721
|
+
journal?.appendCloseoutPhase({ phase, outcome, detail: detail ?? null });
|
|
722
|
+
if (!closeoutStatusAdvanced && phase !== "queued" && outcome === "started") {
|
|
723
|
+
closeoutStatusAdvanced = true;
|
|
724
|
+
journal?.appendStatus("closing-out", { actor: { kind: "agent" }, reason: "validation passed; running closeout automation", force: true });
|
|
725
|
+
await publishRunProjection("closing-out");
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
reflect: async (status, summary, opts) => {
|
|
729
|
+
await updateRunTaskSourceLifecycle(projectRoot, {
|
|
730
|
+
runId,
|
|
731
|
+
taskId,
|
|
732
|
+
sourceTask: runProjection.record.sourceTask,
|
|
733
|
+
worktreePath: process.cwd(),
|
|
734
|
+
logRoot: runProjection.record.logRoot ?? null,
|
|
735
|
+
sessionPath: runProjection.record.sessionPath ?? null
|
|
736
|
+
}, status, summary, opts);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
journal?.appendStatus("completed", { actor: { kind: "agent" }, reason: "closeout completed" });
|
|
740
|
+
await publishRunProjection("completed");
|
|
741
|
+
await dispatchRunNotifications(projectRoot, runId, taskId, "completed", "closeout completed");
|
|
742
|
+
stopRunStallMonitor();
|
|
743
|
+
stopRunRegistry({ removeRoom: false });
|
|
744
|
+
process.exitCode = 0;
|
|
745
|
+
setTimeout(() => process.exit(0), 0);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
748
|
+
const status = error instanceof CloseoutValidationError ? "needs-attention" : "failed";
|
|
749
|
+
journal?.appendStatus(status, { actor: { kind: "agent" }, errorText: detail, force: true });
|
|
750
|
+
await publishRunProjection(status);
|
|
751
|
+
await dispatchRunNotifications(projectRoot, runId, taskId, "failed", detail);
|
|
752
|
+
stopRunStallMonitor();
|
|
753
|
+
stopRunRegistry({ removeRoom: false });
|
|
754
|
+
process.exitCode = 1;
|
|
755
|
+
setTimeout(() => process.exit(1), 0);
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
const handleRunActivity = () => {
|
|
759
|
+
markRunActivity();
|
|
760
|
+
processRunControlMessages();
|
|
761
|
+
};
|
|
762
|
+
return { beforeAgentStart: handleRunActivity, messageStart: handleRunActivity, agentEnd };
|
|
763
|
+
}
|
|
764
|
+
async function maybeStartSpikeAutohost(ctx) {
|
|
765
|
+
if (process.env.RIG_SPIKE_AUTOHOST !== "1")
|
|
766
|
+
return;
|
|
767
|
+
const relayUrl = process.env.RIG_SPIKE_RELAY?.trim();
|
|
768
|
+
if (!relayUrl)
|
|
769
|
+
throw new Error("RIG_SPIKE_RELAY is required when RIG_SPIKE_AUTOHOST=1");
|
|
770
|
+
const collab = ctx.collab;
|
|
771
|
+
const startHost = collab?.startHost ?? collab?.startCollabHost;
|
|
772
|
+
if (!startHost)
|
|
773
|
+
throw new Error("OMP collab host facade is unavailable for detached PTY spike.");
|
|
774
|
+
const identity = resolveRigIdentity(ctx);
|
|
775
|
+
await startHost.call(collab, {
|
|
776
|
+
relayUrl,
|
|
777
|
+
title: "Rig detached PTY spike",
|
|
778
|
+
...identity?.owner ? { owner: identity.owner } : {},
|
|
779
|
+
...identity?.selectedRepo ? { selectedRepo: identity.selectedRepo } : {}
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
var REGISTRY_PROJECTION_TIMELINE_LIMIT = 100, OPERATOR_ACTOR;
|
|
783
|
+
var init_autohost = __esm(() => {
|
|
784
|
+
init_journal();
|
|
785
|
+
init_notifications();
|
|
786
|
+
init_stall();
|
|
787
|
+
init_panel_plugin();
|
|
788
|
+
init_utils();
|
|
789
|
+
OPERATOR_ACTOR = { kind: "operator" };
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// packages/run-worker/src/extension.ts
|
|
793
|
+
var exports_extension = {};
|
|
794
|
+
__export(exports_extension, {
|
|
795
|
+
default: () => rigWorkerExtension,
|
|
796
|
+
__rigRunWorkerTest: () => __rigRunWorkerTest,
|
|
797
|
+
__rigExtensionTest: () => __rigExtensionTest
|
|
798
|
+
});
|
|
799
|
+
function rigWorkerExtension(api) {
|
|
800
|
+
let hooks = null;
|
|
801
|
+
api.on("session_start", async (_event, ctx) => {
|
|
802
|
+
hooks = await maybeStartRunProcessAutohost(api, ctx);
|
|
803
|
+
await maybeStartSpikeAutohost(ctx);
|
|
804
|
+
});
|
|
805
|
+
api.on("before_agent_start", () => {
|
|
806
|
+
hooks?.beforeAgentStart();
|
|
807
|
+
});
|
|
808
|
+
api.on("message_start", () => {
|
|
809
|
+
hooks?.messageStart();
|
|
810
|
+
});
|
|
811
|
+
api.on("agent_end", async (event) => {
|
|
812
|
+
await hooks?.agentEnd(event);
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
var __rigRunWorkerTest, __rigExtensionTest;
|
|
816
|
+
var init_extension = __esm(() => {
|
|
817
|
+
init_autohost();
|
|
818
|
+
init_stall();
|
|
819
|
+
__rigRunWorkerTest = {
|
|
820
|
+
computeRunStall,
|
|
821
|
+
appendRunStallDetected,
|
|
822
|
+
detectRunControlText
|
|
823
|
+
};
|
|
824
|
+
__rigExtensionTest = __rigRunWorkerTest;
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
// packages/run-worker/src/plugin.ts
|
|
828
|
+
import { definePlugin } from "@rig/core/config";
|
|
829
|
+
var runWorkerPlugin = definePlugin({
|
|
830
|
+
name: "@rig/run-worker",
|
|
831
|
+
version: "0.0.0-alpha.1",
|
|
832
|
+
contributes: {
|
|
833
|
+
sessionExtensions: [
|
|
834
|
+
{
|
|
835
|
+
id: "rig:run-worker",
|
|
836
|
+
description: "Rig run lifecycle worker (plan\u2192implement\u2192validate\u2192commit\u2192PR\u2192merge\u2192closeout).",
|
|
837
|
+
install: async (api) => {
|
|
838
|
+
const { default: rigWorkerExtension2 } = await Promise.resolve().then(() => (init_extension(), exports_extension));
|
|
839
|
+
rigWorkerExtension2(api);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
]
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
function createRunWorkerPlugin() {
|
|
846
|
+
return runWorkerPlugin;
|
|
847
|
+
}
|
|
848
|
+
export {
|
|
849
|
+
runWorkerPlugin,
|
|
850
|
+
createRunWorkerPlugin
|
|
851
|
+
};
|