@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,23 +1,331 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // packages/run-worker/src/runs/inbox.ts
5
- import { buildInboxResolutionSentinel } from "@rig/contracts";
4
+ // packages/run-worker/src/read-model-backend/inbox.ts
5
+ import { RIG_CONTROL_SENTINEL_END as RIG_CONTROL_SENTINEL_END3, RIG_INBOX_RESOLUTION_SENTINEL as RIG_INBOX_RESOLUTION_SENTINEL2 } from "@rig/contracts";
6
6
 
7
- // packages/run-worker/src/runs/control.ts
8
- import { buildPauseSentinel, buildResumeSentinel, buildStopSentinel } from "@rig/contracts";
7
+ // packages/run-worker/src/read-model-backend/control.ts
8
+ import { RIG_CONTROL_SENTINEL_END as RIG_CONTROL_SENTINEL_END2, RIG_PAUSE_SENTINEL as RIG_PAUSE_SENTINEL2, RIG_RESUME_SENTINEL as RIG_RESUME_SENTINEL2, RIG_STOP_SENTINEL as RIG_STOP_SENTINEL2, RIG_STOP_SENTINEL_END as RIG_STOP_SENTINEL_END2 } from "@rig/contracts";
9
9
 
10
- // packages/run-worker/src/runs/projection.ts
10
+ // packages/run-worker/src/read-model-backend/projection.ts
11
11
  import { isAbsolute, relative, resolve } from "path";
12
- import { Duration, Effect, Option, Stream } from "effect";
12
+ import { RUN_DISCOVERY } from "@rig/contracts";
13
+
14
+ // packages/run-worker/src/session-journal.ts
15
+ import { Schema } from "effect";
13
16
  import {
14
- foldRunSessionEntries,
15
- isTerminalRunStatus,
16
- timelineEntriesFromCustomEntries
17
+ CUSTOM_TYPE_FOR,
18
+ RIG_CONTROL_SENTINEL_END,
19
+ RIG_INBOX_RESOLUTION_SENTINEL,
20
+ RIG_PAUSE_SENTINEL,
21
+ RIG_RESUME_SENTINEL,
22
+ RIG_STOP_SENTINEL,
23
+ RIG_STOP_SENTINEL_END,
24
+ RIG_WORKFLOW_STATUS_CHANGED,
25
+ RunJournalEvent,
26
+ TYPE_FOR_CUSTOM
17
27
  } from "@rig/contracts";
