@h-rig/run-worker 0.0.6-alpha.157 → 0.0.6-alpha.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/src/autohost.d.ts +8 -10
  2. package/dist/src/autohost.js +683 -95
  3. package/dist/src/constants.d.ts +0 -1
  4. package/dist/src/constants.js +0 -2
  5. package/dist/src/extension.js +683 -95
  6. package/dist/src/host-kernel.d.ts +22 -0
  7. package/dist/src/host-kernel.js +78 -0
  8. package/dist/src/host.d.ts +2 -0
  9. package/dist/src/host.js +419 -0
  10. package/dist/src/index.d.ts +0 -6
  11. package/dist/src/index.js +1913 -133
  12. package/dist/src/local-run-changes.d.ts +3 -0
  13. package/dist/src/local-run-changes.js +65 -0
  14. package/dist/src/notifications.js +13 -5
  15. package/dist/src/notify-cap.d.ts +11 -0
  16. package/dist/src/notify-cap.js +13 -0
  17. package/dist/src/panel-plugin.js +3 -7
  18. package/dist/src/plugin.d.ts +0 -11
  19. package/dist/src/plugin.js +1910 -101
  20. package/dist/src/{runs → read-model-backend}/control.d.ts +0 -1
  21. package/dist/src/{runs → read-model-backend}/control.js +361 -38
  22. package/dist/src/{runs → read-model-backend}/diagnostics.d.ts +0 -1
  23. package/dist/src/{runs → read-model-backend}/diagnostics.js +1 -3
  24. package/dist/src/{runs → read-model-backend}/guard.js +2 -3
  25. package/dist/src/{runs → read-model-backend}/inbox.js +366 -36
  26. package/dist/src/{runs → read-model-backend}/index.js +552 -223
  27. package/dist/src/{runs → read-model-backend}/inspect.d.ts +0 -1
  28. package/dist/src/{runs → read-model-backend}/inspect.js +350 -34
  29. package/dist/src/{runs → read-model-backend}/projection.d.ts +21 -7
  30. package/dist/src/{runs → read-model-backend}/projection.js +349 -31
  31. package/dist/src/{runs → read-model-backend}/run-status.d.ts +6 -3
  32. package/dist/src/{runs → read-model-backend}/run-status.js +53 -33
  33. package/dist/src/{runs → read-model-backend}/stats.d.ts +0 -1
  34. package/dist/src/{runs → read-model-backend}/stats.js +373 -58
  35. package/dist/src/read-model-service.d.ts +2 -0
  36. package/dist/src/read-model-service.js +1433 -0
  37. package/dist/src/session-journal.d.ts +60 -0
  38. package/dist/src/session-journal.js +471 -0
  39. package/dist/src/stall.d.ts +8 -3
  40. package/dist/src/stall.js +0 -1
  41. package/dist/src/utils.js +282 -3
  42. package/package.json +9 -12
  43. package/dist/src/journal.d.ts +0 -33
  44. package/dist/src/journal.js +0 -31
  45. /package/dist/src/{runs → read-model-backend}/guard.d.ts +0 -0
  46. /package/dist/src/{runs → read-model-backend}/inbox.d.ts +0 -0
  47. /package/dist/src/{runs → read-model-backend}/index.d.ts +0 -0
@@ -1,29 +1,631 @@
1
1
  // @bun
2
2
  // packages/run-worker/src/autohost.ts
3
- import { runPipelineCloseout } from "@rig/bundle-default-lifecycle/pipeline-closeout";
4
- import { adoptPlacementKernel } from "@rig/kernel/kernel-entrypoint";
5
- 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";
6
- import { Duration, Effect, Fiber, Stream } from "effect";
7
- import { createEnvCloseoutRunners } from "@rig/bundle-default-lifecycle/native/closeout-runners";
8
- import { CloseoutValidationError } from "@rig/bundle-default-lifecycle/native/in-process-closeout";
9
- import { projectRunFromSession } from "@rig/runtime/control-plane/run-session-projection";
10
- import { localRunChanges } from "@rig/runtime/control-plane/run-discovery-stream";
11
- import { updateRunTaskSourceLifecycle } from "@rig/runtime/control-plane/tasks/source-lifecycle";
12
- import { resolveOwnerNamespaceKey } from "@rig/runtime/control-plane/remote-config";
13
- import { resolveRigIdentity } from "@rig/runtime/control-plane/identity";
14
- import { connectWorkerProjection, createRegistryClient } from "@rig/relay-registry";
15
- import { coerceRegistryStatus } from "@rig/relay-registry/schema";
3
+ import {
4
+ PLACEMENT_RUN_TRANSPORT,
5
+ RIG_RUN_STOP_PANEL_ACTION as RIG_RUN_STOP_PANEL_ACTION2,
6
+ RIG_SUPERVISOR_PANEL_ID as RIG_SUPERVISOR_PANEL_ID2,
7
+ RUN_IDENTITY_ENV,
8
+ RUN_REGISTRY_BACKBONE,
9
+ RUN_CLOSEOUT_CAPABILITY,
10
+ TASK_DATA_SERVICE_CAPABILITY
11
+ } from "@rig/contracts";
12
+ import { Duration, Effect as Effect2, Fiber, Stream as Stream2 } from "effect";
13
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
14
+ import { requireInstalledCapability, loadCapabilityForRoot } from "@rig/core/capability-loaders";
15
+ import { resolveOwnerNamespaceKey } from "@rig/core/remote-config";
16
16
 
