@h-rig/supervisor-plugin 0.0.6-alpha.155 → 0.0.6-alpha.157

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.
@@ -2,6 +2,7 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // packages/supervisor-plugin/src/plugin.ts
5
+ import { RIG_CAPABILITY_PANEL_SLOT, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
5
6
  import { definePlugin } from "@rig/core/config";
6
7
 
7
8
  // packages/supervisor-plugin/src/closureStage.ts
@@ -36,17 +37,18 @@ function createDefaultSupervisorClosureStage() {
36
37
  return { kind: "continue", ctx };
37
38
  };
38
39
  }
40
+ var supervisorClosureStage = {
41
+ id: SUPERVISOR_CLOSURE_STAGE_ID,
42
+ kind: "observe",
43
+ after: ["source-closeout"],
44
+ before: ["journal-append"],
45
+ priority: 0,
46
+ protected: false
47
+ };
39
48
  var supervisorClosureStageMutation = {
40
49
  op: "insert",
41
50
  contributedBy: "@rig/supervisor-plugin",
42
- stage: {
43
- id: SUPERVISOR_CLOSURE_STAGE_ID,
44
- kind: "observe",
45
- after: ["source-closeout"],
46
- before: ["journal-append"],
47
- priority: 0,
48
- protected: false
49
- }
51
+ stage: supervisorClosureStage
50
52
  };
51
53
 
52
54
  // packages/supervisor-plugin/src/awaiter.ts
@@ -85,6 +87,261 @@ async function awaitTerminalRun(options) {
85
87
  }
86
88
  }
87
89
 