18
- import { registrySnapshotStream } from "@rig/runtime/control-plane/discovery";
28
+ var decodeRunJournalEvent = Schema.decodeUnknownSync(RunJournalEvent);
29
+ var RUN_STATUS_TRANSITIONS = {
30
+ created: ["queued", "preparing", "running", "failed", "stopped"],
31
+ queued: ["preparing", "running", "failed", "stopped"],
32
+ preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
33
+ running: [
34
+ "queued",
35
+ "waiting-approval",
36
+ "waiting-user-input",
37
+ "paused",
38
+ "validating",
39
+ "reviewing",
40
+ "closing-out",
41
+ "needs-attention",
42
+ "completed",
43
+ "failed",
44
+ "stopped"
45
+ ],
46
+ "waiting-approval": ["running", "needs-attention", "failed", "stopped"],
47
+ "waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
48
+ paused: ["running", "failed", "stopped"],
49
+ validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
50
+ reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
51
+ "closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
52
+ "needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
53
+ completed: [],
54
+ failed: ["queued", "preparing", "running", "closing-out"],
55
+ stopped: ["queued", "preparing", "running", "closing-out"]
56
+ };
57
+ var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
58
+ function isTerminalRunStatus(status) {
59
+ return TERMINAL_RUN_STATUSES.includes(status);
60
+ }
61
+ function canTransitionRunStatus(from, to) {
62
+ if (from === null)
63
+ return true;
64
+ if (from === to)
65
+ return true;
66
+ return RUN_STATUS_TRANSITIONS[from].includes(to);
67
+ }
68
+ function assertRunStatusTransition(from, to) {
69
+ if (!canTransitionRunStatus(from, to)) {
70
+ throw new Error(`Illegal run status transition: ${from ?? "(none)"} -> ${to}. ` + `Allowed from ${from ?? "(none)"}: ${from ? RUN_STATUS_TRANSITIONS[from].join(", ") : "(any)"}.`);
71
+ }
72
+ }
73
+ function reduceRunJournal(events, runId = null) {
74
+ let record = {};
75
+ let status = null;
76
+ const statusHistory = [];
77
+ const pendingApprovals = new Map;
78
+ const resolvedApprovals = [];
79
+ const pendingUserInputs = new Map;
80
+ const resolvedUserInputs = [];
81
+ const closeoutPhases = [];
82
+ let resolvedPipeline = null;
83
+ const stageOutcomes = [];
84
+ const anomalies = [];
85
+ let steeringCount = 0;
86
+ let stallCount = 0;
87
+ let lastSeq = 0;
88
+ let lastEventAt = null;
89
+ const projectedRunId = runId ?? events[0]?.runId ?? null;
90
+ for (const event of events) {
91
+ lastSeq = event.seq;
92
+ lastEventAt = event.at;
93
+ switch (event.type) {
94
+ case "status-changed": {
95
+ if (!canTransitionRunStatus(status, event.to)) {
96
+ anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
97
+ }
98
+ statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
99
+ const wasTerminal = status !== null && isTerminalRunStatus(status);
100
+ status = event.to;
101
+ record = { ...record, updatedAt: event.at };
102
+ if (isTerminalRunStatus(event.to) && !record.completedAt)
103
+ record = { ...record, completedAt: event.at };
104
+ if (!isTerminalRunStatus(event.to) && wasTerminal)
105
+ record = { ...record, completedAt: null };
106
+ if (event.to === "running" && !record.startedAt)
107
+ record = { ...record, startedAt: event.at };
108
+ break;
109
+ }
110
+ case "record-patch": {
111
+ const next = { ...record };
112
+ for (const [key, value] of Object.entries(event.patch)) {
113
+ if (value !== undefined)
114
+ next[key] = value;
115
+ }
116
+ next.updatedAt = event.patch.updatedAt ?? event.at;
117
+ record = next;
118
+ break;
119
+ }
120
+ case "approval-requested": {
121
+ pendingApprovals.set(event.requestId, {
122
+ requestId: event.requestId,
123
+ requestKind: event.requestKind,
124
+ actionId: event.actionId ?? null,
125
+ payload: event.payload,
126
+ requestedAt: event.at
127
+ });
128
+ break;
129
+ }
130
+ case "approval-resolved": {
131
+ const pending = pendingApprovals.get(event.requestId);
132
+ if (!pending) {
133
+ const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
134
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
135
+ break;
136
+ }
137
+ pendingApprovals.delete(event.requestId);
138
+ resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
139
+ break;
140
+ }
141
+ case "input-requested": {
142
+ pendingUserInputs.set(event.requestId, {
143
+ requestId: event.requestId,
144
+ requestKind: "user-input",
145
+ actionId: null,
146
+ payload: event.payload,
147
+ requestedAt: event.at
148
+ });
149
+ break;
150
+ }
151
+ case "input-resolved": {
152
+ const pending = pendingUserInputs.get(event.requestId);
153
+ if (!pending) {
154
+ const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
155
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
156
+ break;
157
+ }
158
+ pendingUserInputs.delete(event.requestId);
159
+ resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
160
+ break;
161
+ }
162
+ case "steering":
163
+ steeringCount += 1;
164
+ break;
165
+ case "adopted":
166
+ record = { ...record, pid: event.pid, updatedAt: event.at };
167
+ break;
168
+ case "stall-detected":
169
+ stallCount += 1;
170
+ break;
171
+ case "closeout-phase":
172
+ closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
173
+ break;
174
+ case "pipeline-resolved":
175
+ resolvedPipeline = event.pipeline;
176
+ break;
177
+ case "stage-outcome":
178
+ stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
179
+ break;
180
+ case "timeline-entry":
181
+ case "log-entry":
182
+ break;
183
+ }
184
+ }
185
+ return {
186
+ runId: projectedRunId,
187
+ record,
188
+ status,
189
+ statusHistory,
190
+ pendingApprovals: [...pendingApprovals.values()],
191
+ resolvedApprovals,
192
+ pendingUserInputs: [...pendingUserInputs.values()],
193
+ resolvedUserInputs,
194
+ steeringCount,
195
+ stallCount,
196
+ closeoutPhases,
197
+ resolvedPipeline,
198
+ stageOutcomes,
199
+ lastSeq,
200
+ lastEventAt,
201
+ anomalies
202
+ };
203
+ }
204
+ function isRunSessionCustomType(customType) {
205
+ return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
206
+ }
207
+ function foldRunSessionEntries(entries, runId) {
208
+ const events = [];
209
+ entries.forEach((entry, index) => {
210
+ if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
211
+ return;
212
+ const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
213
+ const stamped = {
214
+ v: 1,
215
+ seq: index + 1,
216
+ at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
217
+ runId,
218
+ ...data,
219
+ type: TYPE_FOR_CUSTOM[entry.customType]
220
+ };
221
+ try {
222
+ events.push(decodeRunJournalEvent(stamped));
223
+ } catch {}
224
+ });
225
+ return reduceRunJournal(events, runId);
226
+ }
227
+ function isRecord(value) {
228
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
229
+ }
230
+ function timelineEntryFromCustomEntry(entry) {
231
+ if (entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
232
+ return null;
233
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
234
+ const type = typeof payload.type === "string" ? payload.type : "timeline";
235
+ const stage = typeof payload.stage === "string" ? payload.stage : typeof payload.name === "string" ? payload.name : null;
236
+ const status = typeof payload.status === "string" ? payload.status : typeof payload.outcome === "string" ? payload.outcome : null;
237
+ const detail = typeof payload.detail === "string" ? payload.detail : typeof payload.message === "string" ? payload.message : null;
238
+ const at = typeof entry.data.at === "string" ? entry.data.at : typeof payload.at === "string" ? payload.at : null;
239
+ return { at, type, stage, status, detail };
240
+ }
241
+ function timelineEntriesFromCustomEntries(entries) {
242
+ return entries.flatMap((entry) => {
243
+ const timelineEntry = timelineEntryFromCustomEntry(entry);
244
+ return timelineEntry ? [timelineEntry] : [];
245
+ });
246
+ }
247
+ function asCustomEntries(entries) {
248
+ return entries.filter((entry) => entry.type === "custom");
249
+ }
250
+
251
+ class RunSessionJournal {
252
+ sm;
253
+ runId;
254
+ constructor(sm, runId) {
255
+ this.sm = sm;
256
+ this.runId = runId;
257
+ }
258
+ #append(init) {
259
+ this.sm.appendCustomEntry(CUSTOM_TYPE_FOR[init.type], { ...init, at: new Date().toISOString() });
260
+ }
261
+ appendStatus(to, opts = {}) {
262
+ const from = foldRunSessionEntries(asCustomEntries(this.sm.getEntries()), this.runId).status;
263
+ if (!opts.force)
264
+ assertRunStatusTransition(from, to);
265
+ if (opts.errorText !== undefined)
266
+ this.#append({ type: "record-patch", patch: { errorText: opts.errorText } });
267
+ this.#append({
268
+ type: "status-changed",
269
+ from,
270
+ to,
271
+ ...opts.reason !== undefined ? { reason: opts.reason } : {},
272
+ ...opts.actor !== undefined ? { actor: opts.actor } : {}
273
+ });
274
+ }
275
+ appendTimeline(payload) {
276
+ this.#append({ type: "timeline-entry", payload });
277
+ }
278
+ appendCloseoutPhase(input) {
279
+ this.#append({
280
+ type: "closeout-phase",
281
+ phase: input.phase,
282
+ outcome: input.outcome,
283
+ ...input.detail !== undefined ? { detail: input.detail } : {}
284
+ });
285
+ }
286
+ appendApprovalResolved(input) {
287
+ this.#append({
288
+ type: "approval-resolved",
289
+ requestId: input.requestId,
290
+ decision: input.decision,
291
+ actor: input.actor,
292
+ ...input.note !== undefined ? { note: input.note } : {}
293
+ });
294
+ }
295
+ appendInputResolved(input) {
296
+ this.#append({ type: "input-resolved", requestId: input.requestId, answers: input.answers, actor: input.actor });
297
+ }
298
+ appendStall(input) {
299
+ this.#append({ type: "stall-detected", detail: input.detail });
300
+ }
301
+ }
302
+
303
+ // packages/run-worker/src/read-model-backend/projection.ts
304
+ import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
305
+ import { defineCapability } from "@rig/core/capability";
306
+
307
+ // packages/run-worker/src/read-model-backend/run-status.ts
308
+ import { OPERATOR_INACTIVE_RUN_STATUSES } from "@rig/contracts";
309
+ var TERMINAL_RUN_STATUSES2 = ["completed", "failed", "stopped"];
310
+ var ACTIVE_RUN_STATUSES = [
311
+ "created",
312
+ "queued",
313
+ "preparing",
314
+ "running",
315
+ "waiting-approval",
316
+ "waiting-user-input",
317
+ "paused",
318
+ "validating",
319
+ "reviewing",
320
+ "closing-out",
321
+ "needs-attention"
322
+ ];
323
+ var KNOWN_RUN_STATUS = Object.fromEntries([...ACTIVE_RUN_STATUSES, ...TERMINAL_RUN_STATUSES2].map((status) => [status, true]));
324
+ function asRunStatus(status) {
325
+ return KNOWN_RUN_STATUS[status] ? status : null;
326
+ }
19
327
 
