@h-rig/run-worker 0.0.6-alpha.156 → 0.0.6-alpha.158

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,16 +1,324 @@
1
1
  // @bun
2
- // packages/run-worker/src/runs/projection.ts
2
+ // packages/run-worker/src/read-model-backend/projection.ts
3
3
  import { existsSync, readFileSync } from "fs";
4
4
  import { isAbsolute, relative, resolve } from "path";
5
- import { Duration, Effect, Option, Stream } from "effect";
5
+ import { RUN_DISCOVERY } from "@rig/contracts";
6
+
7
+ // packages/run-worker/src/session-journal.ts
8
+ import { Schema } from "effect";
6
9
  import {
7
- foldRunSessionEntries,
8
- isTerminalRunStatus,
9
- timelineEntriesFromCustomEntries
10
+ CUSTOM_TYPE_FOR,
11
+ RIG_CONTROL_SENTINEL_END,
12
+ RIG_INBOX_RESOLUTION_SENTINEL,
13
+ RIG_PAUSE_SENTINEL,
14
+ RIG_RESUME_SENTINEL,
15
+ RIG_STOP_SENTINEL,
16
+ RIG_STOP_SENTINEL_END,
17
+ RIG_WORKFLOW_STATUS_CHANGED,
18
+ RunJournalEvent,
19
+ TYPE_FOR_CUSTOM
10
20
  } from "@rig/contracts";
