@h-rig/supervisor-plugin 0.0.6-alpha.154 → 0.0.6-alpha.156

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/index.js CHANGED
@@ -81,18 +81,329 @@ function createDefaultSupervisorClosureStage() {
81
81
  return { kind: "continue", ctx };
82
82
  };
83
83
  }
84
+ var supervisorClosureStage = {
85
+ id: SUPERVISOR_CLOSURE_STAGE_ID,
86
+ kind: "observe",
87
+ after: ["source-closeout"],
88
+ before: ["journal-append"],
89
+ priority: 0,
90
+ protected: false
91
+ };
84
92
  var supervisorClosureStageMutation = {
85
93
  op: "insert",
86
94
  contributedBy: "@rig/supervisor-plugin",
87
- stage: {
88
- id: SUPERVISOR_CLOSURE_STAGE_ID,
89
- kind: "observe",
90
- after: ["source-closeout"],
91
- before: ["journal-append"],
92
- priority: 0,
93
- protected: false
94
- }
95
+ stage: supervisorClosureStage
95
96
  };
97
+ // packages/supervisor-plugin/src/loop.ts
98
+ import { computeTaskDependencyBadges, toTaskDependencyProjection } from "@rig/contracts";
99
+ import { rankReadyTasks, selectRankedReadyTasks } from "@rig/dependency-graph-plugin";
100
+
101
+ // packages/supervisor-plugin/src/journal.ts
102
+ import { mkdir, readFile, writeFile } from "fs/promises";
103
+ import { dirname } from "path";
104
+ import { Schema } from "effect";
105
+ import {
106
+ SupervisorEvent
107
+ } from "@rig/contracts";
108
+ var NEWLINE = `
109
+ `;
110
+ function reduceSupervisorJournal(events) {
111
+ let processed = 0;
112
+ let succeeded = 0;
113
+ let failed = 0;
114
+ let skipped = 0;
115
+ let current = null;
116
+ let idleReason = null;
117
+ let stopReason = null;
118
+ let status = "running";
119
+ let plannedOrder = [];
120
+ let selectionPolicy = null;
121
+ const concurrency = null;
122
+ const closures = [];
123
+ const anomalies = [];
124
+ for (const event of events) {
125
+ switch (event.kind) {
126
+ case "supervisor.started":
127
+ status = "running";
128
+ break;
129
+ case "supervisor.selection-planned":
130
+ plannedOrder = [...event.taskIds];
131
+ selectionPolicy = event.policy;
132
+ break;
133
+ case "supervisor.dispatch-started":
134
+ break;
135
+ case "supervisor.dispatch-confirmed":
136
+ current = { taskId: event.taskId, runId: event.runId };
137
+ break;
138
+ case "supervisor.dispatch":
139
+ current = { taskId: event.taskId, runId: event.runId };
140
+ break;
141
+ case "supervisor.outcome":
142
+ processed += 1;
143
+ if (event.failed) {
144
+ failed += 1;
145
+ } else {
146
+ succeeded += 1;
147
+ }
148
+ if (event.closure) {
149
+ closures.push(event.closure);
150
+ }
151
+ if (current?.runId === event.runId) {
152
+ current = null;
153
+ } else if (current !== null) {
154
+ anomalies.push(`outcome for ${event.runId} did not match current ${current.runId}`);
155
+ }
156
+ break;
157
+ case "supervisor.skipped":
158
+ processed += 1;
159
+ skipped += 1;
160
+ break;
161
+ case "supervisor.idle":
162
+ status = "idle";
163
+ idleReason = event.reason;
164
+ break;
165
+ case "supervisor.stopped":
166
+ status = "stopped";
167
+ stopReason = event.reason;
168
+ current = null;
169
+ break;
170
+ case "supervisor.finished":
171
+ status = "finished";
172
+ processed = event.processed;
173
+ succeeded = event.succeeded;
174
+ failed = event.failed;
175
+ skipped = event.skipped ?? skipped;
176
+ idleReason = event.idleReason;
177
+ current = null;
178
+ break;
179
+ }
180
+ }
181
+ return { status, processed, succeeded, failed, skipped, current, plannedOrder, selectionPolicy, concurrency, idleReason, stopReason, closures, anomalies };
182
+ }
183
+ function createFileSupervisorJournal(path) {
184
+ return {
185
+ async append(line) {
186
+ await mkdir(dirname(path), { recursive: true });
187
+ await writeFile(path, `${line}${NEWLINE}`, { flag: "a" });
188
+ },
189
+ async read() {
190
+ try {
191
+ return await readFile(path, "utf8");
192
+ } catch (error) {
193
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
194
+ if (code === "ENOENT")
195
+ return "";
196
+ throw error;
197
+ }
198
+ }
199
+ };
200
+ }
201
+ function createInMemorySupervisorJournalStore(seed = []) {
202
+ const lines = seed.map((event) => JSON.stringify(event));
203
+ return {
204
+ async append(line) {
205
+ lines.push(line);
206
+ },
207
+ async read() {
208
+ return lines.join(NEWLINE);
209
+ }
210
+ };
211
+ }
212
+ function parseSupervisorJournal(text) {
213
+ return text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line, index) => {
214
+ try {
215
+ return Schema.decodeUnknownSync(SupervisorEvent)(JSON.parse(line));
216
+ } catch (error) {
217
+ const message = error instanceof Error ? error.message : String(error);
218
+ throw new Error(`Invalid supervisor journal line ${index + 1}: ${message}`);
219
+ }
220
+ });
221
+ }
222
+ function createSupervisorJournal(store) {
223
+ const append = async (event) => {
224
+ await store.append(JSON.stringify(Schema.decodeUnknownSync(SupervisorEvent)(event)));
225
+ };
226
+ const readEvents = async () => parseSupervisorJournal(await store.read());
227
+ const readProjection = async () => reduceSupervisorJournal(await readEvents());
228
+ return { append, readEvents, readProjection };
229
+ }
230
+
231
+ // packages/supervisor-plugin/src/loop.ts
232
+ import { classifyTasks, isHumanBlockerClass } from "@rig/blocker-classifier-plugin";
233
+ function selectionMode(policy) {
234
+ return policy === "blocking-only" || policy === "max-unblock" ? policy : "all-ready";
235
+ }
236
+ function at(deps) {
237
+ return deps.now ? deps.now() : new Date().toISOString();
238
+ }
239
+ function activeTaskIds(runs) {
240
+ const terminal = new Set(["completed", "failed", "stopped", "stale"]);
241
+ return new Set(runs.filter((run) => run.taskId && run.live && !run.stale && !terminal.has(run.status)).map((run) => run.taskId));
242
+ }
243
+ function stopReasonForNoCandidates(tasks, runs) {
244
+ if (tasks.length === 0)
245
+ return "all-done";
246
+ const classifications = classifyTasks(tasks, runs).classifications;
247
+ const nonTerminal = tasks.map(toTaskDependencyProjection).filter((task) => task.status !== "closed" && task.status !== "completed" && task.status !== "cancelled");
248
+ if (nonTerminal.length > 0 && nonTerminal.every((task) => {
249
+ const classification = classifications.find((entry) => entry.taskId === task.id);
250
+ return classification ? isHumanBlockerClass(classification.blockerClass) : false;
251
+ }))
252
+ return "all-human-blocked";
253
+ return "all-done";
254
+ }
255
+ async function planSupervisorLoop(projectRoot, deps, options = {}) {
256
+ const [tasks, runs] = await Promise.all([
257
+ deps.listTasks(projectRoot),
258
+ deps.listRuns ? deps.listRuns(projectRoot) : Promise.resolve([])
259
+ ]);
260
+ const projected = tasks.map(toTaskDependencyProjection);
261
+ const excluded = new Set(options.excludeTaskIds ?? []);
262
+ const candidates = options.candidateTaskIds ? new Set(options.candidateTaskIds) : null;
263
+ const ranked = rankReadyTasks(projected, {
264
+ activeTaskIds: activeTaskIds(runs),
265
+ excludeTaskIds: excluded,
266
+ ...candidates ? { filter: (task) => candidates.has(task.id) } : {},
267
+ selection: selectionMode(options.selectionPolicy)
268
+ });
269
+ const limit = Math.max(0, options.maxTasks ?? ranked.length);
270
+ const selected = selectRankedReadyTasks(projected, {
271
+ activeTaskIds: activeTaskIds(runs),
272
+ excludeTaskIds: excluded,
273
+ ...candidates ? { filter: (task) => candidates.has(task.id) } : {},
274
+ selection: selectionMode(options.selectionPolicy),
275
+ requireDisjointScopes: (options.concurrency ?? 1) > 1,
276
+ limit
277
+ });
278
+ const plannedOrder = selected.map((task) => task.id);
279
+ return {
280
+ plannedOrder,
281
+ ranked: ranked.filter((entry) => plannedOrder.includes(entry.task.id)),
282
+ idleReason: plannedOrder.length === 0 ? stopReasonForNoCandidates(tasks, runs) : null
283
+ };
284
+ }
285
+ function failedOutcome(outcome) {
286
+ if (typeof outcome.failed === "boolean")
287
+ return outcome.failed;
288
+ return outcome.status === "failed" || outcome.status === "stopped" || outcome.status === "needs-attention" || outcome.status === "waiting-approval" || outcome.status === "waiting-user-input";
289
+ }
290
+ function runStatus(value) {
291
+ switch (value) {
292
+ case "created":
293
+ case "queued":
294
+ case "preparing":
295
+ case "running":
296
+ case "waiting-approval":
297
+ case "waiting-user-input":
298
+ case "paused":
299
+ case "validating":
300
+ case "reviewing":
301
+ case "closing-out":
302
+ case "needs-attention":
303
+ case "completed":
304
+ case "failed":
305
+ case "stopped":
306
+ return value;
307
+ default:
308
+ return "failed";
309
+ }
310
+ }
311
+ async function runSupervisorLoop(projectRoot, deps, options = {}) {
312
+ const events = [{ kind: "supervisor.started", at: at(deps), options }];
313
+ const maxTasks = Math.max(0, options.maxTasks ?? Number.POSITIVE_INFINITY);
314
+ const concurrency = Math.max(1, options.concurrency ?? 1);
315
+ let processed = 0;
316
+ let succeeded = 0;
317
+ let failed = 0;
318
+ let skipped = 0;
319
+ const plannedAll = [];
320
+ const dispatchedTaskIds = new Set(options.excludeTaskIds ?? []);
321
+ while (processed < maxTasks) {
322
+ const plan = await planSupervisorLoop(projectRoot, deps, { ...options, excludeTaskIds: dispatchedTaskIds, maxTasks: Math.min(concurrency, maxTasks - processed) });
323
+ if (plan.plannedOrder.length === 0) {
324
+ events.push({ kind: "supervisor.idle", at: at(deps), reason: plan.idleReason ?? "all-done" });
325
+ break;
326
+ }
327
+ events.push({ kind: "supervisor.selection-planned", at: at(deps), taskIds: plan.plannedOrder, policy: options.selectionPolicy ?? "rank" });
328
+ plannedAll.push(...plan.plannedOrder);
329
+ if (options.dryRun)
330
+ break;
331
+ const dispatchTask = deps.dispatch ?? deps.dispatchRun;
332
+ if (!dispatchTask)
333
+ throw new Error("runSupervisorLoop requires dispatch when dryRun is false.");
334
+ if (!deps.awaitRunTerminal)
335
+ throw new Error("runSupervisorLoop requires awaitRunTerminal when dryRun is false.");
336
+ for (const entry of plan.ranked) {
337
+ const taskId = entry.task.id;
338
+ events.push({ kind: "supervisor.dispatch-started", at: at(deps), taskId, score: entry.score });
339
+ const dispatch = await dispatchTask({
340
+ projectRoot,
341
+ taskId: entry.task.id,
342
+ ...entry.task.title !== undefined ? { title: entry.task.title } : {},
343
+ ...options.model !== undefined ? { model: options.model } : {},
344
+ ...options.force !== undefined ? { force: options.force } : {}
345
+ });
346
+ dispatchedTaskIds.add(entry.task.id);
347
+ const runId = dispatch.runId;
348
+ events.push({ kind: "supervisor.dispatch-confirmed", at: at(deps), taskId, runId });
349
+ const outcome = await deps.awaitRunTerminal(projectRoot, dispatch.runId, entry.task.id);
350
+ const outcomeFailed = failedOutcome(outcome);
351
+ if (outcomeFailed)
352
+ failed += 1;
353
+ else
354
+ succeeded += 1;
355
+ processed += 1;
356
+ events.push({
357
+ kind: "supervisor.outcome",
358
+ at: at(deps),
359
+ taskId,
360
+ runId,
361
+ status: runStatus(outcome.status),
362
+ failed: outcomeFailed,
363
+ unblockedTaskIds: [...outcome.unblockedTaskIds ?? []]
364
+ });
365
+ if (options.failFast && outcomeFailed || options.pauseOnAttention && outcomeFailed) {
366
+ events.push({ kind: "supervisor.idle", at: at(deps), reason: outcome.status === "needs-attention" || outcome.status === "waiting-approval" || outcome.status === "waiting-user-input" ? "judge-stop" : "source-error" });
367
+ processed = maxTasks;
368
+ break;
369
+ }
370
+ if (processed >= maxTasks)
371
+ break;
372
+ }
373
+ }
374
+ const projectionBeforeFinish = reduceSupervisorJournal(events);
375
+ const idleReason = projectionBeforeFinish.idleReason ?? (processed >= maxTasks && Number.isFinite(maxTasks) ? "max-tasks" : null);
376
+ events.push({ kind: "supervisor.finished", at: at(deps), processed, succeeded, failed, skipped, idleReason });
377
+ const projection = reduceSupervisorJournal(events);
378
+ return { ok: failed === 0, dryRun: options.dryRun === true, plannedOrder: plannedAll, events, projection };
379
+ }
380
+ async function controlSupervisorRun(projectRoot, runId, control, deps) {
381
+ await deps.deliverRunControl(projectRoot, runId, control);
382
+ return { ok: true, runId, control: control.kind };
383
+ }
384
+ function collectBlockingClosure(taskId, badges) {
385
+ const closure = new Set;
386
+ const visit = (currentTaskId) => {
387
+ for (const blockerId of badges.get(currentTaskId)?.blockedBy ?? []) {
388
+ if (closure.has(blockerId))
389
+ continue;
390
+ closure.add(blockerId);
391
+ visit(blockerId);
392
+ }
393
+ };
394
+ visit(taskId);
395
+ return closure;
396
+ }
397
+ async function unblockTask(projectRoot, taskId, deps, options = {}) {
398
+ if (taskId === null)
399
+ return runSupervisorLoop(projectRoot, deps, { ...options, selectionPolicy: "max-unblock", maxTasks: 1 });
400
+ const tasks = await deps.listTasks(projectRoot);
401
+ const projected = tasks.map(toTaskDependencyProjection);
402
+ const badges = computeTaskDependencyBadges(projected);
403
+ const blockers = collectBlockingClosure(taskId, badges);
404
+ return runSupervisorLoop(projectRoot, deps, { ...options, candidateTaskIds: blockers, selectionPolicy: "rank", maxTasks: 1 });
405
+ }
406
+
96
407
  // packages/supervisor-plugin/src/cli.ts
