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