11
- import { registrySnapshotStream } from "@rig/runtime/control-plane/discovery";
21
+ var decodeRunJournalEvent = Schema.decodeUnknownSync(RunJournalEvent);
22
+ var RUN_STATUS_TRANSITIONS = {
23
+ created: ["queued", "preparing", "running", "failed", "stopped"],
24
+ queued: ["preparing", "running", "failed", "stopped"],
25
+ preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
26
+ running: [
27
+ "queued",
28
+ "waiting-approval",
29
+ "waiting-user-input",
30
+ "paused",
31
+ "validating",
32
+ "reviewing",
33
+ "closing-out",
34
+ "needs-attention",
35
+ "completed",
36
+ "failed",
37
+ "stopped"
38
+ ],
39
+ "waiting-approval": ["running", "needs-attention", "failed", "stopped"],
40
+ "waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
41
+ paused: ["running", "failed", "stopped"],
42
+ validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
43
+ reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
44
+ "closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
45
+ "needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
46
+ completed: [],
47
+ failed: ["queued", "preparing", "running", "closing-out"],
48
+ stopped: ["queued", "preparing", "running", "closing-out"]
49
+ };
50
+ var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
51
+ function isTerminalRunStatus(status) {
52
+ return TERMINAL_RUN_STATUSES.includes(status);
53
+ }
54
+ function canTransitionRunStatus(from, to) {
55
+ if (from === null)
56
+ return true;
57
+ if (from === to)
58
+ return true;
59
+ return RUN_STATUS_TRANSITIONS[from].includes(to);
60
+ }
61
+ function assertRunStatusTransition(from, to) {
62
+ if (!canTransitionRunStatus(from, to)) {
63
+ throw new Error(`Illegal run status transition: ${from ?? "(none)"} -> ${to}. ` + `Allowed from ${from ?? "(none)"}: ${from ? RUN_STATUS_TRANSITIONS[from].join(", ") : "(any)"}.`);
64
+ }
65
+ }
66
+ function reduceRunJournal(events, runId = null) {
67
+ let record = {};
68
+ let status = null;
69
+ const statusHistory = [];
70
+ const pendingApprovals = new Map;
71
+ const resolvedApprovals = [];
72
+ const pendingUserInputs = new Map;
73
+ const resolvedUserInputs = [];
74
+ const closeoutPhases = [];
75
+ let resolvedPipeline = null;
76
+ const stageOutcomes = [];
77
+ const anomalies = [];
78
+ let steeringCount = 0;
79
+ let stallCount = 0;
80
+ let lastSeq = 0;
81
+ let lastEventAt = null;
82
+ const projectedRunId = runId ?? events[0]?.runId ?? null;
83
+ for (const event of events) {
84
+ lastSeq = event.seq;
85
+ lastEventAt = event.at;
86
+ switch (event.type) {
87
+ case "status-changed": {
88
+ if (!canTransitionRunStatus(status, event.to)) {
89
+ anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
90
+ }
91
+ statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
92
+ const wasTerminal = status !== null && isTerminalRunStatus(status);
93
+ status = event.to;
94
+ record = { ...record, updatedAt: event.at };
95
+ if (isTerminalRunStatus(event.to) && !record.completedAt)
96
+ record = { ...record, completedAt: event.at };
97
+ if (!isTerminalRunStatus(event.to) && wasTerminal)
98
+ record = { ...record, completedAt: null };
99
+ if (event.to === "running" && !record.startedAt)
100
+ record = { ...record, startedAt: event.at };
101
+ break;
102
+ }
103
+ case "record-patch": {
104
+ const next = { ...record };
105
+ for (const [key, value] of Object.entries(event.patch)) {
106
+ if (value !== undefined)
107
+ next[key] = value;
108
+ }
109
+ next.updatedAt = event.patch.updatedAt ?? event.at;
110
+ record = next;
111
+ break;
112
+ }
113
+ case "approval-requested": {
114
+ pendingApprovals.set(event.requestId, {
115
+ requestId: event.requestId,
116
+ requestKind: event.requestKind,
117
+ actionId: event.actionId ?? null,
118
+ payload: event.payload,
119
+ requestedAt: event.at
120
+ });
121
+ break;
122
+ }
123
+ case "approval-resolved": {
124
+ const pending = pendingApprovals.get(event.requestId);
125
+ if (!pending) {
126
+ const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
127
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
128
+ break;
129
+ }
130
+ pendingApprovals.delete(event.requestId);
131
+ resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
132
+ break;
133
+ }
134
+ case "input-requested": {
135
+ pendingUserInputs.set(event.requestId, {
136
+ requestId: event.requestId,
137
+ requestKind: "user-input",
138
+ actionId: null,
139
+ payload: event.payload,
140
+ requestedAt: event.at
141
+ });
142
+ break;
143
+ }
144
+ case "input-resolved": {
145
+ const pending = pendingUserInputs.get(event.requestId);
146
+ if (!pending) {
147
+ const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
148
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
149
+ break;
150
+ }
151
+ pendingUserInputs.delete(event.requestId);
152
+ resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
153
+ break;
154
+ }
155
+ case "steering":
156
+ steeringCount += 1;
157
+ break;
158
+ case "adopted":
159
+ record = { ...record, pid: event.pid, updatedAt: event.at };
160
+ break;
161
+ case "stall-detected":
162
+ stallCount += 1;
163
+ break;
164
+ case "closeout-phase":
165
+ closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
166
+ break;
167
+ case "pipeline-resolved":
168
+ resolvedPipeline = event.pipeline;
169
+ break;
170
+ case "stage-outcome":
171
+ stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
172
+ break;
173
+ case "timeline-entry":
174
+ case "log-entry":
175
+ break;
176
+ }
177
+ }
178
+ return {
179
+ runId: projectedRunId,
180
+ record,
181
+ status,
182
+ statusHistory,
183
+ pendingApprovals: [...pendingApprovals.values()],
184
+ resolvedApprovals,
185
+ pendingUserInputs: [...pendingUserInputs.values()],
186
+ resolvedUserInputs,
187
+ steeringCount,
188
+ stallCount,
189
+ closeoutPhases,
190
+ resolvedPipeline,
191
+ stageOutcomes,
192
+ lastSeq,
193
+ lastEventAt,
194
+ anomalies
195
+ };
196
+ }
197
+ function isRunSessionCustomType(customType) {
198
+ return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
199
+ }
200
+ function foldRunSessionEntries(entries, runId) {
201
+ const events = [];
202
+ entries.forEach((entry, index) => {
203
+ if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
204
+ return;
205
+ const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
206
+ const stamped = {
207
+ v: 1,
208
+ seq: index + 1,
209
+ at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
210
+ runId,
211
+ ...data,
212
+ type: TYPE_FOR_CUSTOM[entry.customType]
213
+ };
214
+ try {
215
+ events.push(decodeRunJournalEvent(stamped));
216
+ } catch {}
217
+ });
218
+ return reduceRunJournal(events, runId);
219
+ }
220
+ function isRecord(value) {
221
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
222
+ }
223
+ function timelineEntryFromCustomEntry(entry) {
224
+ if (entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
225
+ return null;
226
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
227
+ const type = typeof payload.type === "string" ? payload.type : "timeline";
228
+ const stage = typeof payload.stage === "string" ? payload.stage : typeof payload.name === "string" ? payload.name : null;
229
+ const status = typeof payload.status === "string" ? payload.status : typeof payload.outcome === "string" ? payload.outcome : null;
230
+ const detail = typeof payload.detail === "string" ? payload.detail : typeof payload.message === "string" ? payload.message : null;
231
+ const at = typeof entry.data.at === "string" ? entry.data.at : typeof payload.at === "string" ? payload.at : null;
232
+ return { at, type, stage, status, detail };
233
+ }
234
+ function timelineEntriesFromCustomEntries(entries) {
235
+ return entries.flatMap((entry) => {
236
+ const timelineEntry = timelineEntryFromCustomEntry(entry);
237
+ return timelineEntry ? [timelineEntry] : [];
238
+ });
239
+ }
240
+ function asCustomEntries(entries) {
241
+ return entries.filter((entry) => entry.type === "custom");
242
+ }
243
+
244
+ class RunSessionJournal {
245
+ sm;
246
+ runId;
247
+ constructor(sm, runId) {
248
+ this.sm = sm;
249
+ this.runId = runId;
250
+ }
251
+ #append(init) {
252
+ this.sm.appendCustomEntry(CUSTOM_TYPE_FOR[init.type], { ...init, at: new Date().toISOString() });
253
+ }
254
+ appendStatus(to, opts = {}) {
255
+ const from = foldRunSessionEntries(asCustomEntries(this.sm.getEntries()), this.runId).status;
256
+ if (!opts.force)
257
+ assertRunStatusTransition(from, to);
258
+ if (opts.errorText !== undefined)
259
+ this.#append({ type: "record-patch", patch: { errorText: opts.errorText } });
260
+ this.#append({
261
+ type: "status-changed",
262
+ from,
263
+ to,
264
+ ...opts.reason !== undefined ? { reason: opts.reason } : {},
265
+ ...opts.actor !== undefined ? { actor: opts.actor } : {}
266
+ });
267
+ }
268
+ appendTimeline(payload) {
269
+ this.#append({ type: "timeline-entry", payload });
270
+ }
271
+ appendCloseoutPhase(input) {
272
+ this.#append({
273
+ type: "closeout-phase",
274
+ phase: input.phase,
275
+ outcome: input.outcome,
276
+ ...input.detail !== undefined ? { detail: input.detail } : {}
277
+ });
278
+ }
279
+ appendApprovalResolved(input) {
280
+ this.#append({
281
+ type: "approval-resolved",
282
+ requestId: input.requestId,
283
+ decision: input.decision,
284
+ actor: input.actor,
285
+ ...input.note !== undefined ? { note: input.note } : {}
286
+ });
287
+ }
288
+ appendInputResolved(input) {
289
+ this.#append({ type: "input-resolved", requestId: input.requestId, answers: input.answers, actor: input.actor });
290
+ }
291
+ appendStall(input) {
292
+ this.#append({ type: "stall-detected", detail: input.detail });
293
+ }
294
+ }
295
+
296
+ // packages/run-worker/src/read-model-backend/projection.ts
297
+ import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
298
+ import { defineCapability } from "@rig/core/capability";
299
+
300
+ // packages/run-worker/src/read-model-backend/run-status.ts
301
+ import { OPERATOR_INACTIVE_RUN_STATUSES } from "@rig/contracts";
302
+ var TERMINAL_RUN_STATUSES2 = ["completed", "failed", "stopped"];
303
+ var ACTIVE_RUN_STATUSES = [
304
+ "created",
305
+ "queued",
306
+ "preparing",
307
+ "running",
308
+ "waiting-approval",
309
+ "waiting-user-input",
310
+ "paused",
311
+ "validating",
312
+ "reviewing",
313
+ "closing-out",
314
+ "needs-attention"
315
+ ];
316
+ var KNOWN_RUN_STATUS = Object.fromEntries([...ACTIVE_RUN_STATUSES, ...TERMINAL_RUN_STATUSES2].map((status) => [status, true]));
317
+ function asRunStatus(status) {
318
+ return KNOWN_RUN_STATUS[status] ? status : null;
319
+ }
12
320
 
