@h-rig/supervisor-plugin 0.0.6-alpha.136 → 0.0.6-alpha.138

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.
@@ -0,0 +1,29 @@
1
+ import type { RuntimeCliContext } from "@rig/core";
2
+ export declare const SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
3
+ export declare const SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
4
+ type CommandOutcome = {
5
+ readonly ok: boolean;
6
+ readonly group: string;
7
+ readonly command: string;
8
+ readonly details?: Record<string, unknown>;
9
+ };
10
+ export declare function executeLoop(context: RuntimeCliContext, args: readonly string[]): Promise<CommandOutcome>;
11
+ export declare function executeUnblock(context: RuntimeCliContext, args: readonly string[]): Promise<CommandOutcome>;
12
+ export declare const supervisorCliCommands: readonly [{
13
+ readonly id: "supervisor.loop";
14
+ readonly family: "loop";
15
+ readonly command: "rig loop [--max-tasks <n>] [--concurrency <n>] [--dry-run]";
16
+ readonly description: "Run the autonomous supervisor loop over ready tasks.";
17
+ readonly usage: "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]";
18
+ readonly projectRequired: true;
19
+ readonly run: typeof executeLoop;
20
+ }, {
21
+ readonly id: "supervisor.unblock";
22
+ readonly family: "unblock";
23
+ readonly command: "rig unblock [task-id] [--dry-run]";
24
+ readonly description: "Plan or dispatch work that unblocks a blocked task.";
25
+ readonly usage: "rig unblock [task-id] [--dry-run] [--json]";
26
+ readonly projectRequired: true;
27
+ readonly run: typeof executeUnblock;
28
+ }];
29
+ export {};
@@ -0,0 +1,209 @@
1
+ // @bun
2
+ // packages/supervisor-plugin/src/cli.ts
3
+ import {
4
+ dispatchRun,
5
+ listRuns,
6
+ listTasks,
7
+ runSupervisorLoop,
8
+ unblockTask
9
+ } from "@rig/client";
10
+
11
+ // packages/supervisor-plugin/src/awaiter.ts
12
+ var TERMINAL_RUN_STATUSES = {
13
+ created: false,
14
+ queued: false,
15
+ preparing: false,
16
+ running: false,
17
+ "waiting-approval": false,
18
+ "waiting-user-input": false,
19
+ paused: false,
20
+ validating: false,
21
+ reviewing: false,
22
+ "closing-out": false,
23
+ "needs-attention": true,
24
+ completed: true,
25
+ failed: true,
26
+ stopped: true
27
+ };
28
+ async function awaitTerminalRun(options) {
29
+ const startedAt = Date.now();
30
+ const pollMs = Math.max(0, Math.floor(options.pollMs ?? 5000));
31
+ while (true) {
32
+ const snapshot = await options.readStatus(options.runId);
33
+ if (snapshot && TERMINAL_RUN_STATUSES[snapshot.status]) {
34
+ return {
35
+ runId: options.runId,
36
+ status: snapshot.status,
37
+ failed: snapshot.status !== "completed"
38
+ };
39
+ }
40
+ if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
41
+ return { runId: options.runId, status: "needs-attention", failed: true };
42
+ }
43
+ await options.waitForChange(pollMs);
44
+ }
45
+ }
46
+
47
+ // packages/supervisor-plugin/src/cli.ts
48
+ var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
49
+ var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
50
+ function printJson(value) {
51
+ console.log(JSON.stringify(value, null, 2));
52
+ }
53
+ function takeFlag(args, flag) {
54
+ const rest = [...args];
55
+ const index = rest.indexOf(flag);
56
+ if (index < 0)
57
+ return { value: false, rest };
58
+ rest.splice(index, 1);
59
+ return { value: true, rest };
60
+ }
61
+ function takeOption(args, flag) {
62
+ const rest = [...args];
63
+ const index = rest.indexOf(flag);
64
+ if (index < 0)
65
+ return { rest };
66
+ const value = rest[index + 1];
67
+ if (!value || value.startsWith("-"))
68
+ throw new Error(`${flag} requires a value.`);
69
+ rest.splice(index, 2);
70
+ return { value, rest };
71
+ }
72
+ function requireNoExtraArgs(args, usage) {
73
+ if (args.length > 0)
74
+ throw new Error(`Unexpected argument: ${args[0]}
75
+ Usage: ${usage}`);
76
+ }
77
+ function parsePositiveInt(value, flag, fallback) {
78
+ if (value === undefined)
79
+ return fallback;
80
+ const parsed = Number.parseInt(value, 10);
81
+ if (!Number.isFinite(parsed) || parsed <= 0 || String(parsed) !== value.trim()) {
82
+ throw new Error(`${flag} must be a positive integer.`);
83
+ }
84
+ return parsed;
85
+ }
86
+ function parseCsv(value) {
87
+ return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
88
+ }
89
+ function asRunStatus(value) {
90
+ switch (value) {
91
+ case "created":
92
+ case "queued":
93
+ case "preparing":
94
+ case "running":
95
+ case "waiting-approval":
96
+ case "waiting-user-input":
97
+ case "paused":
98
+ case "validating":
99
+ case "reviewing":
100
+ case "closing-out":
101
+ case "needs-attention":
102
+ case "completed":
103
+ case "failed":
104
+ case "stopped":
105
+ return value;
106
+ default:
107
+ return "needs-attention";
108
+ }
109
+ }
110
+ function delay(ms) {
111
+ const { promise, resolve } = Promise.withResolvers();
112
+ setTimeout(resolve, ms);
113
+ return promise;
114
+ }
115
+ function supervisorDeps(timeoutMs) {
116
+ return {
117
+ listTasks,
118
+ listRuns,
119
+ dispatchRun,
120
+ awaitRunTerminal: async (projectRoot, runId) => {
121
+ const awaitedRunId = runId;
122
+ return awaitTerminalRun({
123
+ runId: awaitedRunId,
124
+ timeoutMs,
125
+ readStatus: async (id) => {
126
+ const run = (await listRuns(projectRoot)).find((candidate) => candidate.runId === id);
127
+ return run ? { runId: id, status: asRunStatus(run.status), ...run.errorSummary ? { diagnostic: run.errorSummary } : {} } : null;
128
+ },
129
+ waitForChange: delay
130
+ });
131
+ }
132
+ };
133
+ }
134
+ function formatSupervisorResult(result) {
135
+ return [
136
+ `ok: ${result.ok}`,
137
+ `dry-run: ${result.dryRun}`,
138
+ `planned: ${result.plannedOrder.length > 0 ? result.plannedOrder.join(", ") : "none"}`,
139
+ `processed: ${result.projection.processed}`,
140
+ `succeeded: ${result.projection.succeeded}`,
141
+ `failed: ${result.projection.failed}`,
142
+ result.projection.idleReason ? `idle: ${result.projection.idleReason}` : null
143
+ ].filter((line) => line !== null).join(`
144
+ `);
145
+ }
146
+ async function executeLoop(context, args) {
147
+ const dry = takeFlag(args, "--dry-run");
148
+ const json = takeFlag(dry.rest, "--json");
149
+ const maxTasks = takeOption(json.rest, "--max-tasks");
150
+ const concurrency = takeOption(maxTasks.rest, "--concurrency");
151
+ const stopWhen = takeOption(concurrency.rest, "--stop-when");
152
+ const timeout = takeOption(stopWhen.rest, "--timeout-ms");
153
+ requireNoExtraArgs(timeout.rest, "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
154
+ const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
155
+ maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
156
+ concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
157
+ dryRun: dry.value || context.dryRun
158
+ });
159
+ const details = { ...result, stopWhen: parseCsv(stopWhen.value) };
160
+ if (context.outputMode === "text") {
161
+ if (json.value)
162
+ printJson(details);
163
+ else
164
+ console.log(formatSupervisorResult(result));
165
+ }
166
+ return { ok: result.ok, group: "loop", command: "run", details };
167
+ }
168
+ async function executeUnblock(context, args) {
169
+ const dry = takeFlag(args, "--dry-run");
170
+ const json = takeFlag(dry.rest, "--json");
171
+ const timeout = takeOption(json.rest, "--timeout-ms");
172
+ const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
173
+ requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
174
+ const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
175
+ if (context.outputMode === "text") {
176
+ if (json.value)
177
+ printJson(result);
178
+ else
179
+ console.log(formatSupervisorResult(result));
180
+ }
181
+ return { ok: result.ok, group: "unblock", command: "run", details: result };
182
+ }
183
+ var supervisorCliCommands = [
184
+ {
185
+ id: SUPERVISOR_LOOP_CLI_ID,
186
+ family: "loop",
187
+ command: "rig loop [--max-tasks <n>] [--concurrency <n>] [--dry-run]",
188
+ description: "Run the autonomous supervisor loop over ready tasks.",
189
+ usage: "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]",
190
+ projectRequired: true,
191
+ run: executeLoop
192
+ },
193
+ {
194
+ id: SUPERVISOR_UNBLOCK_CLI_ID,
195
+ family: "unblock",
196
+ command: "rig unblock [task-id] [--dry-run]",
197
+ description: "Plan or dispatch work that unblocks a blocked task.",
198
+ usage: "rig unblock [task-id] [--dry-run] [--json]",
199
+ projectRequired: true,
200
+ run: executeUnblock
201
+ }
202
+ ];
203
+ export {
204
+ supervisorCliCommands,
205
+ executeUnblock,
206
+ executeLoop,
207
+ SUPERVISOR_UNBLOCK_CLI_ID,
208
+ SUPERVISOR_LOOP_CLI_ID
209
+ };
@@ -1,8 +1,22 @@
1
- import type { StageContext, StageMutation, StageResult, TaskClosureSummary } from "@rig/contracts";
1
+ import type { StageContext, StageMutation, StageResult, StageRun, TaskClosureSummary } from "@rig/contracts";
2
2
  export declare const SUPERVISOR_CLOSURE_STAGE_ID = "supervisor-closure-observer";