17
- // packages/run-worker/src/journal.ts
18
- import { createJournalSessionProvider } from "@rig/kernel/journal-session-provider";
19
- import { RunSessionJournal } from "@rig/runtime/control-plane/run-session-writer";
17
+ // packages/run-worker/src/host-kernel.ts
18
+ import { existsSync, readFileSync } from "fs";
19
+ import { resolve } from "path";
20
+ import { applyConfigEnv } from "@rig/core/config-env";
21
+ import { loadConfig } from "@rig/core/load-config";
22
+ import { bootDefaultKernelIntoProcess } from "@rig/kernel-seed/boot-default";
23
+ import { createPlacementTransportPlugin } from "@rig/kernel-seed/default-kernel";
24
+ function unquoteDotenvValue(value) {
25
+ const trimmed = value.trim();
26
+ if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
27
+ return trimmed.slice(1, -1);
28
+ }
29
+ return trimmed;
30
+ }
31
+ function hydrateDotenv(path, env) {
32
+ if (!existsSync(path))
33
+ return false;
34
+ const source = readFileSync(path, "utf8");
35
+ for (const rawLine of source.split(/\r?\n/)) {
36
+ const line = rawLine.trim();
37
+ if (!line || line.startsWith("#"))
38
+ continue;
39
+ const assignment = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
40
+ const equals = assignment.indexOf("=");
41
+ if (equals <= 0)
42
+ continue;
43
+ const key = assignment.slice(0, equals).trim();
44
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || env[key])
45
+ continue;
46
+ env[key] = unquoteDotenvValue(assignment.slice(equals + 1));
47
+ }
48
+ return true;
49
+ }
50
+ function createRunWorkerPlacementTransportPlugin(transport) {
51
+ return createPlacementTransportPlugin(transport);
52
+ }
53
+ async function hydrateRunWorkerProjectEnv(projectRoot, options = {}) {
54
+ const normalizedRoot = resolve(projectRoot);
55
+ const env = options.env ?? process.env;
56
+ const errors = [];
57
+ let dotenvLoaded = false;
58
+ let configLoaded = false;
59
+ try {
60
+ dotenvLoaded = hydrateDotenv(resolve(normalizedRoot, options.dotenvPath ?? ".env"), env);
61
+ } catch (error) {
62
+ errors.push(`dotenv: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ try {
65
+ applyConfigEnv(await loadConfig(normalizedRoot), env);
66
+ configLoaded = true;
67
+ } catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ if (!message.includes("no rig.config"))
70
+ errors.push(`config: ${message}`);
71
+ }
72
+ return { projectRoot: normalizedRoot, dotenvLoaded, configLoaded, errors };
73
+ }
74
+ async function adoptRunWorkerKernel(root, options = {}) {
75
+ if (options.hydrateEnv === true && root !== undefined) {
76
+ await hydrateRunWorkerProjectEnv(root, options);
77
+ }
78
+ const bootInput = {
79
+ ...options.entrypoint !== undefined ? { entrypoint: options.entrypoint } : {},
80
+ ...options.journal !== undefined ? { journal: options.journal } : {}
81
+ };
82
+ if (options.transport === undefined)
83
+ return await bootDefaultKernelIntoProcess(bootInput);
84
+ return await bootDefaultKernelIntoProcess({
85
+ ...bootInput,
86
+ extraPlugins: [createRunWorkerPlacementTransportPlugin(options.transport)]
87
+ });
88
+ }
89
+
90
+ // packages/run-worker/src/local-run-changes.ts
91
+ import { existsSync as existsSync2, mkdirSync, watch } from "fs";
92
+ import { dirname, resolve as resolve2 } from "path";
93
+ import { Effect, Queue, Stream } from "effect";
94
+ function findNearestGitCheckoutRoot(startDir) {
95
+ let current = resolve2(startDir);
96
+ for (;; ) {
97
+ if (existsSync2(resolve2(current, ".git")))
98
+ return current;
99
+ const parent = dirname(current);
100
+ if (parent === current)
101
+ return null;
102
+ current = parent;
103
+ }
104
+ }
105
+ function resolveCheckoutRoot(projectRoot) {
106
+ const normalizedProjectRoot = resolve2(projectRoot);
107
+ const explicit = process.env.MONOREPO_ROOT?.trim();
108
+ if (explicit) {
109
+ const explicitRoot = resolve2(explicit);
110
+ const gitRoot = findNearestGitCheckoutRoot(explicitRoot);
111
+ if (gitRoot)
112
+ return gitRoot;
113
+ throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there or above it.`);
114
+ }
115
+ return findNearestGitCheckoutRoot(normalizedProjectRoot) ?? normalizedProjectRoot;
116
+ }
117
+ function isRelevantLocalRunChange(filename) {
118
+ if (filename === null || filename === undefined)
119
+ return true;
120
+ const raw = filename.toString();
121
+ if (!raw.trim())
122
+ return true;
123
+ const segments = raw.split(/[\\/]+/).filter(Boolean);
124
+ if (segments.some((segment) => segment === ".git" || segment === "node_modules" || segment === "build" || segment === "dist" || segment === "out" || segment === ".next" || segment === "coverage")) {
125
+ return false;
126
+ }
127
+ const rigIndex = segments.lastIndexOf(".rig");
128
+ if (rigIndex < 0)
129
+ return false;
130
+ const rigChild = segments[rigIndex + 1];
131
+ if (rigChild === "session")
132
+ return true;
133
+ return rigChild === "runtime-context.json" && segments[rigIndex + 2] === undefined;
134
+ }
135
+ var localRunChanges = (projectRoot) => Stream.callback((queue) => Effect.gen(function* () {
136
+ const worktreesRoot = resolve2(resolveCheckoutRoot(projectRoot), ".worktrees");
137
+ if (!existsSync2(worktreesRoot)) {
138
+ try {
139
+ mkdirSync(worktreesRoot, { recursive: true });
140
+ } catch {}
141
+ }
142
+ const watcher = watch(worktreesRoot, { recursive: true }, (_event, filename) => {
143
+ if (!isRelevantLocalRunChange(filename))
144
+ return;
145
+ Queue.offerUnsafe(queue, undefined);
146
+ });
147
+ watcher.on("error", () => {});
148
+ yield* Effect.addFinalizer(() => Effect.sync(() => watcher.close()));
149
+ }), { bufferSize: 1, strategy: "sliding" });
150
+
151
+ // packages/run-worker/src/notify-cap.ts
152
+ import { NOTIFY_SERVICE_CAPABILITY } from "@rig/contracts";
153
+ import { defineCapability } from "@rig/core/capability";
154
+ import { resolvePluginHost } from "@rig/core/project-plugins";
155
+ var NotifyCap = defineCapability(NOTIFY_SERVICE_CAPABILITY);
156
+ async function resolveNotifyService(projectRoot) {
157
+ const { host } = await resolvePluginHost(projectRoot);
158
+ return NotifyCap.resolve(host);
159
+ }
160
+
161
+ // packages/run-worker/src/notifications.ts
162
+ async function dispatchRunNotifications(projectRoot, runId, taskId, outcome, detail) {
163
+ try {
164
+ const notifier = await resolveNotifyService(projectRoot);
165
+ if (!notifier)
166
+ return;
167
+ const event = { runId, type: `run.${outcome}`, timestamp: new Date().toISOString(), payload: { taskId, detail } };
168
+ await Promise.race([
169
+ notifier.notify(event),
170
+ new Promise((resolveCap) => setTimeout(resolveCap, 5000))
171
+ ]);
172
+ } catch {}
173
+ }
174
+
175
+ // packages/run-worker/src/panel-plugin.ts
176
+ import { createProjectPluginHost } from "@rig/core/project-plugins";
177
+ import { RIG_CAPABILITY_PANEL_SLOT, RIG_RUN_STOP_PANEL_ACTION, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
178
+ var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
179
+ var PANEL_PRODUCER_TIMEOUT_MS = 2000;
180
+ async function produceWorkerPanelPayload(producer, context) {
181
+ if (!producer.produce)
182
+ return;
183
+ let timeout;
184
+ try {
185
+ return await Promise.race([
186
+ Promise.resolve(producer.produce(context)),
187
+ new Promise((resolve3) => {
188
+ timeout = setTimeout(() => resolve3(undefined), PANEL_PRODUCER_TIMEOUT_MS);
189
+ })
190
+ ]);
191
+ } finally {
192
+ clearTimeout(timeout);
193
+ }
194
+ }
195
+ async function loadWorkerPanelRegistry(projectRoot) {
196
+ const { host } = await createProjectPluginHost(projectRoot, {
197
+ mode: "strict-config-only"
198
+ });
199
+ return {
200
+ registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
201
+ producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
202
+ };
203
+ }
204
+
205
+ // packages/run-worker/src/session-journal.ts
206
+ import { Schema } from "effect";
207
+ import {
208
+ CUSTOM_TYPE_FOR,
209
+ RIG_CONTROL_SENTINEL_END,
210
+ RIG_INBOX_RESOLUTION_SENTINEL,
211
+ RIG_PAUSE_SENTINEL,
212
+ RIG_RESUME_SENTINEL,
213
+ RIG_STOP_SENTINEL,
214
+ RIG_STOP_SENTINEL_END,
215
+ RIG_WORKFLOW_STATUS_CHANGED,
216
+ RunJournalEvent,
217
+ TYPE_FOR_CUSTOM
218
+ } from "@rig/contracts";
219
+ var decodeRunJournalEvent = Schema.decodeUnknownSync(RunJournalEvent);
220
+ var RUN_STATUS_TRANSITIONS = {
221
+ created: ["queued", "preparing", "running", "failed", "stopped"],
222
+ queued: ["preparing", "running", "failed", "stopped"],
223
+ preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
224
+ running: [
225
+ "queued",
226
+ "waiting-approval",
227
+ "waiting-user-input",
228
+ "paused",
229
+ "validating",
230
+ "reviewing",
231
+ "closing-out",
232
+ "needs-attention",
233
+ "completed",
234
+ "failed",
235
+ "stopped"
236
+ ],
237
+ "waiting-approval": ["running", "needs-attention", "failed", "stopped"],
238
+ "waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
239
+ paused: ["running", "failed", "stopped"],
240
+ validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
241
+ reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
242
+ "closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
243
+ "needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
244
+ completed: [],
245
+ failed: ["queued", "preparing", "running", "closing-out"],
246
+ stopped: ["queued", "preparing", "running", "closing-out"]
247
+ };
248
+ var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
249
+ function isTerminalRunStatus(status) {
250
+ return TERMINAL_RUN_STATUSES.includes(status);
251
+ }
252
+ function canTransitionRunStatus(from, to) {
253
+ if (from === null)
254
+ return true;
255
+ if (from === to)
256
+ return true;
257
+ return RUN_STATUS_TRANSITIONS[from].includes(to);
258
+ }
259
+ function assertRunStatusTransition(from, to) {
260
+ if (!canTransitionRunStatus(from, to)) {
261
+ throw new Error(`Illegal run status transition: ${from ?? "(none)"} -> ${to}. ` + `Allowed from ${from ?? "(none)"}: ${from ? RUN_STATUS_TRANSITIONS[from].join(", ") : "(any)"}.`);
262
+ }
263
+ }
264
+ function reduceRunJournal(events, runId = null) {
265
+ let record = {};
266
+ let status = null;
267
+ const statusHistory = [];
268
+ const pendingApprovals = new Map;
269
+ const resolvedApprovals = [];
270
+ const pendingUserInputs = new Map;
271
+ const resolvedUserInputs = [];
272
+ const closeoutPhases = [];
273
+ let resolvedPipeline = null;
274
+ const stageOutcomes = [];
275
+ const anomalies = [];
276
+ let steeringCount = 0;
277
+ let stallCount = 0;
278
+ let lastSeq = 0;
279
+ let lastEventAt = null;
280
+ const projectedRunId = runId ?? events[0]?.runId ?? null;
281
+ for (const event of events) {
282
+ lastSeq = event.seq;
283
+ lastEventAt = event.at;
284
+ switch (event.type) {
285
+ case "status-changed": {
286
+ if (!canTransitionRunStatus(status, event.to)) {
287
+ anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
288
+ }
289
+ statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
290
+ const wasTerminal = status !== null && isTerminalRunStatus(status);
291
+ status = event.to;
292
+ record = { ...record, updatedAt: event.at };
293
+ if (isTerminalRunStatus(event.to) && !record.completedAt)
294
+ record = { ...record, completedAt: event.at };
295
+ if (!isTerminalRunStatus(event.to) && wasTerminal)
296
+ record = { ...record, completedAt: null };
297
+ if (event.to === "running" && !record.startedAt)
298
+ record = { ...record, startedAt: event.at };
299
+ break;
300
+ }
301
+ case "record-patch": {
302
+ const next = { ...record };
303
+ for (const [key, value] of Object.entries(event.patch)) {
304
+ if (value !== undefined)
305
+ next[key] = value;
306
+ }
307
+ next.updatedAt = event.patch.updatedAt ?? event.at;
308
+ record = next;
309
+ break;
310
+ }
311
+ case "approval-requested": {
312
+ pendingApprovals.set(event.requestId, {
313
+ requestId: event.requestId,
314
+ requestKind: event.requestKind,
315
+ actionId: event.actionId ?? null,
316
+ payload: event.payload,
317
+ requestedAt: event.at
318
+ });
319
+ break;
320
+ }
321
+ case "approval-resolved": {
322
+ const pending = pendingApprovals.get(event.requestId);
323
+ if (!pending) {
324
+ const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
325
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
326
+ break;
327
+ }
328
+ pendingApprovals.delete(event.requestId);
329
+ resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
330
+ break;
331
+ }
332
+ case "input-requested": {
333
+ pendingUserInputs.set(event.requestId, {
334
+ requestId: event.requestId,
335
+ requestKind: "user-input",
336
+ actionId: null,
337
+ payload: event.payload,
338
+ requestedAt: event.at
339
+ });
340
+ break;
341
+ }
342
+ case "input-resolved": {
343
+ const pending = pendingUserInputs.get(event.requestId);
344
+ if (!pending) {
345
+ const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
346
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
347
+ break;
348
+ }
349
+ pendingUserInputs.delete(event.requestId);
350
+ resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
351
+ break;
352
+ }
353
+ case "steering":
354
+ steeringCount += 1;
355
+ break;
356
+ case "adopted":
357
+ record = { ...record, pid: event.pid, updatedAt: event.at };
358
+ break;
359
+ case "stall-detected":
360
+ stallCount += 1;
361
+ break;
362
+ case "closeout-phase":
363
+ closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
364
+ break;
365
+ case "pipeline-resolved":
366
+ resolvedPipeline = event.pipeline;
367
+ break;
368
+ case "stage-outcome":
369
+ stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
370
+ break;
371
+ case "timeline-entry":
372
+ case "log-entry":
373
+ break;
374
+ }
375
+ }
376
+ return {
377
+ runId: projectedRunId,
378
+ record,
379
+ status,
380
+ statusHistory,
381
+ pendingApprovals: [...pendingApprovals.values()],
382
+ resolvedApprovals,
383
+ pendingUserInputs: [...pendingUserInputs.values()],
384
+ resolvedUserInputs,
385
+ steeringCount,
386
+ stallCount,
387
+ closeoutPhases,
388
+ resolvedPipeline,
389
+ stageOutcomes,
390
+ lastSeq,
391
+ lastEventAt,
392
+ anomalies
393
+ };
394
+ }
395
+ function sessionIdFromSessionFile(sessionPath) {
396
+ if (!sessionPath)
397
+ return null;
398
+ const file = sessionPath.split(/[\\/]/).pop() ?? "";
399
+ const match = file.match(/_([0-9a-fA-F][0-9a-fA-F-]{7,})\.jsonl$/);
400
+ return match?.[1] ?? null;
401
+ }
402
+ function isRunSessionCustomType(customType) {
403
+ return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
404
+ }
405
+ function foldRunSessionEntries(entries, runId) {
406
+ const events = [];
407
+ entries.forEach((entry, index) => {
408
+ if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
409
+ return;
410
+ const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
411
+ const stamped = {
412
+ v: 1,
413
+ seq: index + 1,
414
+ at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
415
+ runId,
416
+ ...data,
417
+ type: TYPE_FOR_CUSTOM[entry.customType]
418
+ };
419
+ try {
420
+ events.push(decodeRunJournalEvent(stamped));
421
+ } catch {}
422
+ });
423
+ return reduceRunJournal(events, runId);
424
+ }
425
+ function projectRunFromSession(source, runId) {
426
+ const entries = "getEntries" in source ? source.getEntries() : source;
427
+ return foldRunSessionEntries(entries, runId);
428
+ }
429
+ function isRecord(value) {
430
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
431
+ }
432
+ function timelineEntryFromCustomEntry(entry) {
433
+ if (entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
434
+ return null;
435
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
436
+ const type = typeof payload.type === "string" ? payload.type : "timeline";
437
+ const stage = typeof payload.stage === "string" ? payload.stage : typeof payload.name === "string" ? payload.name : null;
438
+ const status = typeof payload.status === "string" ? payload.status : typeof payload.outcome === "string" ? payload.outcome : null;
439
+ const detail = typeof payload.detail === "string" ? payload.detail : typeof payload.message === "string" ? payload.message : null;
440
+ const at = typeof entry.data.at === "string" ? entry.data.at : typeof payload.at === "string" ? payload.at : null;
441
+ return { at, type, stage, status, detail };
442
+ }
443
+ function latestTimelineEntriesFromCustomEntries(entries, limit) {
444
+ if (limit <= 0)
445
+ return [];
446
+ const timeline = [];
447
+ for (let index = entries.length - 1;index >= 0 && timeline.length < limit; index -= 1) {
448
+ const timelineEntry = timelineEntryFromCustomEntry(entries[index]);
449
+ if (timelineEntry)
450
+ timeline.push(timelineEntry);
451
+ }
452
+ timeline.reverse();
453
+ return timeline;
454
+ }
455
+ function parseStopSentinel(text, expectedRunId) {
456
+ const start = text.indexOf(RIG_STOP_SENTINEL);
457
+ if (start < 0)
458
+ return null;
459
+ const end = text.indexOf(RIG_STOP_SENTINEL_END, start);
460
+ const body = text.slice(start + RIG_STOP_SENTINEL.length, end >= 0 ? end : undefined).trim();
461
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
462
+ if (runId && runId !== expectedRunId)
463
+ return null;
464
+ const reason = body.match(/(?:^|\s)reason=(.+)$/)?.[1]?.trim() ?? null;
465
+ return { reason };
466
+ }
467
+ function parseControlSentinel(marker, text, expectedRunId) {
468
+ const start = text.indexOf(marker);
469
+ if (start < 0)
470
+ return null;
471
+ const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
472
+ const body = text.slice(start + marker.length, end >= 0 ? end : undefined).trim();
473
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
474
+ if (runId && runId !== expectedRunId)
475
+ return null;
476
+ const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
477
+ return { requestedBy };
478
+ }
479
+ function parsePauseSentinel(text, expectedRunId) {
480
+ return parseControlSentinel(RIG_PAUSE_SENTINEL, text, expectedRunId);
481
+ }
482
+ function parseResumeSentinel(text, expectedRunId) {
483
+ return parseControlSentinel(RIG_RESUME_SENTINEL, text, expectedRunId);
484
+ }
485
+ function decodeInboxResolutionPayload(payload) {
486
+ try {
487
+ const parsed = JSON.parse(decodeURIComponent(payload));
488
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
489
+ return null;
490
+ const record = parsed;
491
+ if (record.kind === "approval") {
492
+ const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
493
+ const decision = record.decision === "approve" || record.decision === "reject" ? record.decision : null;
494
+ const note = typeof record.note === "string" ? record.note : record.note === null ? null : undefined;
495
+ if (!requestId || !decision)
496
+ return null;
497
+ return { kind: "approval", requestId, decision, ...note !== undefined ? { note } : {} };
498
+ }
499
+ if (record.kind === "input") {
500
+ const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
501
+ const answers = record.answers && typeof record.answers === "object" && !Array.isArray(record.answers) ? Object.fromEntries(Object.entries(record.answers).filter((entry) => typeof entry[1] === "string")) : null;
502
+ if (!requestId || !answers)
503
+ return null;
504
+ return { kind: "input", requestId, answers };
505
+ }
506
+ } catch {
507
+ return null;
508
+ }
509
+ return null;
510
+ }
511
+ function parseInboxResolutionSentinel(text, expectedRunId) {
512
+ const start = text.indexOf(RIG_INBOX_RESOLUTION_SENTINEL);
513
+ if (start < 0)
514
+ return null;
515
+ const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
516
+ const body = text.slice(start + RIG_INBOX_RESOLUTION_SENTINEL.length, end >= 0 ? end : undefined).trim();
517
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
518
+ if (runId && runId !== expectedRunId)
519
+ return null;
520
+ const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
521
+ const payload = body.match(/(?:^|\s)data=([^\s>]+)/)?.[1] ?? "";
522
+ const decoded = decodeInboxResolutionPayload(payload);
523
+ if (!decoded)
524
+ return null;
525
+ return { ...decoded, requestedBy };
526
+ }
527
+ function asCustomEntries(entries) {
528
+ return entries.filter((entry) => entry.type === "custom");
529
+ }
530
+
531
+ class RunSessionJournal {
532
+ sm;
533
+ runId;
534
+ constructor(sm, runId) {
535
+ this.sm = sm;
536
+ this.runId = runId;
537
+ }
538
+ #append(init) {
539
+ this.sm.appendCustomEntry(CUSTOM_TYPE_FOR[init.type], { ...init, at: new Date().toISOString() });
540
+ }
541
+ appendStatus(to, opts = {}) {
542
+ const from = foldRunSessionEntries(asCustomEntries(this.sm.getEntries()), this.runId).status;
543
+ if (!opts.force)
544
+ assertRunStatusTransition(from, to);
545
+ if (opts.errorText !== undefined)
546
+ this.#append({ type: "record-patch", patch: { errorText: opts.errorText } });
547
+ this.#append({
548
+ type: "status-changed",
549
+ from,
550
+ to,
551
+ ...opts.reason !== undefined ? { reason: opts.reason } : {},
552
+ ...opts.actor !== undefined ? { actor: opts.actor } : {}
553
+ });
554
+ }
555
+ appendTimeline(payload) {
556
+ this.#append({ type: "timeline-entry", payload });
557
+ }
558
+ appendCloseoutPhase(input) {
559
+ this.#append({
560
+ type: "closeout-phase",
561
+ phase: input.phase,
562
+ outcome: input.outcome,
563
+ ...input.detail !== undefined ? { detail: input.detail } : {}
564
+ });
565
+ }
566
+ appendApprovalResolved(input) {
567
+ this.#append({
568
+ type: "approval-resolved",
569
+ requestId: input.requestId,
570
+ decision: input.decision,
571
+ actor: input.actor,
572
+ ...input.note !== undefined ? { note: input.note } : {}
573
+ });
574
+ }
575
+ appendInputResolved(input) {
576
+ this.#append({ type: "input-resolved", requestId: input.requestId, answers: input.answers, actor: input.actor });
577
+ }
578
+ appendStall(input) {
579
+ this.#append({ type: "stall-detected", detail: input.detail });
580
+ }
581
+ }
582
+ function isRunJournalEventInit(event) {
583
+ if (event === null || typeof event !== "object")
584
+ return false;
585
+ const type = event.type;
586
+ return typeof type === "string" && Object.hasOwn(CUSTOM_TYPE_FOR, type);
587
+ }
588
+ function createJournalSessionProvider(options) {
589
+ const now = options.now ?? (() => new Date);
590
+ const appendRunEvent = async (event, runId = options.runId) => {
591
+ options.store.appendCustomEntry(CUSTOM_TYPE_FOR[event.type], { ...event, runId, at: now().toISOString() });
592
+ };
593
+ const readEntries = () => asCustomEntries(options.store.getEntries());
594
+ const entriesForRun = (runId) => readEntries().filter((entry) => {
595
+ const data = entry.data;
596
+ if (data === null || typeof data !== "object")
597
+ return runId === options.runId;
598
+ const candidate = data.runId;
599
+ return candidate === undefined ? runId === options.runId : candidate === runId;
600
+ });
601
+ return {
602
+ runId: options.runId,
603
+ async append(event) {
604
+ if (isRunJournalEventInit(event))
605
+ await appendRunEvent(event);
606
+ },
607
+ appendRunEvent,
608
+ async recordPipeline(runId, pipeline) {
609
+ await appendRunEvent({ type: "pipeline-resolved", pipeline }, runId);
610
+ },
611
+ async recordStageOutcome(runId, outcome) {
612
+ await appendRunEvent({ type: "stage-outcome", outcome }, runId);
613
+ },
614
+ async read(runId) {
615
+ return entriesForRun(runId);
616
+ },
617
+ readEntries,
618
+ readProjection: () => foldRunSessionEntries(entriesForRun(options.runId), options.runId)
619
+ };
620
+ }
20
621
  async function createRunJournal(sessionManager, runId) {
21
622
  try {
22
- const journal = new RunSessionJournal(sessionManager, runId);
623
+ const writableSessionManager = sessionManager;
624
+ const journal = new RunSessionJournal(writableSessionManager, runId);
23
625
  const kernel = createJournalSessionProvider({
24
626
  runId,
25
627
  store: {
26
- appendCustomEntry: (customType, data) => sessionManager.appendCustomEntry(customType, data),
628
+ appendCustomEntry: (customType, data) => writableSessionManager.appendCustomEntry(customType, data),
27
629
  getEntries: () => sessionManager.getEntries?.() ?? sessionManager.getBranch?.() ?? []
28
630
  }
29
631
  });
@@ -42,24 +644,7 @@ async function createRunJournal(sessionManager, runId) {
42
644
  }
43
645
  }
44
646
 
45
- // packages/run-worker/src/notifications.ts
46
- import { resolve } from "path";
47
- import { dispatchEventToTargets, loadNotificationConfig } from "@rig/notifications-plugin/notifications";
48
- async function dispatchRunNotifications(projectRoot, runId, taskId, outcome, detail) {
49
- try {
50
- const config = await loadNotificationConfig(resolve(projectRoot, ".rig", "notifications.json"));
51
- if (config.targets.length === 0)
52
- return;
53
- const event = { runId, type: `run.${outcome}`, timestamp: new Date().toISOString(), payload: { taskId, detail } };
54
- await Promise.race([
55
- dispatchEventToTargets(event, config.targets),
56
- new Promise((resolveCap) => setTimeout(resolveCap, 5000))
57
- ]);
58
- } catch {}
59
- }
60
-
61
647
  // packages/run-worker/src/constants.ts
62
- var RUN_PROCESS_STEER_TIMEOUT_MS = 10 * 60 * 1000;
63
648
  var TRACKED_RUN_STALL_MS = 20 * 60 * 1000;
64
649
  var RUN_PROCESS_STALL_SWEEP_MS = 60 * 1000;
65
650
  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.";
@@ -108,43 +693,9 @@ function startRunProcessStallMonitor(opts) {
108
693
  return () => clearInterval(timer);
109
694
  }
110
695
 
111
- // packages/run-worker/src/panel-plugin.ts
112
- import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/core/project-plugins";
113
- import { RIG_CAPABILITY_PANEL_SLOT, RIG_RUN_STOP_PANEL_ACTION, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
114
- var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
115
- var PANEL_PRODUCER_TIMEOUT_MS = 2000;
116
- async function produceWorkerPanelPayload(producer, context) {
117
- if (!producer.produce)
118
- return;
119
- let timeout;
120
- try {
121
- return await Promise.race([
122
- Promise.resolve(producer.produce(context)),
123
- new Promise((resolve2) => {
124
- timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
125
- })
126
- ]);
127
- } finally {
128
- clearTimeout(timeout);
129
- }
130
- }
131
- async function loadWorkerPanelRegistry(projectRoot) {
132
- const { host, resolved } = await createProjectPluginHost(projectRoot, {
133
- mode: "strict-config-only",
134
- surfaceName: "run-worker-panel-registry"
135
- });
136
- for (const message of projectPluginResolutionWarningMessages(resolved)) {
137
- console.warn(`[rig-run] ${message}`);
138
- }
139
- return {
140
- registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
141
- producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
142
- };
143
- }
144
-
145
696
  // packages/run-worker/src/utils.ts
146
- import { createWorkflowStatusChanged, RIG_WORKFLOW_STATUS_CHANGED } from "@rig/contracts";
147
- import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/runtime/control-plane/remote-config";
697
+ import { RIG_WORKFLOW_STATUS_CHANGED as RIG_WORKFLOW_STATUS_CHANGED2 } from "@rig/contracts";
698
+ import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/core/remote-config";
148
699
  function customEntries(entries) {
149
700
  const customs = [];
150
701
  for (const entry of entries) {
@@ -164,6 +715,12 @@ function rigRelayUrl() {
164
715
  }
165
716
 
166
717
  // packages/run-worker/src/autohost.ts
718
+ var TaskDataCap = defineCapability2(TASK_DATA_SERVICE_CAPABILITY);
719
+ var PlacementTransportCap = defineCapability2(PLACEMENT_RUN_TRANSPORT);
720
+ var RunIdentityEnvCap = defineCapability2(RUN_IDENTITY_ENV);
721
+ var RunRegistryBackboneCap = defineCapability2(RUN_REGISTRY_BACKBONE);
722
+ var RunCloseoutCap = defineCapability2(RUN_CLOSEOUT_CAPABILITY);
723
+ var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable in run-exec autohost: ensure @rig/task-sources-plugin (default bundle) is installed.");
167
724
  function syntheticRegistryOwner(namespaceKey) {
168
725
  const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
169
726
  return {
@@ -172,19 +729,44 @@ function syntheticRegistryOwner(namespaceKey) {
172
729
  namespaceKey
173
730
  };
174
731
  }
732
+ var REGISTRY_STATUS = {
733
+ created: true,
734
+ queued: true,
735
+ preparing: true,
736
+ running: true,
737
+ "waiting-approval": true,
738
+ "waiting-user-input": true,
739
+ paused: true,
740
+ validating: true,
741
+ reviewing: true,
742
+ "closing-out": true,
743
+ "needs-attention": true,
744
+ completed: true,
745
+ failed: true,
746
+ stopped: true,
747
+ starting: true,
748
+ "waiting-input": true
749
+ };
750
+ function coerceRegistryStatus(value, fallback) {
751
+ if (value === "starting")
752
+ return "preparing";
753
+ if (value === "waiting-input")
754
+ return "waiting-user-input";
755
+ return typeof value === "string" && REGISTRY_STATUS[value] ? value : fallback;
756
+ }
175
757
  var REGISTRY_PROJECTION_TIMELINE_LIMIT = 100;
176
758
  async function reflectStoppedRunTaskSource(input) {
177
759
  const taskId = input.taskId?.trim();
178
760
  if (!taskId)
179
761
  return false;
180
762
  const reason = input.reason?.trim();
181
- await (input.updateLifecycle ?? updateRunTaskSourceLifecycle)(input.projectRoot, {
763
+ await (input.updateLifecycle ?? taskData().updateRunTaskSourceLifecycle)(input.projectRoot, {
182
764
  runId: input.runId,
183
765
  taskId,
184
- sourceTask: input.sourceTask,
185
- worktreePath: input.worktreePath,
186
- logRoot: input.logRoot,
187
- sessionPath: input.sessionPath
766
+ ...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
767
+ ...input.worktreePath !== undefined ? { worktreePath: input.worktreePath } : {},
768
+ ...input.logRoot !== undefined ? { logRoot: input.logRoot } : {},
769
+ ...input.sessionPath !== undefined ? { sessionPath: input.sessionPath } : {}
188
770
  }, "cancelled", reason ? `Rig stopped by operator: ${reason}` : "Rig stopped by operator.");
189
771
  return true;
190
772
  }
@@ -266,8 +848,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
266
848
  throw new Error("OMP session id is required when RIG_RUN_PROCESS=1; RIG_RUN_ID is only a dispatch handle.");
267
849
  const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
268
850
  const journal = await createRunJournal(ctx.sessionManager, runId);
269
- await adoptPlacementKernel(projectRoot, {
851
+ const placementTransport = await loadCapabilityForRoot(projectRoot, PlacementTransportCap);
852
+ if (!placementTransport) {
853
+ throw new Error("placement run transport capability unavailable: ensure @rig/transport-plugin is installed.");
854
+ }
855
+ await adoptRunWorkerKernel(projectRoot, {
270
856
  entrypoint: "run-worker",
857
+ hydrateEnv: true,
858
+ transport: placementTransport,
271
859
  ...journal ? { journal: journal.kernel } : {}
272
860
  });
273
861
  if (!ctx.hasUI)
@@ -276,7 +864,9 @@ async function maybeStartRunProcessAutohost(api, ctx) {
276
864
  const startHost = collab?.startCollabHost ?? collab?.startHost;
277
865
  if (!startHost)
278
866
  throw new Error("OMP collab host facade is unavailable for Rig run process.");
279
- const identity = resolveRigIdentity(ctx);
867
+ const identityEnv = await loadCapabilityForRoot(projectRoot, RunIdentityEnvCap);
868
+ const identity = identityEnv?.resolveRigIdentity(ctx) ?? null;
869
+ const registryBackbone = await loadCapabilityForRoot(projectRoot, RunRegistryBackboneCap);
280
870
  const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
281
871
  let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
282
872
  const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
@@ -298,7 +888,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
298
888
  const runRegistryNamespace = identity?.owner?.namespaceKey ?? resolveOwnerNamespaceKey(rigProjectRoot()) ?? "anonymous";
299
889
  const runRegistryOwner = identity?.owner ?? syntheticRegistryOwner(runRegistryNamespace);
300
890
  const runRegistryRepo = identity?.selectedRepo ?? "";
301
- const runRegistry = createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace });
891
+ const runRegistry = registryBackbone?.createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace }) ?? null;
302
892
  let runRegistryLinks = {};
303
893
  const processedControlEntryIds = new Set((ctx.sessionManager.getEntries?.() ?? []).map((entry) => entry && typeof entry === "object" && typeof entry.id === "string" ? entry.id : null).filter((id) => id !== null));
304
894
  const processedControlEntryObjects = new WeakSet;
@@ -332,7 +922,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
332
922
  joinLink: links.joinLink,
333
923
  webLink: links.webLink,
334
924
  relayUrl: links.relayUrl,
335
- sessionPath: ctx.sessionManager.getSessionFile(),
925
+ sessionPath: ctx.sessionManager.getSessionFile() ?? null,
336
926
  timeline
337
927
  });
338
928
  };
@@ -346,7 +936,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
346
936
  projectRoot,
347
937
  runId,
348
938
  folded,
349
- taskIdAtStart,
939
+ ...taskIdAtStart !== undefined ? { taskIdAtStart } : {},
350
940
  runDisplayTitle
351
941
  };
352
942
  const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
@@ -406,7 +996,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
406
996
  runRegistryHeartbeat = null;
407
997
  }
408
998
  if (workerProjectionFiber) {
409
- Effect.runFork(Fiber.interrupt(workerProjectionFiber));
999
+ Effect2.runFork(Fiber.interrupt(workerProjectionFiber));
410
1000
  workerProjectionFiber = null;
411
1001
  }
412
1002
  if (workerProjectionConnection) {
@@ -518,7 +1108,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
518
1108
  if (roomId !== runId) {
519
1109
  throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
520
1110
  }
521
- runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
1111
+ runRegistryLinks = { joinLink: projection.joinLink ?? null, webLink: projection.webLink ?? null, relayUrl: projection.relayUrl ?? rigRelayUrl() };
522
1112
  const timeline = {
523
1113
  type: "collab-host-started",
524
1114
  roomId,
@@ -530,10 +1120,11 @@ async function maybeStartRunProcessAutohost(api, ctx) {
530
1120
  console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
531
1121
  ctx.ui.notify("Rig run collab host started.", "info");
532
1122
  publishRigPanelsSnapshotBestEffort();
533
- if (runRegistry) {
1123
+ if (runRegistry && registryBackbone) {
534
1124
  try {
535
1125
  runRegistryRoomId = roomId;
536
1126
  const initialProjection = buildCurrentRegistryProjection();
1127
+ const sessionPath = ctx.sessionManager.getSessionFile();
537
1128
  await runRegistry.registerRoom({
538
1129
  roomId,
539
1130
  owner: runRegistryOwner,
@@ -545,14 +1136,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
545
1136
  relayUrl: projection.relayUrl ?? rigRelayUrl(),
546
1137
  startedAt: new Date().toISOString(),
547
1138
  cwd: process.cwd(),
548
- sessionPath: ctx.sessionManager.getSessionFile() ?? undefined,
1139
+ ...sessionPath ? { sessionPath } : {},
549
1140
  pid: process.pid,
550
1141
  projection: initialProjection
551
1142
  });
552
- workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
1143
+ workerProjectionConnection = registryBackbone.connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
553
1144
  workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
554
1145
  pushWorkerProjection();
555
- workerProjectionFiber = Effect.runFork(localRunChanges(projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.sync(() => pushWorkerProjection()))));
1146
+ workerProjectionFiber = Effect2.runFork(localRunChanges(projectRoot).pipe(Stream2.debounce(Duration.millis(500)), Stream2.runForEach(() => Effect2.sync(() => pushWorkerProjection()))));
556
1147
  journal?.appendTimeline({ type: "registry-registered", roomId });
557
1148
  console.log(`[rig-run] registry-registered roomId=${roomId}`);
558
1149
  runRegistryHeartbeat = setInterval(() => {
@@ -585,7 +1176,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
585
1176
  if (taskIdAtStart) {
586
1177
  journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
587
1178
  try {
588
- await updateRunTaskSourceLifecycle(projectRoot, {
1179
+ await taskData().updateRunTaskSourceLifecycle(projectRoot, {
589
1180
  runId,
590
1181
  taskId: taskIdAtStart,
591
1182
  sourceTask: runProjection.record.sourceTask,
@@ -678,10 +1269,9 @@ async function maybeStartRunProcessAutohost(api, ctx) {
678
1269
  setTimeout(() => process.exit(1), 0);
679
1270
  return;
680
1271
  }
681
- const { command, gitCommand } = createEnvCloseoutRunners(process.env);
682
1272
  let closeoutStatusAdvanced = false;
683
1273
  try {
684
- await runPipelineCloseout({
1274
+ await requireInstalledCapability(RunCloseoutCap, "run-closeout capability unavailable in run-worker autohost: ensure @rig/bundle-default-lifecycle (default bundle) is installed.")({
685
1275
  projectRoot,
686
1276
  runId,
687
1277
  taskId,
@@ -689,8 +1279,6 @@ async function maybeStartRunProcessAutohost(api, ctx) {
689
1279
  workspace: process.cwd(),
690
1280
  artifactRoot: runProjection.record.artifactRoot ?? null,
691
1281
  sourceTask: runProjection.record.sourceTask && typeof runProjection.record.sourceTask === "object" && !Array.isArray(runProjection.record.sourceTask) ? runProjection.record.sourceTask : null,
692
- command,
693
- gitCommand,
694
1282
  steerPi,
695
1283
  onValidationStart: async () => {
696
1284
  journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
@@ -706,7 +1294,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
706
1294
  }
707
1295
  },
708
1296
  reflect: async (status, summary, opts) => {
709
- await updateRunTaskSourceLifecycle(projectRoot, {
1297
+ await taskData().updateRunTaskSourceLifecycle(projectRoot, {
710
1298
  runId,
711
1299
  taskId,
712
1300
  sourceTask: runProjection.record.sourceTask,
@@ -725,7 +1313,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
725
1313
  setTimeout(() => process.exit(0), 0);
726
1314
  } catch (error) {
727
1315
  const detail = error instanceof Error ? error.message : String(error);
728
- const status = error instanceof CloseoutValidationError ? "needs-attention" : "failed";
1316
+ const status = error instanceof Error && error.name === "CloseoutValidationError" ? "needs-attention" : "failed";
729
1317
  journal?.appendStatus(status, { actor: { kind: "agent" }, errorText: detail, force: true });
730
1318
  await publishRunProjection(status);
731
1319
  await dispatchRunNotifications(projectRoot, runId, taskId, "failed", detail);
@@ -751,7 +1339,7 @@ async function maybeStartSpikeAutohost(ctx) {
751
1339
  const startHost = collab?.startHost ?? collab?.startCollabHost;
752
1340
  if (!startHost)
753
1341
  throw new Error("OMP collab host facade is unavailable for detached PTY spike.");
754
- const identity = resolveRigIdentity(ctx);
1342
+ const identity = (await loadCapabilityForRoot(rigProjectRoot(), RunIdentityEnvCap))?.resolveRigIdentity(ctx) ?? null;
755
1343
  await startHost.call(collab, {
756
1344
  relayUrl,
757
1345
  title: "Rig detached PTY spike",