90
+ // packages/supervisor-plugin/src/loop.ts
91
+ import { computeTaskDependencyBadges, toTaskDependencyProjection } from "@rig/contracts";
92
+ import { rankReadyTasks, selectRankedReadyTasks } from "@rig/dependency-graph-plugin";
93
+
94
+ // packages/supervisor-plugin/src/journal.ts
95
+ import { Schema } from "effect";
96
+ import {
97
+ SupervisorEvent
98
+ } from "@rig/contracts";
99
+ function reduceSupervisorJournal(events) {
100
+ let processed = 0;
101
+ let succeeded = 0;
102
+ let failed = 0;
103
+ let skipped = 0;
104
+ let current = null;
105
+ let idleReason = null;
106
+ let stopReason = null;
107
+ let status = "running";
108
+ let plannedOrder = [];
109
+ let selectionPolicy = null;
110
+ const concurrency = null;
111
+ const closures = [];
112
+ const anomalies = [];
113
+ for (const event of events) {
114
+ switch (event.kind) {
115
+ case "supervisor.started":
116
+ status = "running";
117
+ break;
118
+ case "supervisor.selection-planned":
119
+ plannedOrder = [...event.taskIds];
120
+ selectionPolicy = event.policy;
121
+ break;
122
+ case "supervisor.dispatch-started":
123
+ break;
124
+ case "supervisor.dispatch-confirmed":
125
+ current = { taskId: event.taskId, runId: event.runId };
126
+ break;
127
+ case "supervisor.dispatch":
128
+ current = { taskId: event.taskId, runId: event.runId };
129
+ break;
130
+ case "supervisor.outcome":
131
+ processed += 1;
132
+ if (event.failed) {
133
+ failed += 1;
134
+ } else {
135
+ succeeded += 1;
136
+ }
137
+ if (event.closure) {
138
+ closures.push(event.closure);
139
+ }
140
+ if (current?.runId === event.runId) {
141
+ current = null;
142
+ } else if (current !== null) {
143
+ anomalies.push(`outcome for ${event.runId} did not match current ${current.runId}`);
144
+ }
145
+ break;
146
+ case "supervisor.skipped":
147
+ processed += 1;
148
+ skipped += 1;
149
+ break;
150
+ case "supervisor.idle":
151
+ status = "idle";
152
+ idleReason = event.reason;
153
+ break;
154
+ case "supervisor.stopped":
155
+ status = "stopped";
156
+ stopReason = event.reason;
157
+ current = null;
158
+ break;
159
+ case "supervisor.finished":
160
+ status = "finished";
161
+ processed = event.processed;
162
+ succeeded = event.succeeded;
163
+ failed = event.failed;
164
+ skipped = event.skipped ?? skipped;
165
+ idleReason = event.idleReason;
166
+ current = null;
167
+ break;
168
+ }
169
+ }
170
+ return { status, processed, succeeded, failed, skipped, current, plannedOrder, selectionPolicy, concurrency, idleReason, stopReason, closures, anomalies };
171
+ }
172
+
173
+ // packages/supervisor-plugin/src/loop.ts
174
+ import { classifyTasks, isHumanBlockerClass } from "@rig/blocker-classifier-plugin";
175
+ function selectionMode(policy) {
176
+ return policy === "blocking-only" || policy === "max-unblock" ? policy : "all-ready";
177
+ }
178
+ function at(deps) {
179
+ return deps.now ? deps.now() : new Date().toISOString();
180
+ }
181
+ function activeTaskIds(runs) {
182
+ const terminal = new Set(["completed", "failed", "stopped", "stale"]);
183
+ return new Set(runs.filter((run) => run.taskId && run.live && !run.stale && !terminal.has(run.status)).map((run) => run.taskId));
184
+ }
185
+ function stopReasonForNoCandidates(tasks, runs) {
186
+ if (tasks.length === 0)
187
+ return "all-done";
188
+ const classifications = classifyTasks(tasks, runs).classifications;
189
+ const nonTerminal = tasks.map(toTaskDependencyProjection).filter((task) => task.status !== "closed" && task.status !== "completed" && task.status !== "cancelled");
190
+ if (nonTerminal.length > 0 && nonTerminal.every((task) => {
191
+ const classification = classifications.find((entry) => entry.taskId === task.id);
192
+ return classification ? isHumanBlockerClass(classification.blockerClass) : false;
193
+ }))
194
+ return "all-human-blocked";
195
+ return "all-done";
196
+ }
197
+ async function planSupervisorLoop(projectRoot, deps, options = {}) {
198
+ const [tasks, runs] = await Promise.all([
199
+ deps.listTasks(projectRoot),
200
+ deps.listRuns ? deps.listRuns(projectRoot) : Promise.resolve([])
201
+ ]);
202
+ const projected = tasks.map(toTaskDependencyProjection);
203
+ const excluded = new Set(options.excludeTaskIds ?? []);
204
+ const candidates = options.candidateTaskIds ? new Set(options.candidateTaskIds) : null;
205
+ const ranked = rankReadyTasks(projected, {
206
+ activeTaskIds: activeTaskIds(runs),
207
+ excludeTaskIds: excluded,
208
+ ...candidates ? { filter: (task) => candidates.has(task.id) } : {},
209
+ selection: selectionMode(options.selectionPolicy)
210
+ });
211
+ const limit = Math.max(0, options.maxTasks ?? ranked.length);
212
+ const selected = selectRankedReadyTasks(projected, {
213
+ activeTaskIds: activeTaskIds(runs),
214
+ excludeTaskIds: excluded,
215
+ ...candidates ? { filter: (task) => candidates.has(task.id) } : {},
216
+ selection: selectionMode(options.selectionPolicy),
217
+ requireDisjointScopes: (options.concurrency ?? 1) > 1,
218
+ limit
219
+ });
220
+ const plannedOrder = selected.map((task) => task.id);
221
+ return {
222
+ plannedOrder,
223
+ ranked: ranked.filter((entry) => plannedOrder.includes(entry.task.id)),
224
+ idleReason: plannedOrder.length === 0 ? stopReasonForNoCandidates(tasks, runs) : null
225
+ };
226
+ }
227
+ function failedOutcome(outcome) {
228
+ if (typeof outcome.failed === "boolean")
229
+ return outcome.failed;
230
+ return outcome.status === "failed" || outcome.status === "stopped" || outcome.status === "needs-attention" || outcome.status === "waiting-approval" || outcome.status === "waiting-user-input";
231
+ }
232
+ function runStatus(value) {
233
+ switch (value) {
234
+ case "created":
235
+ case "queued":
236
+ case "preparing":
237
+ case "running":
238
+ case "waiting-approval":
239
+ case "waiting-user-input":
240
+ case "paused":
241
+ case "validating":
242
+ case "reviewing":
243
+ case "closing-out":
244
+ case "needs-attention":
245
+ case "completed":
246
+ case "failed":
247
+ case "stopped":
248
+ return value;
249
+ default:
250
+ return "failed";
251
+ }
252
+ }
253
+ async function runSupervisorLoop(projectRoot, deps, options = {}) {
254
+ const events = [{ kind: "supervisor.started", at: at(deps), options }];
255
+ const maxTasks = Math.max(0, options.maxTasks ?? Number.POSITIVE_INFINITY);
256
+ const concurrency = Math.max(1, options.concurrency ?? 1);
257
+ let processed = 0;
258
+ let succeeded = 0;
259
+ let failed = 0;
260
+ let skipped = 0;
261
+ const plannedAll = [];
262
+ const dispatchedTaskIds = new Set(options.excludeTaskIds ?? []);
263
+ while (processed < maxTasks) {
264
+ const plan = await planSupervisorLoop(projectRoot, deps, { ...options, excludeTaskIds: dispatchedTaskIds, maxTasks: Math.min(concurrency, maxTasks - processed) });
265
+ if (plan.plannedOrder.length === 0) {
266
+ events.push({ kind: "supervisor.idle", at: at(deps), reason: plan.idleReason ?? "all-done" });
267
+ break;
268
+ }
269
+ events.push({ kind: "supervisor.selection-planned", at: at(deps), taskIds: plan.plannedOrder, policy: options.selectionPolicy ?? "rank" });
270
+ plannedAll.push(...plan.plannedOrder);
271
+ if (options.dryRun)
272
+ break;
273
+ const dispatchTask = deps.dispatch ?? deps.dispatchRun;
274
+ if (!dispatchTask)
275
+ throw new Error("runSupervisorLoop requires dispatch when dryRun is false.");
276
+ if (!deps.awaitRunTerminal)
277
+ throw new Error("runSupervisorLoop requires awaitRunTerminal when dryRun is false.");
278
+ for (const entry of plan.ranked) {
279
+ const taskId = entry.task.id;
280
+ events.push({ kind: "supervisor.dispatch-started", at: at(deps), taskId, score: entry.score });
281
+ const dispatch = await dispatchTask({
282
+ projectRoot,
283
+ taskId: entry.task.id,
284
+ ...entry.task.title !== undefined ? { title: entry.task.title } : {},
285
+ ...options.model !== undefined ? { model: options.model } : {},
286
+ ...options.force !== undefined ? { force: options.force } : {}
287
+ });
288
+ dispatchedTaskIds.add(entry.task.id);
289
+ const runId = dispatch.runId;
290
+ events.push({ kind: "supervisor.dispatch-confirmed", at: at(deps), taskId, runId });
291
+ const outcome = await deps.awaitRunTerminal(projectRoot, dispatch.runId, entry.task.id);
292
+ const outcomeFailed = failedOutcome(outcome);
293
+ if (outcomeFailed)
294
+ failed += 1;
295
+ else
296
+ succeeded += 1;
297
+ processed += 1;
298
+ events.push({
299
+ kind: "supervisor.outcome",
300
+ at: at(deps),
301
+ taskId,
302
+ runId,
303
+ status: runStatus(outcome.status),
304
+ failed: outcomeFailed,
305
+ unblockedTaskIds: [...outcome.unblockedTaskIds ?? []]
306
+ });
307
+ if (options.failFast && outcomeFailed || options.pauseOnAttention && outcomeFailed) {
308
+ 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" });
309
+ processed = maxTasks;
310
+ break;
311
+ }
312
+ if (processed >= maxTasks)
313
+ break;
314
+ }
315
+ }
316
+ const projectionBeforeFinish = reduceSupervisorJournal(events);
317
+ const idleReason = projectionBeforeFinish.idleReason ?? (processed >= maxTasks && Number.isFinite(maxTasks) ? "max-tasks" : null);
318
+ events.push({ kind: "supervisor.finished", at: at(deps), processed, succeeded, failed, skipped, idleReason });
319
+ const projection = reduceSupervisorJournal(events);
320
+ return { ok: failed === 0, dryRun: options.dryRun === true, plannedOrder: plannedAll, events, projection };
321
+ }
322
+ function collectBlockingClosure(taskId, badges) {
323
+ const closure = new Set;
324
+ const visit = (currentTaskId) => {
325
+ for (const blockerId of badges.get(currentTaskId)?.blockedBy ?? []) {
326
+ if (closure.has(blockerId))
327
+ continue;
328
+ closure.add(blockerId);
329
+ visit(blockerId);
330
+ }
331
+ };
332
+ visit(taskId);
333
+ return closure;
334
+ }
335
+ async function unblockTask(projectRoot, taskId, deps, options = {}) {
336
+ if (taskId === null)
337
+ return runSupervisorLoop(projectRoot, deps, { ...options, selectionPolicy: "max-unblock", maxTasks: 1 });
338
+ const tasks = await deps.listTasks(projectRoot);
339
+ const projected = tasks.map(toTaskDependencyProjection);
340
+ const badges = computeTaskDependencyBadges(projected);
341
+ const blockers = collectBlockingClosure(taskId, badges);
342
+ return runSupervisorLoop(projectRoot, deps, { ...options, candidateTaskIds: blockers, selectionPolicy: "rank", maxTasks: 1 });
343
+ }
344
+
88
345
  // packages/supervisor-plugin/src/cli.ts
