@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.
- package/dist/src/autohost.d.ts +8 -10
- package/dist/src/autohost.js +683 -95
- package/dist/src/constants.d.ts +0 -1
- package/dist/src/constants.js +0 -2
- package/dist/src/extension.js +683 -95
- package/dist/src/host-kernel.d.ts +22 -0
- package/dist/src/host-kernel.js +78 -0
- package/dist/src/host.d.ts +2 -0
- package/dist/src/host.js +419 -0
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.js +1913 -133
- package/dist/src/local-run-changes.d.ts +3 -0
- package/dist/src/local-run-changes.js +65 -0
- package/dist/src/notifications.js +13 -5
- package/dist/src/notify-cap.d.ts +11 -0
- package/dist/src/notify-cap.js +13 -0
- package/dist/src/panel-plugin.js +3 -7
- package/dist/src/plugin.d.ts +0 -11
- package/dist/src/plugin.js +1910 -101
- package/dist/src/{runs → read-model-backend}/control.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/control.js +361 -38
- package/dist/src/{runs → read-model-backend}/diagnostics.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/diagnostics.js +1 -3
- package/dist/src/{runs → read-model-backend}/guard.js +2 -3
- package/dist/src/{runs → read-model-backend}/inbox.js +366 -36
- package/dist/src/{runs → read-model-backend}/index.js +552 -223
- package/dist/src/{runs → read-model-backend}/inspect.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/inspect.js +350 -34
- package/dist/src/{runs → read-model-backend}/projection.d.ts +21 -7
- package/dist/src/{runs → read-model-backend}/projection.js +349 -31
- package/dist/src/{runs → read-model-backend}/run-status.d.ts +6 -3
- package/dist/src/{runs → read-model-backend}/run-status.js +53 -33
- package/dist/src/{runs → read-model-backend}/stats.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/stats.js +373 -58
- package/dist/src/read-model-service.d.ts +2 -0
- package/dist/src/read-model-service.js +1433 -0
- package/dist/src/session-journal.d.ts +60 -0
- package/dist/src/session-journal.js +471 -0
- package/dist/src/stall.d.ts +8 -3
- package/dist/src/stall.js +0 -1
- package/dist/src/utils.js +282 -3
- package/package.json +9 -12
- package/dist/src/journal.d.ts +0 -33
- package/dist/src/journal.js +0 -31
- /package/dist/src/{runs → read-model-backend}/guard.d.ts +0 -0
- /package/dist/src/{runs → read-model-backend}/inbox.d.ts +0 -0
- /package/dist/src/{runs → read-model-backend}/index.d.ts +0 -0
|
@@ -1,16 +1,324 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// packages/run-worker/src/
|
|
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 {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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
|
|
151
|
-
...
|
|
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
|
|
261
|
-
return sortByRecency(
|
|
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
|
|
310
|
-
|
|
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
|
-
|
|
664
|
+
runRecordsFromCollab,
|
|
347
665
|
runRecordFromRegistryEntry,
|
|
348
666
|
resolveRunJoinTarget,
|
|
349
667
|
resolveJoinTarget,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
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
|
|
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/
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
};
|