@h-rig/run-worker 0.0.6-alpha.157 → 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
@@ -0,0 +1,60 @@
1
+ import type { ExtensionContext } from "@oh-my-pi/pi-coding-agent";
2
+ import { RIG_WORKFLOW_STATUS_CHANGED, type ApprovalDecision, type JournalCapability, type RigRunTimelineEntry, type RigWorkflowStatusChanged, type RunActor, type RunCloseoutPhase, type RunCloseoutPhaseOutcome, type RunInboxResolutionSentinel, type RunJournalEventInit, type RunJournalProjection, type RunSessionCustomEntry, type RunStatus } from "@rig/contracts";
3
+ export declare function isTerminalRunStatus(status: RunStatus): boolean;
4
+ export declare function sessionIdFromSessionFile(sessionPath: string | null | undefined): string | null;
5
+ export declare function foldRunSessionEntries(entries: ReadonlyArray<RunSessionCustomEntry>, runId: string): RunJournalProjection;
6
+ export declare function projectRunFromSession(source: {
7
+ getEntries(): ReadonlyArray<RunSessionCustomEntry>;
8
+ } | ReadonlyArray<RunSessionCustomEntry>, runId: string): RunJournalProjection;
9
+ export declare function timelineEntriesFromCustomEntries(entries: readonly RunSessionCustomEntry[]): readonly RigRunTimelineEntry[];
10
+ export declare function latestTimelineEntriesFromCustomEntries(entries: readonly RunSessionCustomEntry[], limit: number): readonly RigRunTimelineEntry[];
11
+ export declare function parseStopSentinel(text: string, expectedRunId: string): {
12
+ reason: string | null;
13
+ } | null;
14
+ export declare function parsePauseSentinel(text: string, expectedRunId: string): {
15
+ requestedBy: string | null;
16
+ } | null;
17
+ export declare function parseResumeSentinel(text: string, expectedRunId: string): {
18
+ requestedBy: string | null;
19
+ } | null;
20
+ export declare function parseInboxResolutionSentinel(text: string, expectedRunId: string): RunInboxResolutionSentinel | null;
21
+ export type JournalSessionProvider = JournalCapability & {
22
+ readonly runId: string;
23
+ readonly appendRunEvent: (event: RunJournalEventInit) => Promise<void>;
24
+ readonly readEntries: () => readonly RunSessionCustomEntry[];
25
+ readonly readProjection: () => RunJournalProjection;
26
+ };
27
+ export type RunJournal = {
28
+ readonly kernel: JournalSessionProvider;
29
+ appendStatus(to: RunStatus, opts?: {
30
+ reason?: string | null;
31
+ actor?: RunActor;
32
+ errorText?: string | null;
33
+ force?: boolean;
34
+ }): void;
35
+ appendTimeline(payload: unknown): void;
36
+ appendCloseoutPhase(input: {
37
+ phase: RunCloseoutPhase;
38
+ outcome: RunCloseoutPhaseOutcome;
39
+ detail?: string | null;
40
+ }): void;
41
+ appendApprovalResolved(input: {
42
+ requestId: string;
43
+ decision: ApprovalDecision;
44
+ note?: string | null;
45
+ actor: RunActor;
46
+ }): void;
47
+ appendInputResolved(input: {
48
+ requestId: string;
49
+ answers: Record<string, string>;
50
+ actor: RunActor;
51
+ }): void;
52
+ appendStall(input: {
53
+ detail: string;
54
+ }): void;
55
+ };
56
+ export declare function createRunJournal(sessionManager: ExtensionContext["sessionManager"], runId: string): Promise<RunJournal | null>;
57
+ export declare function createWorkflowStatusChanged(input: Omit<RigWorkflowStatusChanged, "schemaVersion" | "changedAt"> & {
58
+ changedAt?: string;
59
+ }): RigWorkflowStatusChanged;
60
+ export { RIG_WORKFLOW_STATUS_CHANGED };
@@ -0,0 +1,471 @@
1
+ // @bun
2
+ // packages/run-worker/src/session-journal.ts
3
+ import { Schema } from "effect";
4
+ import {
5
+ CUSTOM_TYPE_FOR,
6
+ RIG_CONTROL_SENTINEL_END,
7
+ RIG_INBOX_RESOLUTION_SENTINEL,
8
+ RIG_PAUSE_SENTINEL,
9
+ RIG_RESUME_SENTINEL,
10
+ RIG_STOP_SENTINEL,
11
+ RIG_STOP_SENTINEL_END,
12
+ RIG_WORKFLOW_STATUS_CHANGED,
13
+ RunJournalEvent,
14
+ TYPE_FOR_CUSTOM
15
+ } from "@rig/contracts";
16
+ var decodeRunJournalEvent = Schema.decodeUnknownSync(RunJournalEvent);
17
+ var RUN_STATUS_TRANSITIONS = {
18
+ created: ["queued", "preparing", "running", "failed", "stopped"],
19
+ queued: ["preparing", "running", "failed", "stopped"],
20
+ preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
21
+ running: [
22
+ "queued",
23
+ "waiting-approval",
24
+ "waiting-user-input",
25
+ "paused",
26
+ "validating",
27
+ "reviewing",
28
+ "closing-out",
29
+ "needs-attention",
30
+ "completed",
31
+ "failed",
32
+ "stopped"
33
+ ],
34
+ "waiting-approval": ["running", "needs-attention", "failed", "stopped"],
35
+ "waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
36
+ paused: ["running", "failed", "stopped"],
37
+ validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
38
+ reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
39
+ "closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
40
+ "needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
41
+ completed: [],
42
+ failed: ["queued", "preparing", "running", "closing-out"],
43
+ stopped: ["queued", "preparing", "running", "closing-out"]
44
+ };
45
+ var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
46
+ function isTerminalRunStatus(status) {
47
+ return TERMINAL_RUN_STATUSES.includes(status);
48
+ }
49
+ function canTransitionRunStatus(from, to) {
50
+ if (from === null)
51
+ return true;
52
+ if (from === to)
53
+ return true;
54
+ return RUN_STATUS_TRANSITIONS[from].includes(to);
55
+ }
56
+ function assertRunStatusTransition(from, to) {
57
+ if (!canTransitionRunStatus(from, to)) {
58
+ throw new Error(`Illegal run status transition: ${from ?? "(none)"} -> ${to}. ` + `Allowed from ${from ?? "(none)"}: ${from ? RUN_STATUS_TRANSITIONS[from].join(", ") : "(any)"}.`);
59
+ }
60
+ }
61
+ function reduceRunJournal(events, runId = null) {
62
+ let record = {};
63
+ let status = null;
64
+ const statusHistory = [];
65
+ const pendingApprovals = new Map;
66
+ const resolvedApprovals = [];
67
+ const pendingUserInputs = new Map;
68
+ const resolvedUserInputs = [];
69
+ const closeoutPhases = [];
70
+ let resolvedPipeline = null;
71
+ const stageOutcomes = [];
72
+ const anomalies = [];
73
+ let steeringCount = 0;
74
+ let stallCount = 0;
75
+ let lastSeq = 0;
76
+ let lastEventAt = null;
77
+ const projectedRunId = runId ?? events[0]?.runId ?? null;
78
+ for (const event of events) {
79
+ lastSeq = event.seq;
80
+ lastEventAt = event.at;
81
+ switch (event.type) {
82
+ case "status-changed": {
83
+ if (!canTransitionRunStatus(status, event.to)) {
84
+ anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
85
+ }
86
+ statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
87
+ const wasTerminal = status !== null && isTerminalRunStatus(status);
88
+ status = event.to;
89
+ record = { ...record, updatedAt: event.at };
90
+ if (isTerminalRunStatus(event.to) && !record.completedAt)
91
+ record = { ...record, completedAt: event.at };
92
+ if (!isTerminalRunStatus(event.to) && wasTerminal)
93
+ record = { ...record, completedAt: null };
94
+ if (event.to === "running" && !record.startedAt)
95
+ record = { ...record, startedAt: event.at };
96
+ break;
97
+ }
98
+ case "record-patch": {
99
+ const next = { ...record };
100
+ for (const [key, value] of Object.entries(event.patch)) {
101
+ if (value !== undefined)
102
+ next[key] = value;
103
+ }
104
+ next.updatedAt = event.patch.updatedAt ?? event.at;
105
+ record = next;
106
+ break;
107
+ }
108
+ case "approval-requested": {
109
+ pendingApprovals.set(event.requestId, {
110
+ requestId: event.requestId,
111
+ requestKind: event.requestKind,
112
+ actionId: event.actionId ?? null,
113
+ payload: event.payload,
114
+ requestedAt: event.at
115
+ });
116
+ break;
117
+ }
118
+ case "approval-resolved": {
119
+ const pending = pendingApprovals.get(event.requestId);
120
+ if (!pending) {
121
+ const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
122
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
123
+ break;
124
+ }
125
+ pendingApprovals.delete(event.requestId);
126
+ resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
127
+ break;
128
+ }
129
+ case "input-requested": {
130
+ pendingUserInputs.set(event.requestId, {
131
+ requestId: event.requestId,
132
+ requestKind: "user-input",
133
+ actionId: null,
134
+ payload: event.payload,
135
+ requestedAt: event.at
136
+ });
137
+ break;
138
+ }
139
+ case "input-resolved": {
140
+ const pending = pendingUserInputs.get(event.requestId);
141
+ if (!pending) {
142
+ const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
143
+ anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
144
+ break;
145
+ }
146
+ pendingUserInputs.delete(event.requestId);
147
+ resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
148
+ break;
149
+ }
150
+ case "steering":
151
+ steeringCount += 1;
152
+ break;
153
+ case "adopted":
154
+ record = { ...record, pid: event.pid, updatedAt: event.at };
155
+ break;
156
+ case "stall-detected":
157
+ stallCount += 1;
158
+ break;
159
+ case "closeout-phase":
160
+ closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
161
+ break;
162
+ case "pipeline-resolved":
163
+ resolvedPipeline = event.pipeline;
164
+ break;
165
+ case "stage-outcome":
166
+ stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
167
+ break;
168
+ case "timeline-entry":
169
+ case "log-entry":
170
+ break;
171
+ }
172
+ }
173
+ return {
174
+ runId: projectedRunId,
175
+ record,
176
+ status,
177
+ statusHistory,
178
+ pendingApprovals: [...pendingApprovals.values()],
179
+ resolvedApprovals,
180
+ pendingUserInputs: [...pendingUserInputs.values()],
181
+ resolvedUserInputs,
182
+ steeringCount,
183
+ stallCount,
184
+ closeoutPhases,
185
+ resolvedPipeline,
186
+ stageOutcomes,
187
+ lastSeq,
188
+ lastEventAt,
189
+ anomalies
190
+ };
191
+ }
192
+ function sessionIdFromSessionFile(sessionPath) {
193
+ if (!sessionPath)
194
+ return null;
195
+ const file = sessionPath.split(/[\\/]/).pop() ?? "";
196
+ const match = file.match(/_([0-9a-fA-F][0-9a-fA-F-]{7,})\.jsonl$/);
197
+ return match?.[1] ?? null;
198
+ }
199
+ function isRunSessionCustomType(customType) {
200
+ return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
201
+ }
202
+ function foldRunSessionEntries(entries, runId) {
203
+ const events = [];
204
+ entries.forEach((entry, index) => {
205
+ if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
206
+ return;
207
+ const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
208
+ const stamped = {
209
+ v: 1,
210
+ seq: index + 1,
211
+ at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
212
+ runId,
213
+ ...data,
214
+ type: TYPE_FOR_CUSTOM[entry.customType]
215
+ };
216
+ try {
217
+ events.push(decodeRunJournalEvent(stamped));
218
+ } catch {}
219
+ });
220
+ return reduceRunJournal(events, runId);
221
+ }
222
+ function projectRunFromSession(source, runId) {
223
+ const entries = "getEntries" in source ? source.getEntries() : source;
224
+ return foldRunSessionEntries(entries, runId);
225
+ }
226
+ function isRecord(value) {
227
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
228
+ }
229
+ function timelineEntryFromCustomEntry(entry) {
230
+ if (entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
231
+ return null;
232
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
233
+ const type = typeof payload.type === "string" ? payload.type : "timeline";
234
+ const stage = typeof payload.stage === "string" ? payload.stage : typeof payload.name === "string" ? payload.name : null;
235
+ const status = typeof payload.status === "string" ? payload.status : typeof payload.outcome === "string" ? payload.outcome : null;
236
+ const detail = typeof payload.detail === "string" ? payload.detail : typeof payload.message === "string" ? payload.message : null;
237
+ const at = typeof entry.data.at === "string" ? entry.data.at : typeof payload.at === "string" ? payload.at : null;
238
+ return { at, type, stage, status, detail };
239
+ }
240
+ function timelineEntriesFromCustomEntries(entries) {
241
+ return entries.flatMap((entry) => {
242
+ const timelineEntry = timelineEntryFromCustomEntry(entry);
243
+ return timelineEntry ? [timelineEntry] : [];
244
+ });
245
+ }
246
+ function latestTimelineEntriesFromCustomEntries(entries, limit) {
247
+ if (limit <= 0)
248
+ return [];
249
+ const timeline = [];
250
+ for (let index = entries.length - 1;index >= 0 && timeline.length < limit; index -= 1) {
251
+ const timelineEntry = timelineEntryFromCustomEntry(entries[index]);
252
+ if (timelineEntry)
253
+ timeline.push(timelineEntry);
254
+ }
255
+ timeline.reverse();
256
+ return timeline;
257
+ }
258
+ function parseStopSentinel(text, expectedRunId) {
259
+ const start = text.indexOf(RIG_STOP_SENTINEL);
260
+ if (start < 0)
261
+ return null;
262
+ const end = text.indexOf(RIG_STOP_SENTINEL_END, start);
263
+ const body = text.slice(start + RIG_STOP_SENTINEL.length, end >= 0 ? end : undefined).trim();
264
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
265
+ if (runId && runId !== expectedRunId)
266
+ return null;
267
+ const reason = body.match(/(?:^|\s)reason=(.+)$/)?.[1]?.trim() ?? null;
268
+ return { reason };
269
+ }
270
+ function parseControlSentinel(marker, text, expectedRunId) {
271
+ const start = text.indexOf(marker);
272
+ if (start < 0)
273
+ return null;
274
+ const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
275
+ const body = text.slice(start + marker.length, end >= 0 ? end : undefined).trim();
276
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
277
+ if (runId && runId !== expectedRunId)
278
+ return null;
279
+ const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
280
+ return { requestedBy };
281
+ }
282
+ function parsePauseSentinel(text, expectedRunId) {
283
+ return parseControlSentinel(RIG_PAUSE_SENTINEL, text, expectedRunId);
284
+ }
285
+ function parseResumeSentinel(text, expectedRunId) {
286
+ return parseControlSentinel(RIG_RESUME_SENTINEL, text, expectedRunId);
287
+ }
288
+ function decodeInboxResolutionPayload(payload) {
289
+ try {
290
+ const parsed = JSON.parse(decodeURIComponent(payload));
291
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
292
+ return null;
293
+ const record = parsed;
294
+ if (record.kind === "approval") {
295
+ const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
296
+ const decision = record.decision === "approve" || record.decision === "reject" ? record.decision : null;
297
+ const note = typeof record.note === "string" ? record.note : record.note === null ? null : undefined;
298
+ if (!requestId || !decision)
299
+ return null;
300
+ return { kind: "approval", requestId, decision, ...note !== undefined ? { note } : {} };
301
+ }
302
+ if (record.kind === "input") {
303
+ const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
304
+ const answers = record.answers && typeof record.answers === "object" && !Array.isArray(record.answers) ? Object.fromEntries(Object.entries(record.answers).filter((entry) => typeof entry[1] === "string")) : null;
305
+ if (!requestId || !answers)
306
+ return null;
307
+ return { kind: "input", requestId, answers };
308
+ }
309
+ } catch {
310
+ return null;
311
+ }
312
+ return null;
313
+ }
314
+ function parseInboxResolutionSentinel(text, expectedRunId) {
315
+ const start = text.indexOf(RIG_INBOX_RESOLUTION_SENTINEL);
316
+ if (start < 0)
317
+ return null;
318
+ const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
319
+ const body = text.slice(start + RIG_INBOX_RESOLUTION_SENTINEL.length, end >= 0 ? end : undefined).trim();
320
+ const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
321
+ if (runId && runId !== expectedRunId)
322
+ return null;
323
+ const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
324
+ const payload = body.match(/(?:^|\s)data=([^\s>]+)/)?.[1] ?? "";
325
+ const decoded = decodeInboxResolutionPayload(payload);
326
+ if (!decoded)
327
+ return null;
328
+ return { ...decoded, requestedBy };
329
+ }
330
+ function asCustomEntries(entries) {
331
+ return entries.filter((entry) => entry.type === "custom");
332
+ }
333
+
334
+ class RunSessionJournal {
335
+ sm;
336
+ runId;
337
+ constructor(sm, runId) {
338
+ this.sm = sm;
339
+ this.runId = runId;
340
+ }
341
+ #append(init) {
342
+ this.sm.appendCustomEntry(CUSTOM_TYPE_FOR[init.type], { ...init, at: new Date().toISOString() });
343
+ }
344
+ appendStatus(to, opts = {}) {
345
+ const from = foldRunSessionEntries(asCustomEntries(this.sm.getEntries()), this.runId).status;
346
+ if (!opts.force)
347
+ assertRunStatusTransition(from, to);
348
+ if (opts.errorText !== undefined)
349
+ this.#append({ type: "record-patch", patch: { errorText: opts.errorText } });
350
+ this.#append({
351
+ type: "status-changed",
352
+ from,
353
+ to,
354
+ ...opts.reason !== undefined ? { reason: opts.reason } : {},
355
+ ...opts.actor !== undefined ? { actor: opts.actor } : {}
356
+ });
357
+ }
358
+ appendTimeline(payload) {
359
+ this.#append({ type: "timeline-entry", payload });
360
+ }
361
+ appendCloseoutPhase(input) {
362
+ this.#append({
363
+ type: "closeout-phase",
364
+ phase: input.phase,
365
+ outcome: input.outcome,
366
+ ...input.detail !== undefined ? { detail: input.detail } : {}
367
+ });
368
+ }
369
+ appendApprovalResolved(input) {
370
+ this.#append({
371
+ type: "approval-resolved",
372
+ requestId: input.requestId,
373
+ decision: input.decision,
374
+ actor: input.actor,
375
+ ...input.note !== undefined ? { note: input.note } : {}
376
+ });
377
+ }
378
+ appendInputResolved(input) {
379
+ this.#append({ type: "input-resolved", requestId: input.requestId, answers: input.answers, actor: input.actor });
380
+ }
381
+ appendStall(input) {
382
+ this.#append({ type: "stall-detected", detail: input.detail });
383
+ }
384
+ }
385
+ function isRunJournalEventInit(event) {
386
+ if (event === null || typeof event !== "object")
387
+ return false;
388
+ const type = event.type;
389
+ return typeof type === "string" && Object.hasOwn(CUSTOM_TYPE_FOR, type);
390
+ }
391
+ function createJournalSessionProvider(options) {
392
+ const now = options.now ?? (() => new Date);
393
+ const appendRunEvent = async (event, runId = options.runId) => {
394
+ options.store.appendCustomEntry(CUSTOM_TYPE_FOR[event.type], { ...event, runId, at: now().toISOString() });
395
+ };
396
+ const readEntries = () => asCustomEntries(options.store.getEntries());
397
+ const entriesForRun = (runId) => readEntries().filter((entry) => {
398
+ const data = entry.data;
399
+ if (data === null || typeof data !== "object")
400
+ return runId === options.runId;
401
+ const candidate = data.runId;
402
+ return candidate === undefined ? runId === options.runId : candidate === runId;
403
+ });
404
+ return {
405
+ runId: options.runId,
406
+ async append(event) {
407
+ if (isRunJournalEventInit(event))
408
+ await appendRunEvent(event);
409
+ },
410
+ appendRunEvent,
411
+ async recordPipeline(runId, pipeline) {
412
+ await appendRunEvent({ type: "pipeline-resolved", pipeline }, runId);
413
+ },
414
+ async recordStageOutcome(runId, outcome) {
415
+ await appendRunEvent({ type: "stage-outcome", outcome }, runId);
416
+ },
417
+ async read(runId) {
418
+ return entriesForRun(runId);
419
+ },
420
+ readEntries,
421
+ readProjection: () => foldRunSessionEntries(entriesForRun(options.runId), options.runId)
422
+ };
423
+ }
424
+ async function createRunJournal(sessionManager, runId) {
425
+ try {
426
+ const writableSessionManager = sessionManager;
427
+ const journal = new RunSessionJournal(writableSessionManager, runId);
428
+ const kernel = createJournalSessionProvider({
429
+ runId,
430
+ store: {
431
+ appendCustomEntry: (customType, data) => writableSessionManager.appendCustomEntry(customType, data),
432
+ getEntries: () => sessionManager.getEntries?.() ?? sessionManager.getBranch?.() ?? []
433
+ }
434
+ });
435
+ return {
436
+ kernel,
437
+ appendStatus: journal.appendStatus.bind(journal),
438
+ appendTimeline: journal.appendTimeline.bind(journal),
439
+ appendCloseoutPhase: journal.appendCloseoutPhase.bind(journal),
440
+ appendApprovalResolved: journal.appendApprovalResolved.bind(journal),
441
+ appendInputResolved: journal.appendInputResolved.bind(journal),
442
+ appendStall: journal.appendStall.bind(journal)
443
+ };
444
+ } catch (error) {
445
+ console.warn(`[rig-run] RunSessionJournal unavailable; run-state arming deferred: ${error instanceof Error ? error.message : String(error)}`);
446
+ return null;
447
+ }
448
+ }
449
+ function createWorkflowStatusChanged(input) {
450
+ return {
451
+ schemaVersion: 1,
452
+ status: input.status,
453
+ ...input.detail ? { detail: input.detail } : {},
454
+ changedAt: input.changedAt ?? new Date().toISOString()
455
+ };
456
+ }
457
+ export {
458
+ timelineEntriesFromCustomEntries,
459
+ sessionIdFromSessionFile,
460
+ projectRunFromSession,
461
+ parseStopSentinel,
462
+ parseResumeSentinel,
463
+ parsePauseSentinel,
464
+ parseInboxResolutionSentinel,
465
+ latestTimelineEntriesFromCustomEntries,
466
+ isTerminalRunStatus,
467
+ foldRunSessionEntries,
468
+ createWorkflowStatusChanged,
469
+ createRunJournal,
470
+ RIG_WORKFLOW_STATUS_CHANGED
471
+ };
@@ -1,16 +1,21 @@
1
- import type { RunJournal } from "./journal";
1
+ type StallJournal = {
2
+ appendStall(input: {
3
+ detail: string;
4
+ }): void;
5
+ };
2
6
  export declare function timestampMs(value: number | string | Date | null | undefined): number | null;