89
346
  var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
90
347
  var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
@@ -154,7 +411,12 @@ function delay(ms) {
154
411
  return promise;
155
412
  }
156
413
  async function loadSupervisorClient() {
157
- return await import("@rig/client");
414
+ const [taskIo, runIo, dispatchIo] = await Promise.all([
415
+ import("@rig/core/task-io"),
416
+ import("@rig/run-worker/runs"),
417
+ import("@rig/runtime/control-plane/dispatch")
418
+ ]);
419
+ return { listTasks: taskIo.listTasks, listRuns: runIo.listRuns, dispatchRun: dispatchIo.dispatchRun };
158
420
  }
159
421
  function supervisorDeps(timeoutMs) {
160
422
  return {
@@ -197,8 +459,7 @@ async function executeLoop(context, args) {
197
459
  const stopWhen = takeOption(task.rest, "--stop-when");
198
460
  const timeout = takeOption(stopWhen.rest, "--timeout-ms");
199
461
  requireNoExtraArgs(timeout.rest, "rig loop [--task <id>] [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
200
- const { runSupervisorLoop } = await loadSupervisorClient();
201
- const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), {
462
+ const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
202
463
  maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
203
464
  concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
204
465
  ...task.value ? { candidateTaskIds: [task.value] } : {},
@@ -219,8 +480,7 @@ async function executeUnblock(context, args) {
219
480
  const timeout = takeOption(json.rest, "--timeout-ms");
220
481
  const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
221
482
  requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
222
- const { unblockTask } = await loadSupervisorClient();
223
- const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), { dryRun: dry.value || context.dryRun });
483
+ const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
224
484
  if (context.outputMode === "text") {
225
485
  if (json.value)
226
486
  printJson(result);
@@ -250,6 +510,26 @@ var supervisorCliCommands = [
250
510
  }
251
511
  ];
252
512
 
513
+ // packages/supervisor-plugin/src/panel.ts
514
+ import { RIG_RUN_STOP_PANEL_ACTION } from "@rig/contracts";
515
+ function buildSupervisorPanelPayload(context) {
516
+ const status = context.folded.status ?? "unknown";
517
+ const taskId = context.folded.record.taskId ?? context.taskIdAtStart;
518
+ const operatorActive = status === "running" || status === "validating" || status === "closing-out" || status === "needs-attention";
519
+ return {
520
+ status,
521
+ currentTask: taskId ? { id: taskId, title: context.runDisplayTitle } : null,
522
+ processed: context.folded.closeoutPhases.length,
523
+ succeeded: context.folded.closeoutPhases.filter((phase) => phase.outcome === "completed").length,
524
+ failed: context.folded.closeoutPhases.filter((phase) => phase.outcome === "failed").length,
525
+ skipped: 0,
526
+ plannedOrder: taskId ? [{ id: taskId, title: context.runDisplayTitle, status }] : [],
527
+ idleReason: operatorActive ? null : status,
528
+ stopActionId: operatorActive ? RIG_RUN_STOP_PANEL_ACTION : null,
529
+ closures: []
530
+ };
531
+ }
532
+
253
533
  // packages/supervisor-plugin/src/plugin.ts
254
534
  var SUPERVISOR_PLUGIN_NAME = "@rig/supervisor-plugin";
255
535
  var supervisorPlugin = definePlugin({
@@ -262,16 +542,20 @@ var supervisorPlugin = definePlugin({
262
542
  { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
263
543
  { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
264
544
  ],
265
- cliCommands: supervisorCliCommands.map(({ run: _run, ...metadata }) => metadata),
266
- stageMutations: [supervisorClosureStageMutation]
545
+ cliCommands: supervisorCliCommands,
546
+ panels: [
547
+ {
548
+ id: RIG_SUPERVISOR_PANEL_ID,
549
+ slot: RIG_CAPABILITY_PANEL_SLOT,
550
+ title: "Supervisor",
551
+ capabilityId: "run.supervisor",
552
+ description: "Live run status, closeout progress, and operator stop control.",
553
+ produce: (context) => buildSupervisorPanelPayload(context)
554
+ }
555
+ ],
556
+ stageMutations: [supervisorClosureStageMutation],
557
+ stages: [{ ...supervisorClosureStage, run: createDefaultSupervisorClosureStage() }]
267
558
  }
268
- }, {
269
- stages: { [SUPERVISOR_CLOSURE_STAGE_ID]: createDefaultSupervisorClosureStage() },
270
- featureCapabilities: [
271
- { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
272
- { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
273
- ],
274
- cliCommands: supervisorCliCommands
275
559
  });
276
560
  function createSupervisorPlugin() {
277
561
  return supervisorPlugin;
@@ -1,5 +1,5 @@
1
1
  import type { BlockerClassification, RunId, RunStatus, SupervisorEvent, SupervisorSelectionPolicy, SupervisorStopReason, TaskClosureSummary, TaskSummary } from "@rig/contracts";
2
- import { type TaskDependencyBadgeSummary } from "@rig/core/task-graph";
2
+ import { type TaskDependencyBadgeSummary } from "@rig/contracts";
3
3
  export interface SupervisorActiveRun {
4
4
  readonly taskId: TaskSummary["id"];
5
5
  readonly runId: RunId;
@@ -4,9 +4,9 @@ import {
4
4
  computeTaskDependencyBadges,
5
5
  disjointScope,
6
6
  isTaskTerminalStatus,
7
- rankReadyTasks,
8
7
  readTaskScope
9
- } from "@rig/core/task-graph";
8
+ } from "@rig/contracts";
9
+ import { rankReadyTasks } from "@rig/dependency-graph-plugin";
10
10
  var DEFAULT_STOP_WHEN = new Set(["all-done", "all-human-blocked", "source-error"]);
11
11
  var HUMAN_BLOCKER_CLASS = {
12
12
  "not-blocked": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/supervisor-plugin",
3
- "version": "0.0.6-alpha.155",
3
+ "version": "0.0.6-alpha.157",
4
4
  "type": "module",
5
5
  "description": "First-party autonomous supervisor loop plugin for Rig.",
6
6
  "license": "UNLICENSED",
@@ -41,9 +41,12 @@
41
41
  "module": "./dist/src/index.js",
42
42
  "types": "./dist/src/index.d.ts",
43
43
  "dependencies": {
44
- "@rig/client": "npm:@h-rig/client@0.0.6-alpha.155",
45
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.155",
46
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.155",
44
+ "@rig/blocker-classifier-plugin": "npm:@h-rig/blocker-classifier-plugin@0.0.6-alpha.157",
45
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.157",
46
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.157",
47
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.157",
48
+ "@rig/dependency-graph-plugin": "npm:@h-rig/dependency-graph-plugin@0.0.6-alpha.157",
49
+ "@rig/run-worker": "npm:@h-rig/run-worker@0.0.6-alpha.157",
47
50
  "effect": "4.0.0-beta.90"
48
51
  }
49
52
  }