3
3
  export interface ClosureSummaryPort {
4
4
  summarize(ctx: StageContext): Promise<TaskClosureSummary | null> | TaskClosureSummary | null;
5
5
  record?(summary: TaskClosureSummary): Promise<void> | void;
6
6
  }
7
7
  export declare function createClosureStage(port: ClosureSummaryPort): (ctx: StageContext) => Promise<StageResult>;
8
+ /** Live closeout state threaded into `ctx.metadata.closeoutState`. */
9
+ export interface ClosureCloseoutState {
10
+ readonly prUrl?: string | null;
11
+ readonly mergeGate?: "passed" | "blocked" | "skipped" | null;
12
+ readonly merged?: boolean;
13
+ readonly unblockedTaskIds?: readonly string[];
14
+ }
15
+ export declare function createSupervisorClosureStage(): StageRun;
16
+ /**
17
+ * Default closure stage: appends a `supervisor.outcome` event carrying the
18
+ * {@link TaskClosureSummary} to `<projectRoot>/.rig/supervisor.jsonl`, so the
19
+ * supervisor projection's `closures` are populated by real closeouts.
20
+ */
21
+ export declare function createDefaultSupervisorClosureStage(): StageRun;
8
22
  export declare const supervisorClosureStageMutation: StageMutation;
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // packages/supervisor-plugin/src/closureStage.ts
3
5
  var SUPERVISOR_CLOSURE_STAGE_ID = "supervisor-closure-observer";