13
- // packages/run-worker/src/runs/diagnostics.ts
321
+ // packages/run-worker/src/read-model-backend/diagnostics.ts
14
322
  function normalizeString(value) {
15
323
  if (typeof value !== "string")
16
324
  return null;
@@ -58,7 +366,27 @@ function summarizeRunError(projection) {
58
366
  return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
59
367
  }
60
368
 
61
- // packages/run-worker/src/runs/projection.ts
369
+ // packages/run-worker/src/read-model-backend/projection.ts
370
+ var RunDiscoveryCap = defineCapability(RUN_DISCOVERY);
371
+ function registryEntryFromCollab(collab) {
372
+ const record = collab;
373
+ return {
374
+ roomId: collab.sessionId,
375
+ title: collab.title,
376
+ status: record.registryStatus ?? (collab.stale ? "stale" : "running"),
377
+ startedAt: collab.startedAt ?? null,
378
+ heartbeatAt: collab.updatedAt ?? null,
379
+ sessionPath: collab.sessionPath ?? null,
380
+ cwd: collab.cwd ?? null,
381
+ joinLink: collab.joinLink ?? null,
382
+ webLink: collab.webLink ?? null,
383
+ relayUrl: collab.relayUrl ?? null,
384
+ stale: collab.stale,
385
+ repo: collab.selectedRepo ?? null,
386
+ ...collab.pid === undefined ? {} : { pid: collab.pid },
387
+ projection: record.registryProjection ?? null
388
+ };
389
+ }
62
390
  var EMPTY_PROJECTION = foldRunSessionEntries([], "");
63
391
  var DISCOVERY_DIAGNOSTIC_RUN_ID = "__registry_discovery_error__";
64
392
  function readSessionRunEntries(sessionPath) {
@@ -105,22 +433,7 @@ function registryStatusAsRunStatus(status) {
105
433
  return "waiting-user-input";
106
434
  if (status === "starting")
107
435
  return "preparing";
108
- return typeof status === "string" && [
109
- "created",
110
- "queued",
111
- "preparing",
112
- "running",
113
- "waiting-approval",
114
- "waiting-user-input",
115
- "paused",
116
- "validating",
117
- "reviewing",
118
- "closing-out",
119
- "needs-attention",
120
- "completed",
121
- "failed",
122
- "stopped"
123
- ].includes(status) ? status : null;
436
+ return typeof status === "string" ? asRunStatus(status) : null;
124
437
  }
125
438
  function payloadString(payload, keys) {
126
439
  if (!payload || typeof payload !== "object")
@@ -143,12 +456,14 @@ function payloadOptions(payload) {
143
456
  }
144
457
  function inboxRequest(request, kind) {
145
458
  const fallback = kind === "approval" ? "Approval requested" : "Input requested";
459
+ const body = payloadString(request.payload, ["body", "description", "detail", "details"]);
460
+ const options = payloadOptions(request.payload);
146
461
  return {
147
462
  requestId: request.requestId,
148
463
  kind,
149
464
  title: payloadString(request.payload, ["title", "message", "reason", "prompt", "summary"]) ?? fallback,
150
- body: payloadString(request.payload, ["body", "description", "detail", "details"]),
151
- ...payloadOptions(request.payload) ? { options: payloadOptions(request.payload) } : {},
465
+ ...body !== undefined ? { body } : {},
466
+ ...options ? { options } : {},
152
467
  requestedAt: request.requestedAt ?? null,
153
468
  source: "run"
154
469
  };
@@ -257,8 +572,8 @@ function runRecordFromRegistryEntry(projectRoot, entry) {
257
572
  projection: folded
258
573
  };
259
574
  }
260
- function runRecordsFromRegistrySnapshot(projectRoot, snapshot) {
261
- return sortByRecency(snapshot.entries.map((entry) => runRecordFromRegistryEntry(projectRoot, entry)).filter((record) => record !== null));
575
+ function runRecordsFromCollab(projectRoot, collabs) {
576
+ return sortByRecency(collabs.map((collab) => runRecordFromRegistryEntry(projectRoot, registryEntryFromCollab(collab))).filter((record) => record !== null));
262
577
  }
263
578
  function sortByRecency(records) {
264
579
  return [...records].sort((a, b) => {
@@ -306,8 +621,11 @@ function discoveryDiagnosticRunRecord(projectRoot, error) {
306
621
  }
307
622
  async function listRunProjections(projectRoot, filter = {}) {
308
623
  try {
309
- const snapshot = await Effect.runPromise(registrySnapshotStream(projectRoot, filter).pipe(Stream.runHead, Effect.timeout(Duration.seconds(15)), Effect.map(Option.getOrNull)));
310
- return snapshot ? runRecordsFromRegistrySnapshot(projectRoot, snapshot) : [discoveryDiagnosticRunRecord(projectRoot, "registry discovery stream ended without a snapshot")];
624
+ const discovery = await loadCapabilityForRoot(projectRoot, RunDiscoveryCap);
625
+ if (!discovery)
626
+ return [discoveryDiagnosticRunRecord(projectRoot, "run discovery capability unavailable")];
627
+ const collabs = await discovery.listActiveRunCollab(projectRoot, filter);
628
+ return runRecordsFromCollab(projectRoot, collabs);
311
629
  } catch (error) {
312
630
  return [discoveryDiagnosticRunRecord(projectRoot, error)];
313
631
  }
@@ -343,7 +661,7 @@ var getRun = getRunProjection;
343
661
  var resolveJoinTarget = resolveRunJoinTarget;
344
662
  export {
345
663
  selectRunProjection,
346
- runRecordsFromRegistrySnapshot,
664
+ runRecordsFromCollab,
347
665
  runRecordFromRegistryEntry,
348
666
  resolveRunJoinTarget,
349
667
  resolveJoinTarget,
@@ -1,5 +1,6 @@
1
- import type { RunRecord } from "./projection";
1
+ import { type RunRecord, type RunStatus } from "@rig/contracts";
2
2
  export type RunClassificationPhase = "needs-attention" | "starting" | "active" | "waiting" | "paused" | "completed" | "failed" | "stopped" | "unknown";
3
+ export type RunStatusColorRole = "success" | "action-yellow" | "active-cyan" | "failure" | "muted" | "neutral";
3
4
  export interface RunClassification {
4
5
  readonly status: string;
5
6
  readonly phase: RunClassificationPhase;
@@ -7,12 +8,14 @@ export interface RunClassification {
7
8
  readonly isTerminal: boolean;
8
9
  readonly isNeedsAttention: boolean;
9
10
  }
10
- export type RunStatusColorRole = "success" | "action-yellow" | "active-cyan" | "failure" | "muted" | "neutral";
11
+ export declare function normalizeRunStatusToken(status: unknown): string;
12
+ export declare function isOperatorActiveRunStatus(status: unknown): boolean;
13
+ export declare function asRunStatus(status: string): RunStatus | null;
11
14
  export declare function statusColorRole(status: unknown): RunStatusColorRole;
12
- export declare function runStatusColorRole(run: RunRecord): RunStatusColorRole;
13
15
  export declare function isNeedsAttention(run: RunRecord): boolean;
14
16
  export declare function classifyRun(run: RunRecord): RunClassification;
15
17
  export declare function runStatusText(run: RunRecord): string;
18
+ export declare function runStatusColorRole(run: RunRecord): RunStatusColorRole;
16
19
  export declare function statusRank(run: RunRecord): number;
17
20
  export declare function canSteer(run: RunRecord): boolean;
18
21
  export declare function canStop(run: RunRecord): boolean;
@@ -1,13 +1,36 @@
1
1
  // @bun
2
- // packages/run-worker/src/runs/run-status.ts
3
- import {
4
- ACTIVE_RUN_STATUSES,
5
- TERMINAL_RUN_STATUSES,
6
- isActiveRunStatus,
7
- isTerminalRunStatus,
8
- normalizeRunStatusToken
9
- } from "@rig/contracts";
2
+ // packages/run-worker/src/read-model-backend/run-status.ts
3
+ import { OPERATOR_INACTIVE_RUN_STATUSES } from "@rig/contracts";
4
+ var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
5
+ var ACTIVE_RUN_STATUSES = [
6
+ "created",
7
+ "queued",
8
+ "preparing",
9
+ "running",
10
+ "waiting-approval",
11
+ "waiting-user-input",
12
+ "paused",
13
+ "validating",
14
+ "reviewing",
15
+ "closing-out",
16
+ "needs-attention"
17
+ ];
10
18
  var KNOWN_RUN_STATUS = Object.fromEntries([...ACTIVE_RUN_STATUSES, ...TERMINAL_RUN_STATUSES].map((status) => [status, true]));
19
+ function isTerminalRunStatus(status) {
20
+ return TERMINAL_RUN_STATUSES.includes(status);
21
+ }
22
+ function isActiveRunStatus(status) {
23
+ return !isTerminalRunStatus(status);
24
+ }
25
+ function normalizeRunStatusToken(status) {
26
+ return String(status ?? "").trim().toLowerCase().replace(/[\s_]+/g, "-");
27
+ }
28
+ function isOperatorActiveRunStatus(status) {
29
+ const normalized = normalizeRunStatusToken(status);
30
+ if (!normalized)
31
+ return false;
32
+ return !OPERATOR_INACTIVE_RUN_STATUSES.has(normalized);
33
+ }
11
34
  function canonicalStatusToken(status) {
12
35
  const normalized = normalizeRunStatusToken(status);
13
36
  if (normalized === "waiting-input")
@@ -27,11 +50,8 @@ function statusColorRole(status) {
27
50
  case "merged":
28
51
  return "success";
29
52
  case "needs-attention":
30
- case "needs_attention":
31
53
  case "waiting-approval":
32
54
  case "waiting-user-input":
33
- case "waiting-input":
34
- case "waiting_input":
35
55
  case "blocked":
36
56
  case "paused":
37
57
  return "action-yellow";
@@ -42,14 +62,12 @@ function statusColorRole(status) {
42
62
  case "queued":
43
63
  case "starting":
44
64
  case "pending":
45
- case "in_progress":
46
65
  case "in-progress":
47
66
  case "active":
48
67
  case "booting":
49
68
  case "validating":
50
69
  case "reviewing":
51
70
  case "closing-out":
52
- case "closing_out":
53
71
  return "active-cyan";
54
72
  case "failed":
55
73
  case "error":
@@ -64,23 +82,6 @@ function statusColorRole(status) {
64
82
  return "neutral";
65
83
  }
66
84
  }
67
- function runStatusColorRole(run) {
68
- const classification = classifyRun(run);
69
- return classification.isNeedsAttention && !classification.isTerminal ? "action-yellow" : statusColorRole(classification.status);
70
- }
71
- function isSteerableStatus(status) {
72
- switch (status) {
73
- case "needs-attention":
74
- case "waiting-approval":
75
- case "waiting-user-input":
76
- case "paused":
77
- return false;
78
- default: {
79
- const runStatus = asRunStatus(status);
80
- return runStatus ? isActiveRunStatus(runStatus) : false;
81
- }
82
- }
83
- }
84
85
  function isNeedsAttention(run) {
85
86
  return canonicalStatusToken(run.status) === "needs-attention" || run.pendingApprovals + run.pendingInputs > 0 || run.stallCount > 0;
86
87
  }
@@ -115,10 +116,9 @@ function classifyRun(run) {
115
116
  const runStatus = asRunStatus(status);
116
117
  const isTerminal = runStatus ? isTerminalRunStatus(runStatus) : false;
117
118
  const isNeedsAttentionValue = isNeedsAttention(run);
118
- const phase = phaseForStatus(status, runStatus, isNeedsAttentionValue);
119
119
  return {
120
120
  status,
121
- phase,
121
+ phase: phaseForStatus(status, runStatus, isNeedsAttentionValue),
122
122
  isActive: runStatus ? isActiveRunStatus(runStatus) : !isTerminal && status !== "unknown",
123
123
  isTerminal,
124
124
  isNeedsAttention: isNeedsAttentionValue
@@ -127,6 +127,10 @@ function classifyRun(run) {
127
127
  function runStatusText(run) {
128
128
  return classifyRun(run).status;
129
129
  }
130
+ function runStatusColorRole(run) {
131
+ const classification = classifyRun(run);
132
+ return classification.isNeedsAttention && !classification.isTerminal ? "action-yellow" : statusColorRole(classification.status);
133
+ }
130
134
  function statusRank(run) {
131
135
  const classification = classifyRun(run);
132
136
  if (classification.isNeedsAttention)
@@ -150,6 +154,19 @@ function statusRank(run) {
150
154
  return 6;
151
155
  }
152
156
  }
157
+ function isSteerableStatus(status) {
158
+ switch (status) {
159
+ case "needs-attention":
160
+ case "waiting-approval":
161
+ case "waiting-user-input":
162
+ case "paused":
163
+ return false;
164
+ default: {
165
+ const runStatus = asRunStatus(status);
166
+ return runStatus ? isActiveRunStatus(runStatus) : false;
167
+ }
168
+ }
169
+ }
153
170
  function canSteer(run) {
154
171
  const classification = classifyRun(run);
155
172
  if (classification.phase === "active" || classification.phase === "starting")
@@ -172,10 +189,13 @@ export {
172
189
  statusColorRole,
173
190
  runStatusText,
174
191
  runStatusColorRole,
192
+ normalizeRunStatusToken,
193
+ isOperatorActiveRunStatus,
175
194
  isNeedsAttention,
176
195
  classifyRun,
177
196
  canStop,
178
197
  canSteer,
179
198
  canResume,
180
- canPause
199
+ canPause,
200
+ asRunStatus
181
201
  };
@@ -10,4 +10,3 @@ export declare function computeStats(projectRootOrRuns: string | readonly RunRec
10
10
  since?: Date | null;
11
11
  listRuns?: ListRunsFn;
12
12
  }): Promise<RigStatsData>;
13
- export declare const computeRigStats: typeof computeStats;