97
408
  var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
98
409
  var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
@@ -162,7 +473,12 @@ function delay(ms) {
162
473
  return promise;
163
474
  }
164
475
  async function loadSupervisorClient() {
165
- return await import("@rig/client");
476
+ const [taskIo, runIo, dispatchIo] = await Promise.all([
477
+ import("@rig/core/task-io"),
478
+ import("@rig/run-worker/runs"),
479
+ import("@rig/runtime/control-plane/dispatch")
480
+ ]);
481
+ return { listTasks: taskIo.listTasks, listRuns: runIo.listRuns, dispatchRun: dispatchIo.dispatchRun };
166
482
  }
167
483
  function supervisorDeps(timeoutMs) {
168
484
  return {
@@ -205,8 +521,7 @@ async function executeLoop(context, args) {
205
521
  const stopWhen = takeOption(task.rest, "--stop-when");
206
522
  const timeout = takeOption(stopWhen.rest, "--timeout-ms");
207
523
  requireNoExtraArgs(timeout.rest, "rig loop [--task <id>] [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
208
- const { runSupervisorLoop } = await loadSupervisorClient();
209
- const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), {
524
+ const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
210
525
  maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
211
526
  concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
212
527
  ...task.value ? { candidateTaskIds: [task.value] } : {},
@@ -227,8 +542,7 @@ async function executeUnblock(context, args) {
227
542
  const timeout = takeOption(json.rest, "--timeout-ms");
228
543
  const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
229
544
  requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
230
- const { unblockTask } = await loadSupervisorClient();
231
- const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), { dryRun: dry.value || context.dryRun });
545
+ const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
232
546
  if (context.outputMode === "text") {
233
547
  if (json.value)
234
548
  printJson(result);
@@ -257,65 +571,31 @@ var supervisorCliCommands = [
257
571
  run: executeUnblock
258
572
  }
259
573
  ];
260
- // packages/supervisor-plugin/src/journal.ts
261
- import { mkdir, readFile, writeFile } from "fs/promises";
262
- import { dirname } from "path";
263
- import { Schema } from "effect";
264
- import {
265
- SupervisorEvent,
266
- reduceSupervisorJournal
267
- } from "@rig/contracts";
268
- var NEWLINE = `
269
- `;
270
- function createFileSupervisorJournal(path) {
271
- return {
272
- async append(line) {
273
- await mkdir(dirname(path), { recursive: true });
274
- await writeFile(path, `${line}${NEWLINE}`, { flag: "a" });
275
- },
276
- async read() {
277
- try {
278
- return await readFile(path, "utf8");
279
- } catch (error) {
280
- const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
281
- if (code === "ENOENT")
282
- return "";
283
- throw error;
284
- }
285
- }
286
- };
287
- }
288
- function createInMemorySupervisorJournalStore(seed = []) {
289
- const lines = seed.map((event) => JSON.stringify(event));
574
+ // packages/supervisor-plugin/src/plugin.ts
575
+ import { RIG_CAPABILITY_PANEL_SLOT, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
576
+ import { definePlugin } from "@rig/core/config";
577
+
578
+ // packages/supervisor-plugin/src/panel.ts
579
+ import { RIG_RUN_STOP_PANEL_ACTION } from "@rig/contracts";
580
+ function buildSupervisorPanelPayload(context) {
581
+ const status = context.folded.status ?? "unknown";
582
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
583
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
290
584
  return {
291
- async append(line) {
292
- lines.push(line);
293
- },
294
- async read() {
295
- return lines.join(NEWLINE);
296
- }
585
+ status,
586
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
587
+ processed: context.folded.closeoutPhases.length,
588
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
589
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
590
+ skipped: 0,
591
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
592
+ idleReason: operatorActive ? null : status,
593
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
594
+ closures: []
297
595
  };
298
596
  }
299
- function parseSupervisorJournal(text) {
300
- return text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).map((line, index) => {
301
- try {
302
- return Schema.decodeUnknownSync(SupervisorEvent)(JSON.parse(line));
303
- } catch (error) {
304
- const message = error instanceof Error ? error.message : String(error);
305
- throw new Error(`Invalid supervisor journal line ${index + 1}: ${message}`);
306
- }
307
- });
308
- }
309
- function createSupervisorJournal(store) {
310
- const append = async (event) => {
311
- await store.append(JSON.stringify(Schema.decodeUnknownSync(SupervisorEvent)(event)));
312
- };
313
- const readEvents = async () => parseSupervisorJournal(await store.read());
314
- const readProjection = async () => reduceSupervisorJournal(await readEvents());
315
- return { append, readEvents, readProjection };
316
- }
597
+
317
598
  // packages/supervisor-plugin/src/plugin.ts
318
- import { definePlugin } from "@rig/core/config";
319
599
  var SUPERVISOR_PLUGIN_NAME = "@rig/supervisor-plugin";
320
600
  var supervisorPlugin = definePlugin({
321
601
  name: SUPERVISOR_PLUGIN_NAME,
@@ -327,28 +607,32 @@ var supervisorPlugin = definePlugin({
327
607
  { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
328
608
  { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
329
609
  ],
330
- cliCommands: supervisorCliCommands.map(({ run: _run, ...metadata }) => metadata),
331
- stageMutations: [supervisorClosureStageMutation]
610
+ cliCommands: supervisorCliCommands,
611
+ panels: [
612
+ {
613
+ id: RIG_SUPERVISOR_PANEL_ID,
614
+ slot: RIG_CAPABILITY_PANEL_SLOT,
615
+ title: "Supervisor",
616
+ capabilityId: "run.supervisor",
617
+ description: "Live run status, closeout progress, and operator stop control.",
618
+ produce: (context) => buildSupervisorPanelPayload(context)
619
+ }
620
+ ],
621
+ stageMutations: [supervisorClosureStageMutation],
622
+ stages: [{ ...supervisorClosureStage, run: createDefaultSupervisorClosureStage() }]
332
623
  }
333
- }, {
334
- stages: { [SUPERVISOR_CLOSURE_STAGE_ID]: createDefaultSupervisorClosureStage() },
335
- featureCapabilities: [
336
- { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
337
- { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
338
- ],
339
- cliCommands: supervisorCliCommands
340
624
  });
341
625
  function createSupervisorPlugin() {
342
626
  return supervisorPlugin;
343
627
  }
344
628
  // packages/supervisor-plugin/src/supervisor.ts
345
629
  import {
346
- computeTaskDependencyBadges,
630
+ computeTaskDependencyBadges as computeTaskDependencyBadges2,
347
631
  disjointScope,
348
632
  isTaskTerminalStatus,
349
- rankReadyTasks,
350
633
  readTaskScope
351
- } from "@rig/core/task-graph";
634
+ } from "@rig/contracts";
635
+ import { rankReadyTasks as rankReadyTasks2 } from "@rig/dependency-graph-plugin";
352
636
  var DEFAULT_STOP_WHEN = new Set(["all-done", "all-human-blocked", "source-error"]);
353
637
  var HUMAN_BLOCKER_CLASS = {
354
638
  "not-blocked": false,
@@ -370,7 +654,7 @@ function normalizeOptions(options = {}) {
370
654
  pollMs: Math.max(0, Math.floor(options.pollMs ?? 5000))
371
655
  };
372
656
  }
373
- function selectionMode(policy) {
657
+ function selectionMode2(policy) {
374
658
  if (policy === "blocking-only")
375
659
  return "blocking-only";
376
660
  if (policy === "max-unblock")
@@ -431,15 +715,15 @@ async function runSupervisor(ctx, options = {}) {
431
715
  break;
432
716
  }
433
717
  const snapshot = await ctx.readTasks();
434
- const badges = computeTaskDependencyBadges(snapshot.tasks);
718
+ const badges = computeTaskDependencyBadges2(snapshot.tasks);
435
719
  const activeRuns = snapshot.activeRuns ?? [];
436
- const activeTaskIds = activeRuns.map((run) => run.taskId);
720
+ const activeTaskIds2 = activeRuns.map((run) => run.taskId);
437
721
  const remainingSlots = normalized.maxTasks === null ? normalized.concurrency : Math.min(normalized.concurrency, normalized.maxTasks - processed);
438
- const ranked = rankReadyTasks(snapshot.tasks, {
722
+ const ranked = rankReadyTasks2(snapshot.tasks, {
439
723
  excludeTaskIds: completedTaskIds,
440
- activeTaskIds,
724
+ activeTaskIds: activeTaskIds2,
441
725
  filter: (task) => filterTask(task, normalized.filter),
442
- selection: selectionMode(normalized.selectionPolicy)
726
+ selection: selectionMode2(normalized.selectionPolicy)
443
727
  });
444
728
  const selected = [];
445
729
  const occupiedScopes = activeRuns.flatMap((run) => {
@@ -474,18 +758,18 @@ async function runSupervisor(ctx, options = {}) {
474
758
  ...handle.dispatchHandle === undefined ? {} : { dispatchHandle: handle.dispatchHandle }
475
759
  });
476
760
  const outcome = await ctx.awaitTerminal(handle.runId, entry.task);
477
- const failedOutcome = terminalFailed(outcome);
761
+ const failedOutcome2 = terminalFailed(outcome);
478
762
  await emit({
479
763
  kind: "supervisor.outcome",
480
764
  at: now(),
481
765
  taskId: entry.task.id,
482
766
  runId: outcome.runId,
483
767
  status: outcome.status,
484
- failed: failedOutcome,
768
+ failed: failedOutcome2,
485
769
  unblockedTaskIds: outcome.closure?.unblockedTaskIds ?? [],
486
770
  ...outcome.closure ? { closure: outcome.closure } : {}
487
771
  });
488
- return { taskId: entry.task.id, status: outcome.status, failed: failedOutcome };
772
+ return { taskId: entry.task.id, status: outcome.status, failed: failedOutcome2 };
489
773
  }));
490
774
  for (const outcome of outcomes) {
491
775
  completedTaskIds.add(outcome.taskId);
@@ -510,10 +794,15 @@ async function runSupervisor(ctx, options = {}) {
510
794
  return { processed, succeeded, failed, skipped, idleReason };
511
795
  }
512
796
  export {
797
+ unblockTask,
513
798
  supervisorPlugin,
514
799
  supervisorClosureStageMutation,
800
+ supervisorClosureStage,
515
801
  supervisorCliCommands,
802
+ runSupervisorLoop,
516
803
  runSupervisor,
804
+ reduceSupervisorJournal,
805
+ planSupervisorLoop,
517
806
  parseSupervisorJournal,
518
807
  executeUnblock,
519
808
  executeLoop,
@@ -524,6 +813,7 @@ export {
524
813
  createFileSupervisorJournal,
525
814
  createDefaultSupervisorClosureStage,
526
815
  createClosureStage,
816
+ controlSupervisorRun,
527
817
  awaitTerminalRun,
528
818
  SUPERVISOR_UNBLOCK_CLI_ID,
529
819
  SUPERVISOR_PLUGIN_NAME,
@@ -1,4 +1,12 @@
1
1
  import { SupervisorEvent, type SupervisorProjection } from "@rig/contracts";
2
+ /**
3
+ * Fold a supervisor event journal into the current supervisor projection.
4
+ *
5
+ * Behavioral reducer owned by the supervisor plugin (the only consumer of the
6
+ * supervisor loop's read-model). `@rig/contracts` keeps the `SupervisorEvent` /
7
+ * `SupervisorProjection` schemas; the fold over them lives here.
8
+ */
9
+ export declare function reduceSupervisorJournal(events: readonly SupervisorEvent[]): SupervisorProjection;
2
10
  export interface SupervisorJournalStore {
3
11
  append(line: string): Promise<void>;
4
12
  read(): Promise<string>;
@@ -4,11 +4,83 @@ import { mkdir, readFile, writeFile } from "fs/promises";
4
4
  import { dirname } from "path";
5
5
  import { Schema } from "effect";
6
6
  import {
7
- SupervisorEvent,
8
- reduceSupervisorJournal
7
+ SupervisorEvent
9
8
  } from "@rig/contracts";
10
9
  var NEWLINE = `
11
10
  `;
11
+ function reduceSupervisorJournal(events) {
12
+ let processed = 0;
13
+ let succeeded = 0;
14
+ let failed = 0;
15
+ let skipped = 0;
16
+ let current = null;
17
+ let idleReason = null;
18
+ let stopReason = null;
19
+ let status = "running";
20
+ let plannedOrder = [];
21
+ let selectionPolicy = null;
22
+ const concurrency = null;
23
+ const closures = [];
24
+ const anomalies = [];
25
+ for (const event of events) {
26
+ switch (event.kind) {
27
+ case "supervisor.started":
28
+ status = "running";
29
+ break;
30
+ case "supervisor.selection-planned":
31
+ plannedOrder = [...event.taskIds];
32
+ selectionPolicy = event.policy;
33
+ break;
34
+ case "supervisor.dispatch-started":
35
+ break;
36
+ case "supervisor.dispatch-confirmed":
37
+ current = { taskId: event.taskId, runId: event.runId };
38
+ break;
39
+ case "supervisor.dispatch":
40
+ current = { taskId: event.taskId, runId: event.runId };
41
+ break;
42
+ case "supervisor.outcome":
43
+ processed += 1;
44
+ if (event.failed) {
45
+ failed += 1;
46
+ } else {
47
+ succeeded += 1;
48
+ }
49
+ if (event.closure) {
50
+ closures.push(event.closure);
51
+ }
52
+ if (current?.runId === event.runId) {
53
+ current = null;
54
+ } else if (current !== null) {
55
+ anomalies.push(`outcome for ${event.runId} did not match current ${current.runId}`);
56
+ }
57
+ break;
58
+ case "supervisor.skipped":
59
+ processed += 1;
60
+ skipped += 1;
61
+ break;
62
+ case "supervisor.idle":
63
+ status = "idle";
64
+ idleReason = event.reason;
65
+ break;
66
+ case "supervisor.stopped":
67
+ status = "stopped";
68
+ stopReason = event.reason;
69
+ current = null;
70
+ break;
71
+ case "supervisor.finished":
72
+ status = "finished";
73
+ processed = event.processed;
74
+ succeeded = event.succeeded;
75
+ failed = event.failed;
76
+ skipped = event.skipped ?? skipped;
77
+ idleReason = event.idleReason;
78
+ current = null;
79
+ break;
80
+ }
81
+ }
82
+ return { status, processed, succeeded, failed, skipped, current, plannedOrder, selectionPolicy, concurrency, idleReason, stopReason, closures, anomalies };
83
+ }
12
84
  function createFileSupervisorJournal(path) {
13
85
  return {
14
86
  async append(line) {
@@ -57,6 +129,7 @@ function createSupervisorJournal(store) {
57
129
  return { append, readEvents, readProjection };
58
130
  }
59
131
  export {
132
+ reduceSupervisorJournal,
60
133
  parseSupervisorJournal,
61
134
  createSupervisorJournal,
62
135
  createInMemorySupervisorJournalStore,