4
6
  function createClosureStage(port) {
@@ -11,6 +13,39 @@ function createClosureStage(port) {
11
13
  return { kind: "continue", ctx: { ...ctx, metadata } };
12
14
  };
13
15
  }
16
+ function summarizeClosure(ctx) {
17
+ const state = (ctx.metadata ?? {}).closeoutState ?? null;
18
+ const taskId = typeof ctx.taskId === "string" ? ctx.taskId : null;
19
+ if (!state || !taskId)
20
+ return null;
21
+ return {
22
+ taskId,
23
+ runId: ctx.runId,
24
+ prUrl: state.prUrl ?? null,
25
+ mergeGate: state.mergeGate ?? null,
26
+ acceptanceChecked: [],
27
+ unblockedTaskIds: state.unblockedTaskIds ?? []
28
+ };
29
+ }
30
+ function createSupervisorClosureStage() {
31
+ return createClosureStage({ summarize: summarizeClosure });
32
+ }
33
+ function createDefaultSupervisorClosureStage() {
34
+ return async (ctx) => {
35
+ const summary = summarizeClosure(ctx);
36
+ const projectRoot = (ctx.metadata ?? {}).projectRoot;
37
+ if (summary && typeof projectRoot === "string") {
38
+ const { appendFileSync, mkdirSync } = await import("fs");
39
+ const { join } = await import("path");
40
+ try {
41
+ mkdirSync(join(projectRoot, ".rig"), { recursive: true });
42
+ appendFileSync(join(projectRoot, ".rig", "supervisor.jsonl"), `${JSON.stringify({ kind: "supervisor.outcome", at: new Date().toISOString(), taskId: summary.taskId, runId: summary.runId, status: summary.mergeGate === "passed" ? "completed" : "needs-attention", failed: false, unblockedTaskIds: summary.unblockedTaskIds, closure: summary })}
43
+ `);
44
+ } catch {}
45
+ }
46
+ return { kind: "continue", ctx };
47
+ };
48
+ }
14
49
  var supervisorClosureStageMutation = {
15
50
  op: "insert",
16
51
  contributedBy: "@rig/supervisor-plugin",
@@ -25,6 +60,8 @@ var supervisorClosureStageMutation = {
25
60
  };
26
61
  export {
27
62
  supervisorClosureStageMutation,
63
+ createSupervisorClosureStage,
64
+ createDefaultSupervisorClosureStage,
28
65
  createClosureStage,
29
66
  SUPERVISOR_CLOSURE_STAGE_ID
30
67
  };
@@ -1,5 +1,6 @@
1
1
  export * from "./awaiter";
2
2
  export * from "./closureStage";
3
+ export * from "./cli";
3
4
  export * from "./journal";
4
5
  export * from "./plugin";
5
6
  export * from "./supervisor";
package/dist/src/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // packages/supervisor-plugin/src/awaiter.ts
3
5
  var TERMINAL_RUN_STATUSES = {
4
6
  created: false,
@@ -46,6 +48,39 @@ function createClosureStage(port) {
46
48
  return { kind: "continue", ctx: { ...ctx, metadata } };
47
49
  };
48
50
  }
51
+ function summarizeClosure(ctx) {
52
+ const state = (ctx.metadata ?? {}).closeoutState ?? null;
53
+ const taskId = typeof ctx.taskId === "string" ? ctx.taskId : null;
54
+ if (!state || !taskId)
55
+ return null;
56
+ return {
57
+ taskId,
58
+ runId: ctx.runId,
59
+ prUrl: state.prUrl ?? null,
60
+ mergeGate: state.mergeGate ?? null,
61
+ acceptanceChecked: [],
62
+ unblockedTaskIds: state.unblockedTaskIds ?? []
63
+ };
64
+ }
65
+ function createSupervisorClosureStage() {
66
+ return createClosureStage({ summarize: summarizeClosure });
67
+ }
68
+ function createDefaultSupervisorClosureStage() {
69
+ return async (ctx) => {
70
+ const summary = summarizeClosure(ctx);
71
+ const projectRoot = (ctx.metadata ?? {}).projectRoot;
72
+ if (summary && typeof projectRoot === "string") {
73
+ const { appendFileSync, mkdirSync } = await import("fs");
74
+ const { join } = await import("path");
75
+ try {
76
+ mkdirSync(join(projectRoot, ".rig"), { recursive: true });
77
+ appendFileSync(join(projectRoot, ".rig", "supervisor.jsonl"), `${JSON.stringify({ kind: "supervisor.outcome", at: new Date().toISOString(), taskId: summary.taskId, runId: summary.runId, status: summary.mergeGate === "passed" ? "completed" : "needs-attention", failed: false, unblockedTaskIds: summary.unblockedTaskIds, closure: summary })}
78
+ `);
79
+ } catch {}
80
+ }
81
+ return { kind: "continue", ctx };
82
+ };
83
+ }
49
84
  var supervisorClosureStageMutation = {
50
85
  op: "insert",
51
86
  contributedBy: "@rig/supervisor-plugin",
@@ -58,6 +93,169 @@ var supervisorClosureStageMutation = {
58
93
  protected: false
59
94
  }
60
95
  };
96
+ // packages/supervisor-plugin/src/cli.ts
97
+ import {
98
+ dispatchRun,
99
+ listRuns,
100
+ listTasks,
101
+ runSupervisorLoop,
102
+ unblockTask
103
+ } from "@rig/client";
104
+ var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
105
+ var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
106
+ function printJson(value) {
107
+ console.log(JSON.stringify(value, null, 2));
108
+ }
109
+ function takeFlag(args, flag) {
110
+ const rest = [...args];
111
+ const index = rest.indexOf(flag);
112
+ if (index < 0)
113
+ return { value: false, rest };
114
+ rest.splice(index, 1);
115
+ return { value: true, rest };
116
+ }
117
+ function takeOption(args, flag) {
118
+ const rest = [...args];
119
+ const index = rest.indexOf(flag);
120
+ if (index < 0)
121
+ return { rest };
122
+ const value = rest[index + 1];
123
+ if (!value || value.startsWith("-"))
124
+ throw new Error(`${flag} requires a value.`);
125
+ rest.splice(index, 2);
126
+ return { value, rest };
127
+ }
128
+ function requireNoExtraArgs(args, usage) {
129
+ if (args.length > 0)
130
+ throw new Error(`Unexpected argument: ${args[0]}
131
+ Usage: ${usage}`);
132
+ }
133
+ function parsePositiveInt(value, flag, fallback) {
134
+ if (value === undefined)
135
+ return fallback;
136
+ const parsed = Number.parseInt(value, 10);
137
+ if (!Number.isFinite(parsed) || parsed <= 0 || String(parsed) !== value.trim()) {
138
+ throw new Error(`${flag} must be a positive integer.`);
139
+ }
140
+ return parsed;
141
+ }
142
+ function parseCsv(value) {
143
+ return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
144
+ }
145
+ function asRunStatus(value) {
146
+ switch (value) {
147
+ case "created":
148
+ case "queued":
149
+ case "preparing":
150
+ case "running":
151
+ case "waiting-approval":
152
+ case "waiting-user-input":
153
+ case "paused":
154
+ case "validating":
155
+ case "reviewing":
156
+ case "closing-out":
157
+ case "needs-attention":
158
+ case "completed":
159
+ case "failed":
160
+ case "stopped":
161
+ return value;
162
+ default:
163
+ return "needs-attention";
164
+ }
165
+ }
166
+ function delay(ms) {
167
+ const { promise, resolve } = Promise.withResolvers();
168
+ setTimeout(resolve, ms);
169
+ return promise;
170
+ }
171
+ function supervisorDeps(timeoutMs) {
172
+ return {
173
+ listTasks,
174
+ listRuns,
175
+ dispatchRun,
176
+ awaitRunTerminal: async (projectRoot, runId) => {
177
+ const awaitedRunId = runId;
178
+ return awaitTerminalRun({
179
+ runId: awaitedRunId,
180
+ timeoutMs,
181
+ readStatus: async (id) => {
182
+ const run = (await listRuns(projectRoot)).find((candidate) => candidate.runId === id);
183
+ return run ? { runId: id, status: asRunStatus(run.status), ...run.errorSummary ? { diagnostic: run.errorSummary } : {} } : null;
184
+ },
185
+ waitForChange: delay
186
+ });
187
+ }
188
+ };
189
+ }
190
+ function formatSupervisorResult(result) {
191
+ return [
192
+ `ok: ${result.ok}`,
193
+ `dry-run: ${result.dryRun}`,
194
+ `planned: ${result.plannedOrder.length > 0 ? result.plannedOrder.join(", ") : "none"}`,
195
+ `processed: ${result.projection.processed}`,
196
+ `succeeded: ${result.projection.succeeded}`,
197
+ `failed: ${result.projection.failed}`,
198
+ result.projection.idleReason ? `idle: ${result.projection.idleReason}` : null
199
+ ].filter((line) => line !== null).join(`
200
+ `);
201
+ }
202
+ async function executeLoop(context, args) {
203
+ const dry = takeFlag(args, "--dry-run");
204
+ const json = takeFlag(dry.rest, "--json");
205
+ const maxTasks = takeOption(json.rest, "--max-tasks");
206
+ const concurrency = takeOption(maxTasks.rest, "--concurrency");
207
+ const stopWhen = takeOption(concurrency.rest, "--stop-when");
208
+ const timeout = takeOption(stopWhen.rest, "--timeout-ms");
209
+ requireNoExtraArgs(timeout.rest, "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
210
+ const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
211
+ maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
212
+ concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
213
+ dryRun: dry.value || context.dryRun
214
+ });
215
+ const details = { ...result, stopWhen: parseCsv(stopWhen.value) };
216
+ if (context.outputMode === "text") {
217
+ if (json.value)
218
+ printJson(details);
219
+ else
220
+ console.log(formatSupervisorResult(result));
221
+ }
222
+ return { ok: result.ok, group: "loop", command: "run", details };
223
+ }
224
+ async function executeUnblock(context, args) {
225
+ const dry = takeFlag(args, "--dry-run");
226
+ const json = takeFlag(dry.rest, "--json");
227
+ const timeout = takeOption(json.rest, "--timeout-ms");
228
+ const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
229
+ requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
230
+ const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
231
+ if (context.outputMode === "text") {
232
+ if (json.value)
233
+ printJson(result);
234
+ else
235
+ console.log(formatSupervisorResult(result));
236
+ }
237
+ return { ok: result.ok, group: "unblock", command: "run", details: result };
238
+ }
239
+ var supervisorCliCommands = [
240
+ {
241
+ id: SUPERVISOR_LOOP_CLI_ID,
242
+ family: "loop",
243
+ command: "rig loop [--max-tasks <n>] [--concurrency <n>] [--dry-run]",
244
+ description: "Run the autonomous supervisor loop over ready tasks.",
245
+ usage: "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]",
246
+ projectRequired: true,
247
+ run: executeLoop
248
+ },
249
+ {
250
+ id: SUPERVISOR_UNBLOCK_CLI_ID,
251
+ family: "unblock",
252
+ command: "rig unblock [task-id] [--dry-run]",
253
+ description: "Plan or dispatch work that unblocks a blocked task.",
254
+ usage: "rig unblock [task-id] [--dry-run] [--json]",
255
+ projectRequired: true,
256
+ run: executeUnblock
257
+ }
258
+ ];
61
259
  // packages/supervisor-plugin/src/journal.ts
