@h-rig/run-worker 0.0.6-alpha.136 → 0.0.6-alpha.138

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.
@@ -1,6 +1,5 @@
1
1
  import type { ExtensionAPI, ExtensionContext, AgentEndEvent } from "@oh-my-pi/pi-coding-agent";
2
- import { type RigRunTimelineEntry, type RunInboxResolutionSentinel, type RunSessionCustomEntry } from "@rig/contracts";
3
- import { projectRunFromSession } from "@rig/runtime/control-plane/run-session-projection";
2
+ import { type RigRunTimelineEntry, type RunInboxResolutionSentinel, type RunJournalProjection, type RunSessionCustomEntry } from "@rig/contracts";
4
3
  import { updateRunTaskSourceLifecycle } from "@rig/runtime/control-plane/tasks/source-lifecycle";
5
4
  import { type RegistryRunProjection } from "@rig/relay-registry";
6
5
  export interface RunProcessWorkerHooks {
@@ -23,7 +22,7 @@ export declare function reflectStoppedRunTaskSource(input: {
23
22
  }): Promise<boolean>;
24
23
  export declare function registryRunProjection(input: {
25
24
  readonly runId: string;
26
- readonly folded: ReturnType<typeof projectRunFromSession>;
25
+ readonly folded: RunJournalProjection;
27
26
  readonly entries: readonly RunSessionCustomEntry[];
28
27
  readonly title: string;
29
28
  readonly joinLink?: string | null;
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // packages/run-worker/src/autohost.ts
3
3
  import { runPipelineCloseout } from "@rig/bundle-default-lifecycle/pipeline-closeout";
4
+ import { adoptPlacementKernel } from "@rig/composition/kernel-entrypoint";
4
5
  import { latestTimelineEntriesFromCustomEntries, parseInboxResolutionSentinel, parsePauseSentinel, parseResumeSentinel, parseStopSentinel, sessionIdFromSessionFile } from "@rig/contracts";
5
6
  import { Duration, Effect, Fiber, Stream } from "effect";
6
7
  import { createEnvCloseoutRunners } from "@rig/runtime/control-plane/native/closeout-runners";
@@ -12,7 +13,6 @@ import { resolveOwnerNamespaceKey } from "@rig/runtime/control-plane/remote-conf
12
13
  import { resolveRigIdentity } from "@rig/runtime/control-plane/identity";
13
14
  import { connectWorkerProjection, createRegistryClient } from "@rig/relay-registry";
14
15
  import { coerceRegistryStatus } from "@rig/relay-registry/schema";
15
- import { bootDefaultKernelIntoProcess } from "@rig/kernel/boot-default";
16
16
 
17
17
  // packages/run-worker/src/journal.ts
18
18
  import { createJournalSessionProvider } from "@rig/kernel/journal-session-provider";
@@ -108,6 +108,91 @@ function startRunProcessStallMonitor(opts) {
108
108
  return () => clearInterval(timer);
109
109
  }
110
110
 
111
+ // packages/run-worker/src/panel-plugin.ts
112
+ import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/composition/project-plugins";
113
+ var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
114
+ var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
115
+ var RIG_CAPABILITY_PANEL_SLOT = "capability";
116
+ var RIG_SUPERVISOR_PANEL_ID = "supervisor";
117
+ var PANEL_PRODUCER_TIMEOUT_MS = 2000;
118
+ var RUN_SUPERVISOR_PANEL_REGISTRATION = {
119
+ id: RIG_SUPERVISOR_PANEL_ID,
120
+ slot: RIG_CAPABILITY_PANEL_SLOT,
121
+ title: "Supervisor",
122
+ capabilityId: "run.supervisor",
123
+ description: "Live run status, closeout progress, and operator stop control."
124
+ };
125
+ function buildSupervisorPanelPayload(context) {
126
+ const status = context.folded.status ?? "unknown";
127
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
128
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
129
+ return {
130
+ status,
131
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
132
+ processed: context.folded.closeoutPhases.length,
133
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
134
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
135
+ skipped: 0,
136
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
137
+ idleReason: operatorActive ? null : status,
138
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
139
+ closures: []
140
+ };
141
+ }
142
+ var RUN_SUPERVISOR_PANEL_PRODUCER = {
143
+ ...RUN_SUPERVISOR_PANEL_REGISTRATION,
144
+ produce(context) {
145
+ return buildSupervisorPanelPayload(context);
146
+ }
147
+ };
148
+ async function produceWorkerPanelPayload(producer, context) {
149
+ if (!producer.produce)
150
+ return;
151
+ let timeout;
152
+ try {
153
+ return await Promise.race([
154
+ Promise.resolve(producer.produce(context)),
155
+ new Promise((resolve2) => {
156
+ timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
157
+ })
158
+ ]);
159
+ } finally {
160
+ clearTimeout(timeout);
161
+ }
162
+ }
163
+ var RUN_WORKER_PANEL_PLUGIN = {
164
+ name: "@rig/run-worker-panels",
165
+ version: "0.0.0-alpha.1",
166
+ contributes: {
167
+ panels: [RUN_SUPERVISOR_PANEL_REGISTRATION]
168
+ },
169
+ __runtime: {
170
+ panels: [RUN_SUPERVISOR_PANEL_PRODUCER]
171
+ }
172
+ };
173
+ async function loadWorkerPanelRegistry(projectRoot) {
174
+ try {
175
+ const { host, resolved } = await createProjectPluginHost(projectRoot, {
176
+ mode: "compatibility-default-injection",
177
+ surfaceName: "run-worker-panel-registry",
178
+ surfaceExtras: [RUN_WORKER_PANEL_PLUGIN]
179
+ });
180
+ for (const message of projectPluginResolutionWarningMessages(resolved)) {
181
+ console.warn(`[rig-run] ${message}`);
182
+ }
183
+ return {
184
+ registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
185
+ producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
186
+ };
187
+ } catch (error) {
188
+ console.error(`[rig-run] panel-registry-build-failed ${error instanceof Error ? error.message : String(error)}`);
189
+ return {
190
+ registrations: [RUN_SUPERVISOR_PANEL_REGISTRATION],
191
+ producers: [RUN_SUPERVISOR_PANEL_PRODUCER]
192
+ };
193
+ }
194
+ }
195
+
111
196
  // packages/run-worker/src/utils.ts
112
197
  import { createWorkflowStatusChanged, RIG_WORKFLOW_STATUS_CHANGED } from "@rig/contracts";
113
198
  import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/runtime/control-plane/remote-config";
@@ -130,8 +215,6 @@ function rigRelayUrl() {
130
215
  }
131
216
 
132
217
  // packages/run-worker/src/autohost.ts
133
- var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
134
- var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
135
218
  function syntheticRegistryOwner(namespaceKey) {
136
219
  const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
137
220
  return {
@@ -228,7 +311,16 @@ async function maybeStartRunProcessAutohost(api, ctx) {
228
311
  const envRunId = process.env.RIG_RUN_ID?.trim();
229
312
  if (!envRunId)
230
313
  throw new Error("RIG_RUN_ID is required when RIG_RUN_PROCESS=1");
231
- const runId = sessionIdFromSessionFile(ctx.sessionManager.getSessionFile()) ?? envRunId;
314
+ const sessionManagerRunId = typeof ctx.sessionManager.getSessionId === "function" ? ctx.sessionManager.getSessionId().trim() : "";
315
+ const runId = 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
+ const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
319
+ const journal = await createRunJournal(ctx.sessionManager, runId);
320
+ await adoptPlacementKernel(projectRoot, {
321
+ entrypoint: "run-worker",
322
+ ...journal ? { journal: journal.kernel } : {}
323
+ });
232
324
  if (!ctx.hasUI)
233
325
  return null;
234
326
  const collab = ctx.collab;
@@ -236,12 +328,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
236
328
  if (!startHost)
237
329
  throw new Error("OMP collab host facade is unavailable for Rig run process.");
238
330
  const identity = resolveRigIdentity(ctx);
239
- const journal = await createRunJournal(ctx.sessionManager, runId);
240
- await bootDefaultKernelIntoProcess({
241
- entrypoint: "run-worker",
242
- ...journal ? { journal: journal.kernel } : {}
243
- });
244
- const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
331
+ const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
245
332
  let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
246
333
  const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
247
334
  const runDisplayTitle = (() => {
@@ -301,30 +388,43 @@ async function maybeStartRunProcessAutohost(api, ctx) {
301
388
  });
302
389
  };
303
390
  let lastRigPanelsSnapshotJson = "";
304
- const publishRigPanelsSnapshot = () => {
391
+ const publishRigPanelsSnapshot = async () => {
305
392
  const appendCustomMessageEntry = ctx.sessionManager.appendCustomMessageEntry?.bind(ctx.sessionManager);
306
393
  if (!appendCustomMessageEntry)
307
394
  return;
308
395
  const { folded } = currentProjectionParts();
309
- const status = folded.status ?? "unknown";
310
- const taskId = folded.record.taskId ?? taskIdAtStart;
311
- const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
396
+ const producerContext = {
397
+ projectRoot,
398
+ runId,
399
+ folded,
400
+ taskIdAtStart,
401
+ runDisplayTitle
402
+ };
403
+ const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
404
+ try {
405
+ const payload = await produceWorkerPanelPayload(producer, producerContext);
406
+ return payload === undefined ? null : [producer.id, payload];
407
+ } catch (error) {
408
+ console.error(`[rig-run] panel-producer-failed panel=${producer.id} ${error instanceof Error ? error.message : String(error)}`);
409
+ return null;
410
+ }
411
+ }));
412
+ const payloads = Object.fromEntries(payloadEntries.filter((entry) => entry !== null));
413
+ const registrations = panelRegistry.registrations.map((panel) => ({
414
+ id: panel.id,
415
+ slot: panel.slot,
416
+ title: panel.title,
417
+ capabilityId: panel.capabilityId,
418
+ description: panel.description,
419
+ badge: panel.badge,
420
+ disabled: panel.disabled
421
+ }));
422
+ const activePanel = registrations.find((panel) => panel.id === RIG_SUPERVISOR_PANEL_ID)?.id ?? registrations.find((panel) => !panel.disabled)?.id ?? registrations[0]?.id ?? null;
312
423
  const frameBody = {
313
424
  kind: "snapshot",
314
- activePanel: "supervisor",
315
- registrations: [{ id: "supervisor", title: "Supervisor" }],
316
- supervisor: {
317
- status,
318
- currentTask: taskId ? { id: taskId, title: runDisplayTitle } : null,
319
- processed: folded.closeoutPhases.length,
320
- succeeded: folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
321
- failed: folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
322
- skipped: 0,
323
- plannedOrder: taskId ? [{ id: taskId, title: runDisplayTitle, status }] : [],
324
- idleReason: operatorActive ? null : status,
325
- stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
326
- closures: []
327
- }
425
+ activePanel,
426
+ registrations,
427
+ payloads
328
428
  };
329
429
  const serialized = JSON.stringify(frameBody);
330
430
  if (serialized === lastRigPanelsSnapshotJson)
@@ -332,6 +432,11 @@ async function maybeStartRunProcessAutohost(api, ctx) {
332
432
  lastRigPanelsSnapshotJson = serialized;
333
433
  appendCustomMessageEntry(RIG_PANELS_CUSTOM_MESSAGE_TYPE, "", false, { ...frameBody, updatedAt: new Date().toISOString() }, "agent");
334
434
  };
435
+ const publishRigPanelsSnapshotBestEffort = () => {
436
+ publishRigPanelsSnapshot().catch((error) => {
437
+ console.error(`[rig-run] panel-snapshot-publish-failed ${error instanceof Error ? error.message : String(error)}`);
438
+ });
439
+ };
335
440
  const pushWorkerProjection = (links = runRegistryLinks) => {
336
441
  if (!workerProjectionConnection)
337
442
  return;
@@ -339,12 +444,12 @@ async function maybeStartRunProcessAutohost(api, ctx) {
339
444
  };
340
445
  const publishRunProjection = async (status) => {
341
446
  const projection = buildCurrentRegistryProjection();
342
- publishRigPanelsSnapshot();
343
447
  if (workerProjectionConnection)
344
448
  workerProjectionConnection.push(projection);
345
449
  if (runRegistry && runRegistryRoomId) {
346
450
  await runRegistry.heartbeatRoom(runRegistryRoomId, status, projection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
347
451
  }
452
+ publishRigPanelsSnapshotBestEffort();
348
453
  };
349
454
  const stopRunRegistry = (opts = {}) => {
350
455
  if (runRegistryHeartbeat) {
@@ -460,10 +565,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
460
565
  relayUrl: rigRelayUrl(),
461
566
  onPanelAction: handlePanelAction
462
567
  });
568
+ const roomId = projection.sessionId?.trim() || runId;
569
+ if (roomId !== runId) {
570
+ throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
571
+ }
463
572
  runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
464
573
  const timeline = {
465
574
  type: "collab-host-started",
466
- roomId: projection.sessionId,
575
+ roomId,
467
576
  joinLink: projection.joinLink,
468
577
  webLink: projection.webLink,
469
578
  relayUrl: projection.relayUrl
@@ -471,13 +580,13 @@ async function maybeStartRunProcessAutohost(api, ctx) {
471
580
  journal?.appendTimeline(timeline);
472
581
  console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
473
582
  ctx.ui.notify("Rig run collab host started.", "info");
474
- publishRigPanelsSnapshot();
583
+ publishRigPanelsSnapshotBestEffort();
475
584
  if (runRegistry) {
476
585
  try {
477
- runRegistryRoomId = projection.sessionId;
586
+ runRegistryRoomId = roomId;
478
587
  const initialProjection = buildCurrentRegistryProjection();
479
588
  await runRegistry.registerRoom({
480
- roomId: projection.sessionId,
589
+ roomId,
481
590
  owner: runRegistryOwner,
482
591
  repo: runRegistryRepo,
483
592
  title: runDisplayTitle,
@@ -491,15 +600,15 @@ async function maybeStartRunProcessAutohost(api, ctx) {
491
600
  pid: process.pid,
492
601
  projection: initialProjection
493
602
  });
494
- workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: projection.sessionId });
603
+ workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
495
604
  workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
496
605
  pushWorkerProjection();
497
606
  workerProjectionFiber = Effect.runFork(localRunChanges(projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.sync(() => pushWorkerProjection()))));
498
- journal?.appendTimeline({ type: "registry-registered", roomId: projection.sessionId });
499
- console.log(`[rig-run] registry-registered roomId=${projection.sessionId}`);
607
+ journal?.appendTimeline({ type: "registry-registered", roomId });
608
+ console.log(`[rig-run] registry-registered roomId=${roomId}`);
500
609
  runRegistryHeartbeat = setInterval(() => {
501
610
  const heartbeatProjection = buildCurrentRegistryProjection();
502
- runRegistry.heartbeatRoom(projection.sessionId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
611
+ runRegistry.heartbeatRoom(roomId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
503
612
  }, 15000);
504
613
  if (typeof runRegistryHeartbeat.unref === "function")
505
614
  runRegistryHeartbeat.unref();
@@ -508,15 +617,22 @@ async function maybeStartRunProcessAutohost(api, ctx) {
508
617
  ctx.ui.notify("Rig run could not register to the discovery registry; it may not appear in Runs.", "warning");
509
618
  }
510
619
  }
620
+ return true;
511
621
  } catch (error) {
622
+ if (error instanceof Error && error.message.startsWith("Collab host session id mismatch:")) {
623
+ publishFatalTerminal(error);
624
+ return false;
625
+ }
512
626
  console.error(`[rig-run] collab-host-start-failed ${error instanceof Error ? error.message : String(error)}`);
513
627
  ctx.ui.notify("Rig run collab host could not reach the relay; session remains local.", "warning");
628
+ return true;
514
629
  }
515
630
  };
516
631
  journal?.appendTimeline({ type: "stage", stage: "Connect", status: "running", detail: "run process session_start" });
517
632
  journal?.appendTimeline({ type: "stage", stage: "Prepare", status: "completed", detail: "run process prepared" });
518
633
  journal?.appendStatus("running", { actor: { kind: "agent" }, reason: "run process session started", force: true });
519
- await startRunCollabHost();
634
+ if (!await startRunCollabHost())
635
+ return null;
520
636
  if (taskIdAtStart) {
521
637
  journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
522
638
  try {
@@ -631,6 +747,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
631
747
  journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
632
748
  await publishRunProjection("validating");
633
749
  },
750
+ kernelJournal: journal?.kernel ?? null,
634
751
  journalPhase: async (phase, outcome, detail) => {
635
752
  journal?.appendCloseoutPhase({ phase, outcome, detail: detail ?? null });
636
753
  if (!closeoutStatusAdvanced && phase !== "queued" && outcome === "started") {
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // packages/run-worker/src/autohost.ts
3
3
  import { runPipelineCloseout } from "@rig/bundle-default-lifecycle/pipeline-closeout";
4
+ import { adoptPlacementKernel } from "@rig/composition/kernel-entrypoint";
4
5
  import { latestTimelineEntriesFromCustomEntries, parseInboxResolutionSentinel, parsePauseSentinel, parseResumeSentinel, parseStopSentinel, sessionIdFromSessionFile } from "@rig/contracts";
5
6
  import { Duration, Effect, Fiber, Stream } from "effect";
6
7
  import { createEnvCloseoutRunners } from "@rig/runtime/control-plane/native/closeout-runners";
@@ -12,7 +13,6 @@ import { resolveOwnerNamespaceKey } from "@rig/runtime/control-plane/remote-conf
12
13
  import { resolveRigIdentity } from "@rig/runtime/control-plane/identity";
13
14
  import { connectWorkerProjection, createRegistryClient } from "@rig/relay-registry";
14
15
  import { coerceRegistryStatus } from "@rig/relay-registry/schema";
15
- import { bootDefaultKernelIntoProcess } from "@rig/kernel/boot-default";
16
16
 
17
17
  // packages/run-worker/src/journal.ts
18
18
  import { createJournalSessionProvider } from "@rig/kernel/journal-session-provider";
@@ -108,6 +108,91 @@ function startRunProcessStallMonitor(opts) {
108
108
  return () => clearInterval(timer);
109
109
  }
110
110
 
111
+ // packages/run-worker/src/panel-plugin.ts
112
+ import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/composition/project-plugins";
113
+ var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
114
+ var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
115
+ var RIG_CAPABILITY_PANEL_SLOT = "capability";
116
+ var RIG_SUPERVISOR_PANEL_ID = "supervisor";
117
+ var PANEL_PRODUCER_TIMEOUT_MS = 2000;
118
+ var RUN_SUPERVISOR_PANEL_REGISTRATION = {
119
+ id: RIG_SUPERVISOR_PANEL_ID,
120
+ slot: RIG_CAPABILITY_PANEL_SLOT,
121
+ title: "Supervisor",
122
+ capabilityId: "run.supervisor",
123
+ description: "Live run status, closeout progress, and operator stop control."
124
+ };
125
+ function buildSupervisorPanelPayload(context) {
126
+ const status = context.folded.status ?? "unknown";
127
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
128
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
129
+ return {
130
+ status,
131
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
132
+ processed: context.folded.closeoutPhases.length,
133
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
134
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
135
+ skipped: 0,
136
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
137
+ idleReason: operatorActive ? null : status,
138
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
139
+ closures: []
140
+ };
141
+ }
142
+ var RUN_SUPERVISOR_PANEL_PRODUCER = {
143
+ ...RUN_SUPERVISOR_PANEL_REGISTRATION,
144
+ produce(context) {
145
+ return buildSupervisorPanelPayload(context);
146
+ }
147
+ };
148
+ async function produceWorkerPanelPayload(producer, context) {
149
+ if (!producer.produce)
150
+ return;
151
+ let timeout;
152
+ try {
153
+ return await Promise.race([
154
+ Promise.resolve(producer.produce(context)),
155
+ new Promise((resolve2) => {
156
+ timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
157
+ })
158
+ ]);
159
+ } finally {
160
+ clearTimeout(timeout);
161
+ }
162
+ }
163
+ var RUN_WORKER_PANEL_PLUGIN = {
164
+ name: "@rig/run-worker-panels",
165
+ version: "0.0.0-alpha.1",
166
+ contributes: {
167
+ panels: [RUN_SUPERVISOR_PANEL_REGISTRATION]
168
+ },
169
+ __runtime: {
170
+ panels: [RUN_SUPERVISOR_PANEL_PRODUCER]
171
+ }
172
+ };
173
+ async function loadWorkerPanelRegistry(projectRoot) {
174
+ try {
175
+ const { host, resolved } = await createProjectPluginHost(projectRoot, {
176
+ mode: "compatibility-default-injection",
177
+ surfaceName: "run-worker-panel-registry",
178
+ surfaceExtras: [RUN_WORKER_PANEL_PLUGIN]
179
+ });
180
+ for (const message of projectPluginResolutionWarningMessages(resolved)) {
181
+ console.warn(`[rig-run] ${message}`);
182
+ }
183
+ return {
184
+ registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
185
+ producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
186
+ };
187
+ } catch (error) {
188
+ console.error(`[rig-run] panel-registry-build-failed ${error instanceof Error ? error.message : String(error)}`);
189
+ return {
190
+ registrations: [RUN_SUPERVISOR_PANEL_REGISTRATION],
191
+ producers: [RUN_SUPERVISOR_PANEL_PRODUCER]
192
+ };
193
+ }
194
+ }
195
+
111
196
  // packages/run-worker/src/utils.ts
112
197
  import { createWorkflowStatusChanged, RIG_WORKFLOW_STATUS_CHANGED } from "@rig/contracts";
113
198
  import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/runtime/control-plane/remote-config";
@@ -130,8 +215,6 @@ function rigRelayUrl() {
130
215
  }
131
216
 
132
217
  // packages/run-worker/src/autohost.ts
133
- var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
134
- var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
135
218
  function syntheticRegistryOwner(namespaceKey) {
136
219
  const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
137
220
  return {
@@ -228,7 +311,16 @@ async function maybeStartRunProcessAutohost(api, ctx) {
228
311
  const envRunId = process.env.RIG_RUN_ID?.trim();
229
312
  if (!envRunId)
230
313
  throw new Error("RIG_RUN_ID is required when RIG_RUN_PROCESS=1");
231
- const runId = sessionIdFromSessionFile(ctx.sessionManager.getSessionFile()) ?? envRunId;
314
+ const sessionManagerRunId = typeof ctx.sessionManager.getSessionId === "function" ? ctx.sessionManager.getSessionId().trim() : "";
315
+ const runId = 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
+ const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
319
+ const journal = await createRunJournal(ctx.sessionManager, runId);
320
+ await adoptPlacementKernel(projectRoot, {
321
+ entrypoint: "run-worker",
322
+ ...journal ? { journal: journal.kernel } : {}
323
+ });
232
324
  if (!ctx.hasUI)
233
325
  return null;
234
326
  const collab = ctx.collab;
@@ -236,12 +328,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
236
328
  if (!startHost)
237
329
  throw new Error("OMP collab host facade is unavailable for Rig run process.");
238
330
  const identity = resolveRigIdentity(ctx);
239
- const journal = await createRunJournal(ctx.sessionManager, runId);
240
- await bootDefaultKernelIntoProcess({
241
- entrypoint: "run-worker",
242
- ...journal ? { journal: journal.kernel } : {}
243
- });
244
- const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
331
+ const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
245
332
  let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
246
333
  const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
247
334
  const runDisplayTitle = (() => {
@@ -301,30 +388,43 @@ async function maybeStartRunProcessAutohost(api, ctx) {
301
388
  });
302
389
  };
303
390
  let lastRigPanelsSnapshotJson = "";
304
- const publishRigPanelsSnapshot = () => {
391
+ const publishRigPanelsSnapshot = async () => {
305
392
  const appendCustomMessageEntry = ctx.sessionManager.appendCustomMessageEntry?.bind(ctx.sessionManager);
306
393
  if (!appendCustomMessageEntry)
307
394
  return;
308
395
  const { folded } = currentProjectionParts();
309
- const status = folded.status ?? "unknown";
310
- const taskId = folded.record.taskId ?? taskIdAtStart;
311
- const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
396
+ const producerContext = {
397
+ projectRoot,
398
+ runId,
399
+ folded,
400
+ taskIdAtStart,
401
+ runDisplayTitle
402
+ };
403
+ const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
404
+ try {
405
+ const payload = await produceWorkerPanelPayload(producer, producerContext);
406
+ return payload === undefined ? null : [producer.id, payload];
407
+ } catch (error) {
408
+ console.error(`[rig-run] panel-producer-failed panel=${producer.id} ${error instanceof Error ? error.message : String(error)}`);
409
+ return null;
410
+ }
411
+ }));
412
+ const payloads = Object.fromEntries(payloadEntries.filter((entry) => entry !== null));
413
+ const registrations = panelRegistry.registrations.map((panel) => ({
414
+ id: panel.id,
415
+ slot: panel.slot,
416
+ title: panel.title,
417
+ capabilityId: panel.capabilityId,
418
+ description: panel.description,
419
+ badge: panel.badge,
420
+ disabled: panel.disabled
421
+ }));
422
+ const activePanel = registrations.find((panel) => panel.id === RIG_SUPERVISOR_PANEL_ID)?.id ?? registrations.find((panel) => !panel.disabled)?.id ?? registrations[0]?.id ?? null;
312
423
  const frameBody = {
313
424
  kind: "snapshot",
314
- activePanel: "supervisor",
315
- registrations: [{ id: "supervisor", title: "Supervisor" }],
316
- supervisor: {
317
- status,
318
- currentTask: taskId ? { id: taskId, title: runDisplayTitle } : null,
319
- processed: folded.closeoutPhases.length,
320
- succeeded: folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
321
- failed: folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
322
- skipped: 0,
323
- plannedOrder: taskId ? [{ id: taskId, title: runDisplayTitle, status }] : [],
324
- idleReason: operatorActive ? null : status,
325
- stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
326
- closures: []
327
- }
425
+ activePanel,
426
+ registrations,
427
+ payloads
328
428
  };
329
429
  const serialized = JSON.stringify(frameBody);
330
430
  if (serialized === lastRigPanelsSnapshotJson)
@@ -332,6 +432,11 @@ async function maybeStartRunProcessAutohost(api, ctx) {
332
432
  lastRigPanelsSnapshotJson = serialized;
333
433
  appendCustomMessageEntry(RIG_PANELS_CUSTOM_MESSAGE_TYPE, "", false, { ...frameBody, updatedAt: new Date().toISOString() }, "agent");
334
434
  };
435
+ const publishRigPanelsSnapshotBestEffort = () => {
436
+ publishRigPanelsSnapshot().catch((error) => {
437
+ console.error(`[rig-run] panel-snapshot-publish-failed ${error instanceof Error ? error.message : String(error)}`);
438
+ });
439
+ };
335
440
  const pushWorkerProjection = (links = runRegistryLinks) => {
336
441
  if (!workerProjectionConnection)
337
442
  return;
@@ -339,12 +444,12 @@ async function maybeStartRunProcessAutohost(api, ctx) {
339
444
  };
340
445
  const publishRunProjection = async (status) => {
341
446
  const projection = buildCurrentRegistryProjection();
342
- publishRigPanelsSnapshot();
343
447
  if (workerProjectionConnection)
344
448
  workerProjectionConnection.push(projection);
345
449
  if (runRegistry && runRegistryRoomId) {
346
450
  await runRegistry.heartbeatRoom(runRegistryRoomId, status, projection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
347
451
  }
452
+ publishRigPanelsSnapshotBestEffort();
348
453
  };
349
454
  const stopRunRegistry = (opts = {}) => {
350
455
  if (runRegistryHeartbeat) {
@@ -460,10 +565,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
460
565
  relayUrl: rigRelayUrl(),
461
566
  onPanelAction: handlePanelAction
462
567
  });
568
+ const roomId = projection.sessionId?.trim() || runId;
569
+ if (roomId !== runId) {
570
+ throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
571
+ }
463
572
  runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
464
573
  const timeline = {
465
574
  type: "collab-host-started",
466
- roomId: projection.sessionId,
575
+ roomId,
467
576
  joinLink: projection.joinLink,
468
577
  webLink: projection.webLink,
469
578
  relayUrl: projection.relayUrl
@@ -471,13 +580,13 @@ async function maybeStartRunProcessAutohost(api, ctx) {
471
580
  journal?.appendTimeline(timeline);
472
581
  console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
473
582
  ctx.ui.notify("Rig run collab host started.", "info");
474
- publishRigPanelsSnapshot();
583
+ publishRigPanelsSnapshotBestEffort();
475
584
  if (runRegistry) {
476
585
  try {
477
- runRegistryRoomId = projection.sessionId;
586
+ runRegistryRoomId = roomId;
478
587
  const initialProjection = buildCurrentRegistryProjection();
479
588
  await runRegistry.registerRoom({
480
- roomId: projection.sessionId,
589
+ roomId,
481
590
  owner: runRegistryOwner,
482
591
  repo: runRegistryRepo,
483
592
  title: runDisplayTitle,
@@ -491,15 +600,15 @@ async function maybeStartRunProcessAutohost(api, ctx) {
491
600
  pid: process.pid,
492
601
  projection: initialProjection
493
602
  });
494
- workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: projection.sessionId });
603
+ workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
495
604
  workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
496
605
  pushWorkerProjection();
497
606
  workerProjectionFiber = Effect.runFork(localRunChanges(projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.sync(() => pushWorkerProjection()))));
498
- journal?.appendTimeline({ type: "registry-registered", roomId: projection.sessionId });
499
- console.log(`[rig-run] registry-registered roomId=${projection.sessionId}`);
607
+ journal?.appendTimeline({ type: "registry-registered", roomId });
608
+ console.log(`[rig-run] registry-registered roomId=${roomId}`);
500
609
  runRegistryHeartbeat = setInterval(() => {
501
610
  const heartbeatProjection = buildCurrentRegistryProjection();
502
- runRegistry.heartbeatRoom(projection.sessionId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
611
+ runRegistry.heartbeatRoom(roomId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
503
612
  }, 15000);
504
613
  if (typeof runRegistryHeartbeat.unref === "function")
505
614
  runRegistryHeartbeat.unref();
@@ -508,15 +617,22 @@ async function maybeStartRunProcessAutohost(api, ctx) {
508
617
  ctx.ui.notify("Rig run could not register to the discovery registry; it may not appear in Runs.", "warning");
509
618
  }
510
619
  }
620
+ return true;
511
621
  } catch (error) {
622
+ if (error instanceof Error && error.message.startsWith("Collab host session id mismatch:")) {
623
+ publishFatalTerminal(error);
624
+ return false;
625
+ }
512
626
  console.error(`[rig-run] collab-host-start-failed ${error instanceof Error ? error.message : String(error)}`);
513
627
  ctx.ui.notify("Rig run collab host could not reach the relay; session remains local.", "warning");
628
+ return true;
514
629
  }
515
630
  };
516
631
  journal?.appendTimeline({ type: "stage", stage: "Connect", status: "running", detail: "run process session_start" });
517
632
  journal?.appendTimeline({ type: "stage", stage: "Prepare", status: "completed", detail: "run process prepared" });
518
633
  journal?.appendStatus("running", { actor: { kind: "agent" }, reason: "run process session started", force: true });
519
- await startRunCollabHost();
634
+ if (!await startRunCollabHost())
635
+ return null;
520
636
  if (taskIdAtStart) {
521
637
  journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
522
638
  try {
@@ -631,6 +747,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
631
747
  journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
632
748
  await publishRunProjection("validating");
633
749
  },
750
+ kernelJournal: journal?.kernel ?? null,
634
751
  journalPhase: async (phase, outcome, detail) => {
635
752
  journal?.appendCloseoutPhase({ phase, outcome, detail: detail ?? null });
636
753
  if (!closeoutStatusAdvanced && phase !== "queued" && outcome === "started") {
@@ -2,5 +2,6 @@ export { default, __rigExtensionTest, __rigRunWorkerTest } from "./extension";
2
2
  export * from "./autohost";
3
3
  export * from "./constants";
4
4
  export * from "./journal";
5
+ export * from "./panel-plugin";
5
6
  export * from "./notifications";
6
7
  export * from "./stall";
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // packages/run-worker/src/autohost.ts
3
3
  import { runPipelineCloseout } from "@rig/bundle-default-lifecycle/pipeline-closeout";
4
+ import { adoptPlacementKernel } from "@rig/composition/kernel-entrypoint";
4
5
  import { latestTimelineEntriesFromCustomEntries, parseInboxResolutionSentinel, parsePauseSentinel, parseResumeSentinel, parseStopSentinel, sessionIdFromSessionFile } from "@rig/contracts";
5
6
  import { Duration, Effect, Fiber, Stream } from "effect";
6
7
  import { createEnvCloseoutRunners } from "@rig/runtime/control-plane/native/closeout-runners";
@@ -12,7 +13,6 @@ import { resolveOwnerNamespaceKey } from "@rig/runtime/control-plane/remote-conf
12
13
  import { resolveRigIdentity } from "@rig/runtime/control-plane/identity";
13
14
  import { connectWorkerProjection, createRegistryClient } from "@rig/relay-registry";
14
15
  import { coerceRegistryStatus } from "@rig/relay-registry/schema";
15
- import { bootDefaultKernelIntoProcess } from "@rig/kernel/boot-default";
16
16
 
17
17
  // packages/run-worker/src/journal.ts
18
18
  import { createJournalSessionProvider } from "@rig/kernel/journal-session-provider";
@@ -108,6 +108,91 @@ function startRunProcessStallMonitor(opts) {
108
108
  return () => clearInterval(timer);
109
109
  }
110
110
 
111
+ // packages/run-worker/src/panel-plugin.ts
112
+ import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/composition/project-plugins";
113
+ var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
114
+ var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
115
+ var RIG_CAPABILITY_PANEL_SLOT = "capability";
116
+ var RIG_SUPERVISOR_PANEL_ID = "supervisor";
117
+ var PANEL_PRODUCER_TIMEOUT_MS = 2000;
118
+ var RUN_SUPERVISOR_PANEL_REGISTRATION = {
119
+ id: RIG_SUPERVISOR_PANEL_ID,
120
+ slot: RIG_CAPABILITY_PANEL_SLOT,
121
+ title: "Supervisor",
122
+ capabilityId: "run.supervisor",
123
+ description: "Live run status, closeout progress, and operator stop control."
124
+ };
125
+ function buildSupervisorPanelPayload(context) {
126
+ const status = context.folded.status ?? "unknown";
127
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
128
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
129
+ return {
130
+ status,
131
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
132
+ processed: context.folded.closeoutPhases.length,
133
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
134
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
135
+ skipped: 0,
136
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
137
+ idleReason: operatorActive ? null : status,
138
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
139
+ closures: []
140
+ };
141
+ }
142
+ var RUN_SUPERVISOR_PANEL_PRODUCER = {
143
+ ...RUN_SUPERVISOR_PANEL_REGISTRATION,
144
+ produce(context) {
145
+ return buildSupervisorPanelPayload(context);
146
+ }
147
+ };
148
+ async function produceWorkerPanelPayload(producer, context) {
149
+ if (!producer.produce)
150
+ return;
151
+ let timeout;
152
+ try {
153
+ return await Promise.race([
154
+ Promise.resolve(producer.produce(context)),
155
+ new Promise((resolve2) => {
156
+ timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
157
+ })
158
+ ]);
159
+ } finally {
160
+ clearTimeout(timeout);
161
+ }
162
+ }
163
+ var RUN_WORKER_PANEL_PLUGIN = {
164
+ name: "@rig/run-worker-panels",
165
+ version: "0.0.0-alpha.1",
166
+ contributes: {
167
+ panels: [RUN_SUPERVISOR_PANEL_REGISTRATION]
168
+ },
169
+ __runtime: {
170
+ panels: [RUN_SUPERVISOR_PANEL_PRODUCER]
171
+ }
172
+ };
173
+ async function loadWorkerPanelRegistry(projectRoot) {
174
+ try {
175
+ const { host, resolved } = await createProjectPluginHost(projectRoot, {
176
+ mode: "compatibility-default-injection",
177
+ surfaceName: "run-worker-panel-registry",
178
+ surfaceExtras: [RUN_WORKER_PANEL_PLUGIN]
179
+ });
180
+ for (const message of projectPluginResolutionWarningMessages(resolved)) {
181
+ console.warn(`[rig-run] ${message}`);
182
+ }
183
+ return {
184
+ registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
185
+ producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
186
+ };
187
+ } catch (error) {
188
+ console.error(`[rig-run] panel-registry-build-failed ${error instanceof Error ? error.message : String(error)}`);
189
+ return {
190
+ registrations: [RUN_SUPERVISOR_PANEL_REGISTRATION],
191
+ producers: [RUN_SUPERVISOR_PANEL_PRODUCER]
192
+ };
193
+ }
194
+ }
195
+
111
196
  // packages/run-worker/src/utils.ts
112
197
  import { createWorkflowStatusChanged, RIG_WORKFLOW_STATUS_CHANGED } from "@rig/contracts";
113
198
  import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/runtime/control-plane/remote-config";
@@ -130,8 +215,6 @@ function rigRelayUrl() {
130
215
  }
131
216
 
132
217
  // packages/run-worker/src/autohost.ts
133
- var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
134
- var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
135
218
  function syntheticRegistryOwner(namespaceKey) {
136
219
  const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
137
220
  return {
@@ -228,7 +311,16 @@ async function maybeStartRunProcessAutohost(api, ctx) {
228
311
  const envRunId = process.env.RIG_RUN_ID?.trim();
229
312
  if (!envRunId)
230
313
  throw new Error("RIG_RUN_ID is required when RIG_RUN_PROCESS=1");
231
- const runId = sessionIdFromSessionFile(ctx.sessionManager.getSessionFile()) ?? envRunId;
314
+ const sessionManagerRunId = typeof ctx.sessionManager.getSessionId === "function" ? ctx.sessionManager.getSessionId().trim() : "";
315
+ const runId = 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
+ const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
319
+ const journal = await createRunJournal(ctx.sessionManager, runId);
320
+ await adoptPlacementKernel(projectRoot, {
321
+ entrypoint: "run-worker",
322
+ ...journal ? { journal: journal.kernel } : {}
323
+ });
232
324
  if (!ctx.hasUI)
233
325
  return null;
234
326
  const collab = ctx.collab;
@@ -236,12 +328,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
236
328
  if (!startHost)
237
329
  throw new Error("OMP collab host facade is unavailable for Rig run process.");
238
330
  const identity = resolveRigIdentity(ctx);
239
- const journal = await createRunJournal(ctx.sessionManager, runId);
240
- await bootDefaultKernelIntoProcess({
241
- entrypoint: "run-worker",
242
- ...journal ? { journal: journal.kernel } : {}
243
- });
244
- const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
331
+ const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
245
332
  let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
246
333
  const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
247
334
  const runDisplayTitle = (() => {
@@ -301,30 +388,43 @@ async function maybeStartRunProcessAutohost(api, ctx) {
301
388
  });
302
389
  };
303
390
  let lastRigPanelsSnapshotJson = "";
304
- const publishRigPanelsSnapshot = () => {
391
+ const publishRigPanelsSnapshot = async () => {
305
392
  const appendCustomMessageEntry = ctx.sessionManager.appendCustomMessageEntry?.bind(ctx.sessionManager);
306
393
  if (!appendCustomMessageEntry)
307
394
  return;
308
395
  const { folded } = currentProjectionParts();
309
- const status = folded.status ?? "unknown";
310
- const taskId = folded.record.taskId ?? taskIdAtStart;
311
- const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
396
+ const producerContext = {
397
+ projectRoot,
398
+ runId,
399
+ folded,
400
+ taskIdAtStart,
401
+ runDisplayTitle
402
+ };
403
+ const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
404
+ try {
405
+ const payload = await produceWorkerPanelPayload(producer, producerContext);
406
+ return payload === undefined ? null : [producer.id, payload];
407
+ } catch (error) {
408
+ console.error(`[rig-run] panel-producer-failed panel=${producer.id} ${error instanceof Error ? error.message : String(error)}`);
409
+ return null;
410
+ }
411
+ }));
412
+ const payloads = Object.fromEntries(payloadEntries.filter((entry) => entry !== null));
413
+ const registrations = panelRegistry.registrations.map((panel) => ({
414
+ id: panel.id,
415
+ slot: panel.slot,
416
+ title: panel.title,
417
+ capabilityId: panel.capabilityId,
418
+ description: panel.description,
419
+ badge: panel.badge,
420
+ disabled: panel.disabled
421
+ }));
422
+ const activePanel = registrations.find((panel) => panel.id === RIG_SUPERVISOR_PANEL_ID)?.id ?? registrations.find((panel) => !panel.disabled)?.id ?? registrations[0]?.id ?? null;
312
423
  const frameBody = {
313
424
  kind: "snapshot",
314
- activePanel: "supervisor",
315
- registrations: [{ id: "supervisor", title: "Supervisor" }],
316
- supervisor: {
317
- status,
318
- currentTask: taskId ? { id: taskId, title: runDisplayTitle } : null,
319
- processed: folded.closeoutPhases.length,
320
- succeeded: folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
321
- failed: folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
322
- skipped: 0,
323
- plannedOrder: taskId ? [{ id: taskId, title: runDisplayTitle, status }] : [],
324
- idleReason: operatorActive ? null : status,
325
- stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
326
- closures: []
327
- }
425
+ activePanel,
426
+ registrations,
427
+ payloads
328
428
  };
329
429
  const serialized = JSON.stringify(frameBody);
330
430
  if (serialized === lastRigPanelsSnapshotJson)
@@ -332,6 +432,11 @@ async function maybeStartRunProcessAutohost(api, ctx) {
332
432
  lastRigPanelsSnapshotJson = serialized;
333
433
  appendCustomMessageEntry(RIG_PANELS_CUSTOM_MESSAGE_TYPE, "", false, { ...frameBody, updatedAt: new Date().toISOString() }, "agent");
334
434
  };
435
+ const publishRigPanelsSnapshotBestEffort = () => {
436
+ publishRigPanelsSnapshot().catch((error) => {
437
+ console.error(`[rig-run] panel-snapshot-publish-failed ${error instanceof Error ? error.message : String(error)}`);
438
+ });
439
+ };
335
440
  const pushWorkerProjection = (links = runRegistryLinks) => {
336
441
  if (!workerProjectionConnection)
337
442
  return;
@@ -339,12 +444,12 @@ async function maybeStartRunProcessAutohost(api, ctx) {
339
444
  };
340
445
  const publishRunProjection = async (status) => {
341
446
  const projection = buildCurrentRegistryProjection();
342
- publishRigPanelsSnapshot();
343
447
  if (workerProjectionConnection)
344
448
  workerProjectionConnection.push(projection);
345
449
  if (runRegistry && runRegistryRoomId) {
346
450
  await runRegistry.heartbeatRoom(runRegistryRoomId, status, projection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
347
451
  }
452
+ publishRigPanelsSnapshotBestEffort();
348
453
  };
349
454
  const stopRunRegistry = (opts = {}) => {
350
455
  if (runRegistryHeartbeat) {
@@ -460,10 +565,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
460
565
  relayUrl: rigRelayUrl(),
461
566
  onPanelAction: handlePanelAction
462
567
  });
568
+ const roomId = projection.sessionId?.trim() || runId;
569
+ if (roomId !== runId) {
570
+ throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
571
+ }
463
572
  runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
464
573
  const timeline = {
465
574
  type: "collab-host-started",
466
- roomId: projection.sessionId,
575
+ roomId,
467
576
  joinLink: projection.joinLink,
468
577
  webLink: projection.webLink,
469
578
  relayUrl: projection.relayUrl
@@ -471,13 +580,13 @@ async function maybeStartRunProcessAutohost(api, ctx) {
471
580
  journal?.appendTimeline(timeline);
472
581
  console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
473
582
  ctx.ui.notify("Rig run collab host started.", "info");
474
- publishRigPanelsSnapshot();
583
+ publishRigPanelsSnapshotBestEffort();
475
584
  if (runRegistry) {
476
585
  try {
477
- runRegistryRoomId = projection.sessionId;
586
+ runRegistryRoomId = roomId;
478
587
  const initialProjection = buildCurrentRegistryProjection();
479
588
  await runRegistry.registerRoom({
480
- roomId: projection.sessionId,
589
+ roomId,
481
590
  owner: runRegistryOwner,
482
591
  repo: runRegistryRepo,
483
592
  title: runDisplayTitle,
@@ -491,15 +600,15 @@ async function maybeStartRunProcessAutohost(api, ctx) {
491
600
  pid: process.pid,
492
601
  projection: initialProjection
493
602
  });
494
- workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: projection.sessionId });
603
+ workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
495
604
  workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
496
605
  pushWorkerProjection();
497
606
  workerProjectionFiber = Effect.runFork(localRunChanges(projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.sync(() => pushWorkerProjection()))));
498
- journal?.appendTimeline({ type: "registry-registered", roomId: projection.sessionId });
499
- console.log(`[rig-run] registry-registered roomId=${projection.sessionId}`);
607
+ journal?.appendTimeline({ type: "registry-registered", roomId });
608
+ console.log(`[rig-run] registry-registered roomId=${roomId}`);
500
609
  runRegistryHeartbeat = setInterval(() => {
501
610
  const heartbeatProjection = buildCurrentRegistryProjection();
502
- runRegistry.heartbeatRoom(projection.sessionId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
611
+ runRegistry.heartbeatRoom(roomId, coerceRegistryStatus(heartbeatProjection.status, "running"), heartbeatProjection).catch((err) => console.error(`[rig-run] registry-heartbeat-failed ${err instanceof Error ? err.message : String(err)}`));
503
612
  }, 15000);
504
613
  if (typeof runRegistryHeartbeat.unref === "function")
505
614
  runRegistryHeartbeat.unref();
@@ -508,15 +617,22 @@ async function maybeStartRunProcessAutohost(api, ctx) {
508
617
  ctx.ui.notify("Rig run could not register to the discovery registry; it may not appear in Runs.", "warning");
509
618
  }
510
619
  }
620
+ return true;
511
621
  } catch (error) {
622
+ if (error instanceof Error && error.message.startsWith("Collab host session id mismatch:")) {
623
+ publishFatalTerminal(error);
624
+ return false;
625
+ }
512
626
  console.error(`[rig-run] collab-host-start-failed ${error instanceof Error ? error.message : String(error)}`);
513
627
  ctx.ui.notify("Rig run collab host could not reach the relay; session remains local.", "warning");
628
+ return true;
514
629
  }
515
630
  };
516
631
  journal?.appendTimeline({ type: "stage", stage: "Connect", status: "running", detail: "run process session_start" });
517
632
  journal?.appendTimeline({ type: "stage", stage: "Prepare", status: "completed", detail: "run process prepared" });
518
633
  journal?.appendStatus("running", { actor: { kind: "agent" }, reason: "run process session started", force: true });
519
- await startRunCollabHost();
634
+ if (!await startRunCollabHost())
635
+ return null;
520
636
  if (taskIdAtStart) {
521
637
  journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
522
638
  try {
@@ -631,6 +747,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
631
747
  journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
632
748
  await publishRunProjection("validating");
633
749
  },
750
+ kernelJournal: journal?.kernel ?? null,
634
751
  journalPhase: async (phase, outcome, detail) => {
635
752
  journal?.appendCloseoutPhase({ phase, outcome, detail: detail ?? null });
636
753
  if (!closeoutStatusAdvanced && phase !== "queued" && outcome === "started") {
@@ -722,8 +839,10 @@ export {
722
839
  startRunProcessStallMonitor,
723
840
  registryRunProjection,
724
841
  reflectStoppedRunTaskSource,
842
+ produceWorkerPanelPayload,
725
843
  maybeStartSpikeAutohost,
726
844
  maybeStartRunProcessAutohost,
845
+ loadWorkerPanelRegistry,
727
846
  dispatchRunNotifications,
728
847
  detectRunControlText,
729
848
  rigWorkerExtension as default,
@@ -733,8 +852,13 @@ export {
733
852
  __rigRunWorkerTest,
734
853
  __rigExtensionTest,
735
854
  TRACKED_RUN_STALL_MS,
855
+ RUN_WORKER_PANEL_PLUGIN,
736
856
  RUN_PROCESS_STEER_TIMEOUT_MS,
737
857
  RUN_PROCESS_STALL_SWEEP_MS,
738
858
  RUN_PROCESS_STALL_DETAIL,
859
+ RIG_SUPERVISOR_PANEL_ID,
860
+ RIG_RUN_STOP_PANEL_ACTION,
861
+ RIG_PANELS_CUSTOM_MESSAGE_TYPE,
862
+ RIG_CAPABILITY_PANEL_SLOT,
739
863
  REGISTRY_PROJECTION_TIMELINE_LIMIT
740
864
  };
@@ -0,0 +1,20 @@
1
+ import type { PanelRegistration, RunJournalProjection } from "@rig/contracts";
2
+ import type { RigPluginWithRuntime, RuntimePanelProducer } from "@rig/core";
3
+ export declare const RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
4
+ export declare const RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
5
+ export declare const RIG_CAPABILITY_PANEL_SLOT = "capability";
6
+ export declare const RIG_SUPERVISOR_PANEL_ID = "supervisor";
7
+ export interface WorkerPanelProducerContext {
8
+ readonly projectRoot: string;
9
+ readonly runId: string;
10
+ readonly folded: RunJournalProjection;
11
+ readonly taskIdAtStart?: string | null;
12
+ readonly runDisplayTitle: string;
13
+ }
14
+ export interface WorkerPanelRegistrySnapshot {
15
+ readonly registrations: readonly PanelRegistration[];
16
+ readonly producers: readonly RuntimePanelProducer[];
17
+ }
18
+ export declare function produceWorkerPanelPayload(producer: RuntimePanelProducer, context: WorkerPanelProducerContext): Promise<unknown>;
19
+ export declare const RUN_WORKER_PANEL_PLUGIN: RigPluginWithRuntime;
20
+ export declare function loadWorkerPanelRegistry(projectRoot: string): Promise<WorkerPanelRegistrySnapshot>;
@@ -0,0 +1,94 @@
1
+ // @bun
2
+ // packages/run-worker/src/panel-plugin.ts
3
+ import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/composition/project-plugins";
4
+ var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
5
+ var RIG_RUN_STOP_PANEL_ACTION = "rig-run:stop";
6
+ var RIG_CAPABILITY_PANEL_SLOT = "capability";
7
+ var RIG_SUPERVISOR_PANEL_ID = "supervisor";
8
+ var PANEL_PRODUCER_TIMEOUT_MS = 2000;
9
+ var RUN_SUPERVISOR_PANEL_REGISTRATION = {
10
+ id: RIG_SUPERVISOR_PANEL_ID,
11
+ slot: RIG_CAPABILITY_PANEL_SLOT,
12
+ title: "Supervisor",
13
+ capabilityId: "run.supervisor",
14
+ description: "Live run status, closeout progress, and operator stop control."
15
+ };
16
+ function buildSupervisorPanelPayload(context) {
17
+ const status = context.folded.status ?? "unknown";
18
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
19
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
20
+ return {
21
+ status,
22
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
23
+ processed: context.folded.closeoutPhases.length,
24
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
25
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
26
+ skipped: 0,
27
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
28
+ idleReason: operatorActive ? null : status,
29
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
30
+ closures: []
31
+ };
32
+ }
33
+ var RUN_SUPERVISOR_PANEL_PRODUCER = {
34
+ ...RUN_SUPERVISOR_PANEL_REGISTRATION,
35
+ produce(context) {
36
+ return buildSupervisorPanelPayload(context);
37
+ }
38
+ };
39
+ async function produceWorkerPanelPayload(producer, context) {
40
+ if (!producer.produce)
41
+ return;
42
+ let timeout;
43
+ try {
44
+ return await Promise.race([
45
+ Promise.resolve(producer.produce(context)),
46
+ new Promise((resolve) => {
47
+ timeout = setTimeout(() => resolve(undefined), PANEL_PRODUCER_TIMEOUT_MS);
48
+ })
49
+ ]);
50
+ } finally {
51
+ clearTimeout(timeout);
52
+ }
53
+ }
54
+ var RUN_WORKER_PANEL_PLUGIN = {
55
+ name: "@rig/run-worker-panels",
56
+ version: "0.0.0-alpha.1",
57
+ contributes: {
58
+ panels: [RUN_SUPERVISOR_PANEL_REGISTRATION]
59
+ },
60
+ __runtime: {
61
+ panels: [RUN_SUPERVISOR_PANEL_PRODUCER]
62
+ }
63
+ };
64
+ async function loadWorkerPanelRegistry(projectRoot) {
65
+ try {
66
+ const { host, resolved } = await createProjectPluginHost(projectRoot, {
67
+ mode: "compatibility-default-injection",
68
+ surfaceName: "run-worker-panel-registry",
69
+ surfaceExtras: [RUN_WORKER_PANEL_PLUGIN]
70
+ });
71
+ for (const message of projectPluginResolutionWarningMessages(resolved)) {
72
+ console.warn(`[rig-run] ${message}`);
73
+ }
74
+ return {
75
+ registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
76
+ producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
77
+ };
78
+ } catch (error) {
79
+ console.error(`[rig-run] panel-registry-build-failed ${error instanceof Error ? error.message : String(error)}`);
80
+ return {
81
+ registrations: [RUN_SUPERVISOR_PANEL_REGISTRATION],
82
+ producers: [RUN_SUPERVISOR_PANEL_PRODUCER]
83
+ };
84
+ }
85
+ }
86
+ export {
87
+ produceWorkerPanelPayload,
88
+ loadWorkerPanelRegistry,
89
+ RUN_WORKER_PANEL_PLUGIN,
90
+ RIG_SUPERVISOR_PANEL_ID,
91
+ RIG_RUN_STOP_PANEL_ACTION,
92
+ RIG_PANELS_CUSTOM_MESSAGE_TYPE,
93
+ RIG_CAPABILITY_PANEL_SLOT
94
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/run-worker",
3
- "version": "0.0.6-alpha.136",
3
+ "version": "0.0.6-alpha.138",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -26,12 +26,14 @@
26
26
  ]
27
27
  },
28
28
  "dependencies": {
29
- "@rig/bundle-default-lifecycle": "npm:@h-rig/bundle-default-lifecycle@0.0.6-alpha.136",
30
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.136",
31
- "@rig/kernel": "npm:@h-rig/kernel@0.0.6-alpha.136",
32
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.136",
33
- "@rig/relay-registry": "npm:@h-rig/relay-registry@0.0.6-alpha.136",
34
29
  "@oh-my-pi/pi-coding-agent": "16.0.4",
30
+ "@rig/bundle-default-lifecycle": "npm:@h-rig/bundle-default-lifecycle@0.0.6-alpha.138",
31
+ "@rig/composition": "npm:@h-rig/composition@0.0.6-alpha.138",
32
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.138",
33
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.138",
34
+ "@rig/kernel": "npm:@h-rig/kernel@0.0.6-alpha.138",
35
+ "@rig/relay-registry": "npm:@h-rig/relay-registry@0.0.6-alpha.138",
36
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.138",
35
37
  "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b"
36
38
  }
37
39
  }