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