62
260
  import { mkdir, readFile, writeFile } from "fs/promises";
63
261
  import { dirname } from "path";
@@ -124,9 +322,24 @@ var supervisorPlugin = definePlugin({
124
322
  provides: [],
125
323
  requires: ["journal", "transport"],
126
324
  contributes: {
325
+ capabilities: [
326
+ { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
327
+ { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
328
+ ],
329
+ cliCommands: supervisorCliCommands.map(({ run: _run, ...metadata }) => metadata),
127
330
  stageMutations: [supervisorClosureStageMutation]
128
331
  }
332
+ }, {
333
+ stages: { [SUPERVISOR_CLOSURE_STAGE_ID]: createDefaultSupervisorClosureStage() },
334
+ featureCapabilities: [
335
+ { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
336
+ { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
337
+ ],
338
+ cliCommands: supervisorCliCommands
129
339
  });
340
+ function createSupervisorPlugin() {
341
+ return supervisorPlugin;
342
+ }
130
343
  // packages/supervisor-plugin/src/supervisor.ts
131
344
  import {
132
345
  computeTaskDependencyBadges,
@@ -298,13 +511,21 @@ async function runSupervisor(ctx, options = {}) {
298
511
  export {
299
512
  supervisorPlugin,
300
513
  supervisorClosureStageMutation,
514
+ supervisorCliCommands,
301
515
  runSupervisor,
302
516
  parseSupervisorJournal,
517
+ executeUnblock,
518
+ executeLoop,
519
+ createSupervisorPlugin,
303
520
  createSupervisorJournal,
521
+ createSupervisorClosureStage,
304
522
  createInMemorySupervisorJournalStore,
305
523
  createFileSupervisorJournal,
524
+ createDefaultSupervisorClosureStage,
306
525
  createClosureStage,
307
526
  awaitTerminalRun,
527
+ SUPERVISOR_UNBLOCK_CLI_ID,
308
528
  SUPERVISOR_PLUGIN_NAME,
529
+ SUPERVISOR_LOOP_CLI_ID,
309
530
  SUPERVISOR_CLOSURE_STAGE_ID
310
531
  };
@@ -1,3 +1,4 @@
1
1
  export declare const SUPERVISOR_PLUGIN_NAME = "@rig/supervisor-plugin";
2
2
  export declare const supervisorPlugin: import("@rig/core").RigPluginWithRuntime;
3
+ export declare function createSupervisorPlugin(): import("@rig/core").RigPluginWithRuntime;
3
4
  export default supervisorPlugin;
@@ -1,9 +1,41 @@
1
1
  // @bun
2
+ var __require = import.meta.require;
3
+
2
4
  // packages/supervisor-plugin/src/plugin.ts
3
5
  import { definePlugin } from "@rig/core";
4
6
 
5
7
  // packages/supervisor-plugin/src/closureStage.ts
6
8
  var SUPERVISOR_CLOSURE_STAGE_ID = "supervisor-closure-observer";
9
+ function summarizeClosure(ctx) {
10
+ const state = (ctx.metadata ?? {}).closeoutState ?? null;
11
+ const taskId = typeof ctx.taskId === "string" ? ctx.taskId : null;
12
+ if (!state || !taskId)
13
+ return null;
14
+ return {
15
+ taskId,
16
+ runId: ctx.runId,
17
+ prUrl: state.prUrl ?? null,
18
+ mergeGate: state.mergeGate ?? null,
19
+ acceptanceChecked: [],
20
+ unblockedTaskIds: state.unblockedTaskIds ?? []
21
+ };
22
+ }
23
+ function createDefaultSupervisorClosureStage() {
24
+ return async (ctx) => {
25
+ const summary = summarizeClosure(ctx);
26
+ const projectRoot = (ctx.metadata ?? {}).projectRoot;
27
+ if (summary && typeof projectRoot === "string") {
28
+ const { appendFileSync, mkdirSync } = await import("fs");
29
+ const { join } = await import("path");
30
+ try {
31
+ mkdirSync(join(projectRoot, ".rig"), { recursive: true });
32
+ appendFileSync(join(projectRoot, ".rig", "supervisor.jsonl"), `${JSON.stringify({ kind: "supervisor.outcome", at: new Date().toISOString(), taskId: summary.taskId, runId: summary.runId, status: summary.mergeGate === "passed" ? "completed" : "needs-attention", failed: false, unblockedTaskIds: summary.unblockedTaskIds, closure: summary })}
33
+ `);
34
+ } catch {}
35
+ }
36
+ return { kind: "continue", ctx };
37
+ };
38
+ }
7
39
  var supervisorClosureStageMutation = {
8
40
  op: "insert",
9
41
  contributedBy: "@rig/supervisor-plugin",
@@ -17,6 +49,208 @@ var supervisorClosureStageMutation = {
17
49
  }
18
50
  };
19
51
 
52
+ // packages/supervisor-plugin/src/cli.ts
53
+ import {
54
+ dispatchRun,
55
+ listRuns,
56
+ listTasks,
57
+ runSupervisorLoop,
58
+ unblockTask
59
+ } from "@rig/client";
60
+
61
+ // packages/supervisor-plugin/src/awaiter.ts
62
+ var TERMINAL_RUN_STATUSES = {
63
+ created: false,
64
+ queued: false,
65
+ preparing: false,
66
+ running: false,
67
+ "waiting-approval": false,
68
+ "waiting-user-input": false,
69
+ paused: false,
70
+ validating: false,
71
+ reviewing: false,
72
+ "closing-out": false,
73
+ "needs-attention": true,
74
+ completed: true,
75
+ failed: true,
76
+ stopped: true
77
+ };
78
+ async function awaitTerminalRun(options) {
79
+ const startedAt = Date.now();
80
+ const pollMs = Math.max(0, Math.floor(options.pollMs ?? 5000));
81
+ while (true) {
82
+ const snapshot = await options.readStatus(options.runId);
83
+ if (snapshot && TERMINAL_RUN_STATUSES[snapshot.status]) {
84
+ return {
85
+ runId: options.runId,
86
+ status: snapshot.status,
87
+ failed: snapshot.status !== "completed"
88
+ };
89
+ }
90
+ if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
91
+ return { runId: options.runId, status: "needs-attention", failed: true };
92
+ }
93
+ await options.waitForChange(pollMs);
94
+ }
95
+ }
96
+
97
+ // packages/supervisor-plugin/src/cli.ts
98
+ var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
99
+ var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
100
+ function printJson(value) {
101
+ console.log(JSON.stringify(value, null, 2));
102
+ }
103
+ function takeFlag(args, flag) {
104
+ const rest = [...args];
105
+ const index = rest.indexOf(flag);
106
+ if (index < 0)
107
+ return { value: false, rest };
108
+ rest.splice(index, 1);
109
+ return { value: true, rest };
110
+ }
111
+ function takeOption(args, flag) {
112
+ const rest = [...args];
113
+ const index = rest.indexOf(flag);
114
+ if (index < 0)
115
+ return { rest };
116
+ const value = rest[index + 1];
117
+ if (!value || value.startsWith("-"))
118
+ throw new Error(`${flag} requires a value.`);
119
+ rest.splice(index, 2);
120
+ return { value, rest };
121
+ }
122
+ function requireNoExtraArgs(args, usage) {
123
+ if (args.length > 0)
124
+ throw new Error(`Unexpected argument: ${args[0]}
125
+ Usage: ${usage}`);
126
+ }
127
+ function parsePositiveInt(value, flag, fallback) {
128
+ if (value === undefined)
129
+ return fallback;
130
+ const parsed = Number.parseInt(value, 10);
131
+ if (!Number.isFinite(parsed) || parsed <= 0 || String(parsed) !== value.trim()) {
132
+ throw new Error(`${flag} must be a positive integer.`);
133
+ }
134
+ return parsed;
135
+ }
136
+ function parseCsv(value) {
137
+ return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
138
+ }
139
+ function asRunStatus(value) {
140
+ switch (value) {
141
+ case "created":
142
+ case "queued":
143
+ case "preparing":
144
+ case "running":
145
+ case "waiting-approval":
146
+ case "waiting-user-input":
147
+ case "paused":
148
+ case "validating":
149
+ case "reviewing":
150
+ case "closing-out":
151
+ case "needs-attention":
152
+ case "completed":
153
+ case "failed":
154
+ case "stopped":
155
+ return value;
156
+ default:
157
+ return "needs-attention";
158
+ }
159
+ }
160
+ function delay(ms) {
161
+ const { promise, resolve } = Promise.withResolvers();
162
+ setTimeout(resolve, ms);
163
+ return promise;
164
+ }
165
+ function supervisorDeps(timeoutMs) {
166
+ return {
167
+ listTasks,
168
+ listRuns,
169
+ dispatchRun,
170
+ awaitRunTerminal: async (projectRoot, runId) => {
171
+ const awaitedRunId = runId;
172
+ return awaitTerminalRun({
173
+ runId: awaitedRunId,
174
+ timeoutMs,
175
+ readStatus: async (id) => {
176
+ const run = (await listRuns(projectRoot)).find((candidate) => candidate.runId === id);
177
+ return run ? { runId: id, status: asRunStatus(run.status), ...run.errorSummary ? { diagnostic: run.errorSummary } : {} } : null;
178
+ },
179
+ waitForChange: delay
180
+ });
181
+ }
182
+ };
183
+ }
184
+ function formatSupervisorResult(result) {
185
+ return [
186
+ `ok: ${result.ok}`,
187
+ `dry-run: ${result.dryRun}`,
188
+ `planned: ${result.plannedOrder.length > 0 ? result.plannedOrder.join(", ") : "none"}`,
189
+ `processed: ${result.projection.processed}`,
190
+ `succeeded: ${result.projection.succeeded}`,
191
+ `failed: ${result.projection.failed}`,
192
+ result.projection.idleReason ? `idle: ${result.projection.idleReason}` : null
193
+ ].filter((line) => line !== null).join(`
194
+ `);
195
+ }
196
+ async function executeLoop(context, args) {
197
+ const dry = takeFlag(args, "--dry-run");
198
+ const json = takeFlag(dry.rest, "--json");
199
+ const maxTasks = takeOption(json.rest, "--max-tasks");
200
+ const concurrency = takeOption(maxTasks.rest, "--concurrency");
201
+ const stopWhen = takeOption(concurrency.rest, "--stop-when");
202
+ const timeout = takeOption(stopWhen.rest, "--timeout-ms");
203
+ requireNoExtraArgs(timeout.rest, "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
204
+ const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
205
+ maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
206
+ concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
207
+ dryRun: dry.value || context.dryRun
208
+ });
209
+ const details = { ...result, stopWhen: parseCsv(stopWhen.value) };
210
+ if (context.outputMode === "text") {
211
+ if (json.value)
212
+ printJson(details);
213
+ else
214
+ console.log(formatSupervisorResult(result));
215
+ }
216
+ return { ok: result.ok, group: "loop", command: "run", details };
217
+ }
218
+ async function executeUnblock(context, args) {
219
+ const dry = takeFlag(args, "--dry-run");
220
+ const json = takeFlag(dry.rest, "--json");
221
+ const timeout = takeOption(json.rest, "--timeout-ms");
222
+ const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
223
+ requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
224
+ const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
225
+ if (context.outputMode === "text") {
226
+ if (json.value)
227
+ printJson(result);
228
+ else
229
+ console.log(formatSupervisorResult(result));
230
+ }
231
+ return { ok: result.ok, group: "unblock", command: "run", details: result };
232
+ }
233
+ var supervisorCliCommands = [
234
+ {
235
+ id: SUPERVISOR_LOOP_CLI_ID,
236
+ family: "loop",
237
+ command: "rig loop [--max-tasks <n>] [--concurrency <n>] [--dry-run]",
238
+ description: "Run the autonomous supervisor loop over ready tasks.",
239
+ usage: "rig loop [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]",
240
+ projectRequired: true,
241
+ run: executeLoop
242
+ },
243
+ {
244
+ id: SUPERVISOR_UNBLOCK_CLI_ID,
245
+ family: "unblock",
246
+ command: "rig unblock [task-id] [--dry-run]",
247
+ description: "Plan or dispatch work that unblocks a blocked task.",
248
+ usage: "rig unblock [task-id] [--dry-run] [--json]",
249
+ projectRequired: true,
250
+ run: executeUnblock
251
+ }
252
+ ];
253
+
20
254
  // packages/supervisor-plugin/src/plugin.ts
21
255
  var SUPERVISOR_PLUGIN_NAME = "@rig/supervisor-plugin";
22
256
  var supervisorPlugin = definePlugin({
@@ -25,12 +259,28 @@ var supervisorPlugin = definePlugin({
25
259
  provides: [],
26
260
  requires: ["journal", "transport"],
27
261
  contributes: {
262
+ capabilities: [
263
+ { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
264
+ { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
265
+ ],
266
+ cliCommands: supervisorCliCommands.map(({ run: _run, ...metadata }) => metadata),
28
267
  stageMutations: [supervisorClosureStageMutation]
29
268
  }
269
+ }, {
270
+ stages: { [SUPERVISOR_CLOSURE_STAGE_ID]: createDefaultSupervisorClosureStage() },
271
+ featureCapabilities: [
272
+ { id: "supervisor.loop", title: "Supervisor loop", commandId: SUPERVISOR_LOOP_CLI_ID },
273
+ { id: "supervisor.unblock", title: "Supervisor unblock", commandId: SUPERVISOR_UNBLOCK_CLI_ID }
274
+ ],
275
+ cliCommands: supervisorCliCommands
30
276
  });
277
+ function createSupervisorPlugin() {
278
+ return supervisorPlugin;
279
+ }
31
280
  var plugin_default = supervisorPlugin;
32
281
  export {
33
282
  supervisorPlugin,
34
283
  plugin_default as default,
284
+ createSupervisorPlugin,
35
285
  SUPERVISOR_PLUGIN_NAME
36
286
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/supervisor-plugin",
3
- "version": "0.0.6-alpha.136",
3
+ "version": "0.0.6-alpha.138",
4
4
  "type": "module",
5
5
  "description": "First-party autonomous supervisor loop plugin for Rig.",
6
6
  "license": "UNLICENSED",
@@ -41,8 +41,9 @@
41
41
  "module": "./dist/src/index.js",
42
42
  "types": "./dist/src/index.d.ts",
43
43
  "dependencies": {
44
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.136",
45
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.136",
44
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.138",
45
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.138",
46
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.138",
46
47
  "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b"
47
48
  }
48
49
  }