3
7
  export declare function computeRunStall(input: {
4
8
  readonly lastActivityAt: number | string | Date | null | undefined;
5
9
  readonly now: number | string | Date;
6
10
  readonly thresholdMs: number;
7
11
  }): boolean;
8
- export declare function appendRunStallDetected(journal: RunJournal | null | undefined, detail?: string): boolean;
12
+ export declare function appendRunStallDetected(journal: StallJournal | null | undefined, detail?: string): boolean;
9
13
  export declare function startRunProcessStallMonitor(opts: {
10
- readonly journal: RunJournal | null | undefined;
14
+ readonly journal: StallJournal | null | undefined;
11
15
  readonly lastActivityAt: () => number;
12
16
  readonly alreadyStalled?: boolean;
13
17
  readonly thresholdMs?: number;
14
18
  readonly intervalMs?: number;
15
19
  readonly now?: () => number;
16
20
  }): () => void;
21
+ export {};
package/dist/src/stall.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // @bun
2
2
  // packages/run-worker/src/constants.ts
3
- var RUN_PROCESS_STEER_TIMEOUT_MS = 10 * 60 * 1000;
4
3
  var TRACKED_RUN_STALL_MS = 20 * 60 * 1000;
5
4
  var RUN_PROCESS_STALL_SWEEP_MS = 60 * 1000;
6
5
  var RUN_PROCESS_STALL_DETAIL = "Run process made no OMP session progress for 20+ minutes; recording a stall so recovery can requeue or resume the session.";