20
- // packages/run-worker/src/runs/diagnostics.ts
328
+ // packages/run-worker/src/read-model-backend/diagnostics.ts
21
329
  function normalizeString(value) {
22
330
  if (typeof value !== "string")
23
331
  return null;
@@ -65,7 +373,27 @@ function summarizeRunError(projection) {
65
373
  return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
66
374
  }
67
375
 
68
- // packages/run-worker/src/runs/projection.ts
376
+ // packages/run-worker/src/read-model-backend/projection.ts
377
+ var RunDiscoveryCap = defineCapability(RUN_DISCOVERY);
378
+ function registryEntryFromCollab(collab) {
379
+ const record = collab;
380
+ return {
381
+ roomId: collab.sessionId,
382
+ title: collab.title,
383
+ status: record.registryStatus ?? (collab.stale ? "stale" : "running"),
384
+ startedAt: collab.startedAt ?? null,
385
+ heartbeatAt: collab.updatedAt ?? null,
386
+ sessionPath: collab.sessionPath ?? null,
387
+ cwd: collab.cwd ?? null,
388
+ joinLink: collab.joinLink ?? null,
389
+ webLink: collab.webLink ?? null,
390
+ relayUrl: collab.relayUrl ?? null,
391
+ stale: collab.stale,
392
+ repo: collab.selectedRepo ?? null,
393
+ ...collab.pid === undefined ? {} : { pid: collab.pid },
394
+ projection: record.registryProjection ?? null
395
+ };
396
+ }
69
397
  var EMPTY_PROJECTION = foldRunSessionEntries([], "");
70
398
  var DISCOVERY_DIAGNOSTIC_RUN_ID = "__registry_discovery_error__";
71
399
  function stringOrNull(value) {
@@ -82,22 +410,7 @@ function registryStatusAsRunStatus(status) {
82
410
  return "waiting-user-input";
83
411
  if (status === "starting")
84
412
  return "preparing";
85
- return typeof status === "string" && [
86
- "created",
87
- "queued",
88
- "preparing",
89
- "running",
90
- "waiting-approval",
91
- "waiting-user-input",
92
- "paused",
93
- "validating",
94
- "reviewing",
95
- "closing-out",
96
- "needs-attention",
97
- "completed",
98
- "failed",
99
- "stopped"
100
- ].includes(status) ? status : null;
413
+ return typeof status === "string" ? asRunStatus(status) : null;
101
414
  }
102
415
  function payloadString(payload, keys) {
103
416
  if (!payload || typeof payload !== "object")
@@ -120,12 +433,14 @@ function payloadOptions(payload) {
120
433
  }
121
434
  function inboxRequest(request, kind) {
122
435
  const fallback = kind === "approval" ? "Approval requested" : "Input requested";
436
+ const body = payloadString(request.payload, ["body", "description", "detail", "details"]);
437
+ const options = payloadOptions(request.payload);
123
438
  return {
124
439
  requestId: request.requestId,
125
440
  kind,
126
441
  title: payloadString(request.payload, ["title", "message", "reason", "prompt", "summary"]) ?? fallback,
127
- body: payloadString(request.payload, ["body", "description", "detail", "details"]),
128
- ...payloadOptions(request.payload) ? { options: payloadOptions(request.payload) } : {},
442
+ ...body !== undefined ? { body } : {},
443
+ ...options ? { options } : {},
129
444
  requestedAt: request.requestedAt ?? null,
130
445
  source: "run"
131
446
  };
@@ -234,8 +549,8 @@ function runRecordFromRegistryEntry(projectRoot, entry) {
234
549
  projection: folded
235
550
  };
236
551
  }
237
- function runRecordsFromRegistrySnapshot(projectRoot, snapshot) {
238
- return sortByRecency(snapshot.entries.map((entry) => runRecordFromRegistryEntry(projectRoot, entry)).filter((record) => record !== null));
552
+ function runRecordsFromCollab(projectRoot, collabs) {
553
+ return sortByRecency(collabs.map((collab) => runRecordFromRegistryEntry(projectRoot, registryEntryFromCollab(collab))).filter((record) => record !== null));
239
554
  }
240
555
  function sortByRecency(records) {
241
556
  return [...records].sort((a, b) => {
@@ -283,8 +598,11 @@ function discoveryDiagnosticRunRecord(projectRoot, error) {
283
598
  }
284
599
  async function listRunProjections(projectRoot, filter = {}) {
285
600
  try {
286
- const snapshot = await Effect.runPromise(registrySnapshotStream(projectRoot, filter).pipe(Stream.runHead, Effect.timeout(Duration.seconds(15)), Effect.map(Option.getOrNull)));
287
- return snapshot ? runRecordsFromRegistrySnapshot(projectRoot, snapshot) : [discoveryDiagnosticRunRecord(projectRoot, "registry discovery stream ended without a snapshot")];
601
+ const discovery = await loadCapabilityForRoot(projectRoot, RunDiscoveryCap);
602
+ if (!discovery)
603
+ return [discoveryDiagnosticRunRecord(projectRoot, "run discovery capability unavailable")];
604
+ const collabs = await discovery.listActiveRunCollab(projectRoot, filter);
605
+ return runRecordsFromCollab(projectRoot, collabs);
288
606
  } catch (error) {
289
607
  return [discoveryDiagnosticRunRecord(projectRoot, error)];
290
608
  }
@@ -312,7 +630,16 @@ async function getRunProjection(projectRoot, runId, filter = {}) {
312
630
  var listRuns = listRunProjections;
313
631
  var getRun = getRunProjection;
314
632
 
315
- // packages/run-worker/src/runs/control.ts
633
+ // packages/run-worker/src/read-model-backend/control.ts
634
+ function buildPauseSentinel(runId) {
635
+ return `${RIG_PAUSE_SENTINEL2} runId=${runId} requestedBy=operator ${RIG_CONTROL_SENTINEL_END2}`;
636
+ }
637
+ function buildResumeSentinel(runId) {
638
+ return `${RIG_RESUME_SENTINEL2} runId=${runId} requestedBy=operator ${RIG_CONTROL_SENTINEL_END2}`;
639
+ }
640
+ function buildStopSentinel(runId, reason) {
641
+ return `${RIG_STOP_SENTINEL2} runId=${runId} reason=${reason} ${RIG_STOP_SENTINEL_END2}`;
642
+ }
316
643
  async function loadCollabControlDeps() {
317
644
  const [{ importRoomKey, seal }, { COLLAB_PROTO, packEnvelope, parseCollabLink }] = await Promise.all([
318
645
  import("@oh-my-pi/pi-coding-agent/collab/crypto"),
@@ -392,7 +719,10 @@ async function deliverRunControl(projectRoot, target, control, deps = {}) {
392
719
  }
393
720
  }
394
721
 
395
- // packages/run-worker/src/runs/inbox.ts
722
+ // packages/run-worker/src/read-model-backend/inbox.ts
723
+ function buildInboxResolutionSentinel(runId, payload) {
724
+ return `${RIG_INBOX_RESOLUTION_SENTINEL2} runId=${runId} data=${encodeURIComponent(JSON.stringify(payload))} requestedBy=operator ${RIG_CONTROL_SENTINEL_END3}`;
725
+ }
396
726
  function promptFromPayload(payload, fallback) {
397
727
  if (payload && typeof payload === "object") {
398
728
  const record = payload;