harnery 0.15.0 → 0.17.0

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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/supervisor.d.ts.map +1 -1
  3. package/dist/commands/supervisor.js +393 -4
  4. package/dist/core/supervisor/index.d.ts +4 -0
  5. package/dist/core/supervisor/index.d.ts.map +1 -1
  6. package/dist/core/supervisor/index.js +4 -0
  7. package/dist/core/supervisor/plan-api.d.ts +16 -0
  8. package/dist/core/supervisor/plan-api.d.ts.map +1 -0
  9. package/dist/core/supervisor/plan-api.js +34 -0
  10. package/dist/core/supervisor/plan-read.d.ts +4 -0
  11. package/dist/core/supervisor/plan-read.d.ts.map +1 -0
  12. package/dist/core/supervisor/plan-read.js +269 -0
  13. package/dist/core/supervisor/plan-types.d.ts +113 -0
  14. package/dist/core/supervisor/plan-types.d.ts.map +1 -0
  15. package/dist/core/supervisor/plan-types.js +14 -0
  16. package/dist/core/supervisor/planning.d.ts +26 -0
  17. package/dist/core/supervisor/planning.d.ts.map +1 -0
  18. package/dist/core/supervisor/planning.js +486 -0
  19. package/dist/core/supervisor/read.d.ts +4 -0
  20. package/dist/core/supervisor/read.d.ts.map +1 -1
  21. package/dist/core/supervisor/read.js +3 -0
  22. package/dist/core/supervisor/runner.d.ts +3 -0
  23. package/dist/core/supervisor/runner.d.ts.map +1 -1
  24. package/dist/core/supervisor/runner.js +37 -7
  25. package/dist/core/supervisor/service-read.d.ts +6 -0
  26. package/dist/core/supervisor/service-read.d.ts.map +1 -0
  27. package/dist/core/supervisor/service-read.js +127 -0
  28. package/dist/core/supervisor/service.d.ts +114 -0
  29. package/dist/core/supervisor/service.d.ts.map +1 -0
  30. package/dist/core/supervisor/service.js +706 -0
  31. package/dist/core/supervisor/state.d.ts +11 -1
  32. package/dist/core/supervisor/state.d.ts.map +1 -1
  33. package/dist/core/supervisor/state.js +186 -7
  34. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  35. package/dist/lib/tunnel/gate.js +6 -1
  36. package/package.json +1 -1
  37. package/src/commands/supervisor.ts +490 -3
  38. package/src/core/supervisor/index.ts +45 -0
  39. package/src/core/supervisor/plan-api.ts +47 -0
  40. package/src/core/supervisor/plan-read.ts +299 -0
  41. package/src/core/supervisor/plan-types.ts +144 -0
  42. package/src/core/supervisor/planning.ts +576 -0
  43. package/src/core/supervisor/read.ts +30 -0
  44. package/src/core/supervisor/runner.ts +39 -2
  45. package/src/core/supervisor/service-read.ts +144 -0
  46. package/src/core/supervisor/service.ts +927 -0
  47. package/src/core/supervisor/state.ts +235 -6
  48. package/src/lib/tunnel/gate.ts +7 -1
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
  - **Cross-machine presence:** sessions on every machine that shares a repo see each other, labeled by machine. Zero-config over the repo's own git remote (presence refs), with an optional live-socket upgrade via an end-to-end-encrypted relay — the public `relay.harnery.com`, or self-host with `harn relay serve` / the bundled Cloudflare Worker (`relay/worker/`). The relay sees only ciphertext in opaquely-named rooms.
24
24
  - **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages. An optional host-owned policy gates dispatch and declared external mutations with `ALLOW`, `ASK`, and `DENY`; CLI `ASK` requests park durably for an explicit approve/deny and same-run resume, while library callers remain fail-closed by default.
25
25
  - **Durable work above attempts:** `harn work` preserves an objective across bounded workflow attempts, dependencies, approval parks, failures, and review. Its one-shot reconciler derives state from append-only local evidence and reports the next explicit action without silently spawning, retrying, or accepting work.
26
- - **Bounded goal supervision:** `harn supervisor` freezes a specialist team and safety limits around a root durable-work graph, then runs foreground scheduling cycles until success, human attention, no progress, or budget exhaustion. Specialist roles survive as configuration while every child remains a bounded process.
26
+ - **Bounded goal supervision:** `harn supervisor` freezes a specialist team and safety limits around a root durable-work graph, then runs scheduling cycles until success, human attention, no progress, or budget exhaustion. An optional planner can propose a schema-gated replacement graph from a frozen workflow-template catalog when the active graph has no legal move; proposals require review by default, old graphs remain immutable evidence, and their attempts still consume the goal budget. Use explicit foreground ticks or enroll goals in the optional heartbeat-backed background service.
27
27
  - **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
28
28
  - **Backup + sync:** `harn backup` snapshots `.harnery/` via [restic](https://restic.net/); `harn sync` keeps a curated subset live across machines via [rclone](https://rclone.org/) (Google Drive or any rclone remote).
29
29
 
@@ -1 +1 @@
1
- {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../src/commands/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA4CnD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA0FnF"}
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../src/commands/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAmEnD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8FnF"}
@@ -1,10 +1,10 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { workflowSubscriptionOnly } from "../core/config.js";
4
4
  import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
5
5
  import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
6
6
  import { loadPolicyFile } from "../core/policy/index.js";
7
- import { createSupervisor, listSupervisors, readSupervisor, runSupervisor, } from "../core/supervisor/index.js";
7
+ import { approveSupervisorPlan, configureSupervisorService, createSupervisor, listSupervisors, readSupervisor, readSupervisorPlan, readSupervisorServiceConfig, readSupervisorServiceStatus, rejectSupervisorPlan, requestSupervisorServiceStop, runSupervisor, runSupervisorServiceDaemon, spawnSupervisorService, supervisorServiceLogPath, } from "../core/supervisor/index.js";
8
8
  export function registerSupervisorCommand(program, emit) {
9
9
  const registry = createBuiltinHarnessRegistry();
10
10
  const supervisor = program
@@ -25,6 +25,7 @@ export function registerSupervisorCommand(program, emit) {
25
25
  .option("--accept-passing-proof", "Allow the supervisor to explicitly accept passing proof")
26
26
  .option("--no-resume-approved", "Stop after an approval instead of resuming its parked run")
27
27
  .option("--retry-blocked", "Allow bounded retry of blocked work")
28
+ .option("--replanning <file>", "Frozen planner policy and allowed workflow-template catalog")
28
29
  .option("--json", "Emit the complete supervisor record as JSON")
29
30
  .action((rootWorkId, opts) => {
30
31
  withSupervisorRoot(emit, (coordRoot) => {
@@ -53,6 +54,7 @@ export function registerSupervisorCommand(program, emit) {
53
54
  resume_approved: opts.resumeApproved,
54
55
  retry_blocked: opts.retryBlocked,
55
56
  },
57
+ replanning: opts.replanning ? readReplanningFile(opts.replanning) : undefined,
56
58
  }), opts.json, emit, true);
57
59
  });
58
60
  });
@@ -84,9 +86,282 @@ export function registerSupervisorCommand(program, emit) {
84
86
  emitSupervisor(readSupervisor(coordRoot, goalId), opts.json, emit, true);
85
87
  });
86
88
  });
89
+ registerPlanCommand(supervisor, emit);
90
+ registerServiceCommand(supervisor, registry, emit);
87
91
  registerRunCommand(supervisor, "tick", registry, emit);
88
92
  registerRunCommand(supervisor, "run", registry, emit);
89
93
  }
94
+ function registerPlanCommand(supervisor, emit) {
95
+ const plan = supervisor
96
+ .command("plan")
97
+ .description("Inspect and resolve bounded planner proposals for a durable goal.");
98
+ plan
99
+ .command("list <goal-id>")
100
+ .description("List append-only replanning attempts for one goal.")
101
+ .option("--json", "Emit complete plan records as JSON")
102
+ .action((goalId, opts) => {
103
+ withSupervisorRoot(emit, (coordRoot) => {
104
+ const plans = readSupervisor(coordRoot, goalId).plans;
105
+ if (opts.json) {
106
+ emit.config({ format: "json" });
107
+ emit.data(plans);
108
+ }
109
+ else if (plans.length === 0) {
110
+ emit.text(`supervisor ${goalId} has no replanning attempts\n`);
111
+ }
112
+ else {
113
+ emit.text(`${plans.map(renderPlanRow).join("\n")}\n`);
114
+ }
115
+ });
116
+ });
117
+ plan
118
+ .command("show <goal-id> <plan-id>")
119
+ .description("Show one planner request, schema-gated proposal, and audit events.")
120
+ .option("--json", "Emit the complete plan record as JSON")
121
+ .action((goalId, planId, opts) => {
122
+ withSupervisorRoot(emit, (coordRoot) => {
123
+ emitPlan(readSupervisorPlan(coordRoot, goalId, planId), opts.json, emit);
124
+ });
125
+ });
126
+ plan
127
+ .command("approve <goal-id> <plan-id>")
128
+ .description("Materialize a reviewed proposal and advance the active immutable root.")
129
+ .option("--actor <name>", "Actor recorded on the plan decision")
130
+ .option("--reason <text>", "Reason for approval")
131
+ .option("--json", "Emit the plan outcome as JSON")
132
+ .action((goalId, planId, opts) => {
133
+ withSupervisorRoot(emit, (coordRoot) => {
134
+ emitPlanOutcome(approveSupervisorPlan({
135
+ coordRoot,
136
+ goalId,
137
+ planId,
138
+ actor: opts.actor,
139
+ reason: opts.reason,
140
+ }), opts.json, emit);
141
+ });
142
+ });
143
+ plan
144
+ .command("reject <goal-id> <plan-id>")
145
+ .description("Reject a pending proposal without mutating durable work.")
146
+ .requiredOption("--reason <text>", "Reason the proposal is rejected")
147
+ .option("--actor <name>", "Actor recorded on the plan decision")
148
+ .option("--json", "Emit the plan outcome as JSON")
149
+ .action((goalId, planId, opts) => {
150
+ withSupervisorRoot(emit, (coordRoot) => {
151
+ emitPlanOutcome(rejectSupervisorPlan({
152
+ coordRoot,
153
+ goalId,
154
+ planId,
155
+ actor: opts.actor,
156
+ reason: opts.reason,
157
+ }), opts.json, emit);
158
+ });
159
+ });
160
+ }
161
+ function registerServiceCommand(supervisor, registry, emit) {
162
+ const service = supervisor
163
+ .command("service")
164
+ .description("Run explicitly enrolled durable goals in a restartable background service.");
165
+ addServiceOptions(service
166
+ .command("start [goal-ids...]")
167
+ .description("Configure and start the detached per-repository supervisor service."))
168
+ .option("--json", "Emit complete service status as JSON")
169
+ .action(async (goalIds, opts) => {
170
+ await withSupervisorRootAsync(emit, async (coordRoot) => {
171
+ prepareServiceConfig(coordRoot, goalIds, opts, registry);
172
+ const status = await spawnSupervisorService(coordRoot);
173
+ emitServiceStatus(status, opts.json, emit);
174
+ });
175
+ });
176
+ addServiceOptions(service
177
+ .command("run [goal-ids...]")
178
+ .description("Run the persistent service loop in the foreground for a process manager."))
179
+ .option("--json", "Emit terminal service status as JSON")
180
+ .action(async (goalIds, opts) => {
181
+ await withSupervisorRootAsync(emit, async (coordRoot) => {
182
+ const config = prepareServiceConfig(coordRoot, goalIds, opts, registry);
183
+ const status = await runSupervisorServiceDaemon({
184
+ coordRoot,
185
+ engine: serviceEngine(config, registry),
186
+ onLog: opts.json ? undefined : (line) => emit.text(`${line}\n`),
187
+ });
188
+ if (opts.json) {
189
+ emit.config({ format: "json" });
190
+ emit.data(status);
191
+ }
192
+ });
193
+ });
194
+ service
195
+ .command("status")
196
+ .description("Show service liveness, heartbeat, enrolled goals, and durable wake state.")
197
+ .option("--json", "Emit complete service status as JSON")
198
+ .action((opts) => {
199
+ withSupervisorRoot(emit, (coordRoot) => {
200
+ emitServiceStatus(readSupervisorServiceStatus(coordRoot), opts.json, emit);
201
+ });
202
+ });
203
+ service
204
+ .command("stop")
205
+ .description("Request a graceful stop; an active goal tick is allowed to finish safely.")
206
+ .option("--json", "Emit complete service status as JSON")
207
+ .action(async (opts) => {
208
+ await withSupervisorRootAsync(emit, async (coordRoot) => {
209
+ let status = requestSupervisorServiceStop(coordRoot);
210
+ const deadline = Date.now() + 5_000;
211
+ while (status.running && Date.now() < deadline) {
212
+ await delay(50);
213
+ status = readSupervisorServiceStatus(coordRoot);
214
+ }
215
+ emitServiceStatus(status, opts.json, emit);
216
+ });
217
+ });
218
+ service
219
+ .command("logs")
220
+ .description("Show the tail of the private background-service log.")
221
+ .option("--lines <n>", "Number of lines to show (default 50)", "50")
222
+ .action((opts) => {
223
+ withSupervisorRoot(emit, (coordRoot) => {
224
+ const lines = integer(opts.lines) ?? 50;
225
+ if (lines > 10_000)
226
+ throw new Error("service log lines must not exceed 10000");
227
+ const path = supervisorServiceLogPath(coordRoot);
228
+ if (!existsSync(path)) {
229
+ emit.text("no supervisor service log\n");
230
+ return;
231
+ }
232
+ const selected = readFileSync(path, "utf8")
233
+ .split("\n")
234
+ .slice(-(lines + 1))
235
+ .join("\n");
236
+ emit.text(selected.endsWith("\n") ? selected : `${selected}\n`);
237
+ });
238
+ });
239
+ service
240
+ .command("daemon", { hidden: true })
241
+ .description("Internal detached service entrypoint.")
242
+ .action(async () => {
243
+ await withSupervisorRootAsync(emit, async (coordRoot) => {
244
+ const config = readSupervisorServiceConfig(coordRoot);
245
+ await runSupervisorServiceDaemon({
246
+ coordRoot,
247
+ engine: serviceEngine(config, registry),
248
+ onLog: (line) => emit.text(`${line}\n`),
249
+ });
250
+ });
251
+ });
252
+ }
253
+ function addServiceOptions(command) {
254
+ return command
255
+ .option("--wake-interval-ms <n>", "State-change poll interval (default 5000)")
256
+ .option("--heartbeat-interval-ms <n>", "Heartbeat interval (default 2000)")
257
+ .option("--error-backoff-base-ms <n>", "Initial service-error backoff (default 2000)")
258
+ .option("--error-backoff-max-ms <n>", "Maximum service-error backoff (default 300000)")
259
+ .option("--harness <name>", "Fallback harness for agent calls without a specialist")
260
+ .option("--cwd <dir>", "Working directory for child agents")
261
+ .option("--subscription-only", "Require stored harness-login billing")
262
+ .option("--allow-api-billing", "Permit API-key override billing")
263
+ .option("--policy <file>", "Host policy JSON/JSONC")
264
+ .option("--isolation <mode>", "shared | worktree | sandbox | remote")
265
+ .option("--approval-to <address>", "Address durable ASK requests");
266
+ }
267
+ function prepareServiceConfig(coordRoot, goalIds, opts, registry) {
268
+ if (goalIds.length === 0) {
269
+ if (hasServiceConfigOverrides(opts)) {
270
+ throw new Error("goal ids are required when changing supervisor service options");
271
+ }
272
+ return readSupervisorServiceConfig(coordRoot);
273
+ }
274
+ if (opts.harness && !registry.get(opts.harness)) {
275
+ throw new Error(`unknown harness ${JSON.stringify(opts.harness)}`);
276
+ }
277
+ const policy = opts.policy ? loadPolicyFile(opts.policy) : undefined;
278
+ return configureSupervisorService({
279
+ coordRoot,
280
+ goalIds,
281
+ wakeIntervalMs: integer(opts.wakeIntervalMs),
282
+ heartbeatIntervalMs: integer(opts.heartbeatIntervalMs),
283
+ errorBackoffBaseMs: integer(opts.errorBackoffBaseMs),
284
+ errorBackoffMaxMs: integer(opts.errorBackoffMaxMs),
285
+ engine: {
286
+ default_harness: opts.harness,
287
+ cwd: opts.cwd,
288
+ subscription_only: opts.subscriptionOnly === true ? true : workflowSubscriptionOnly(coordRoot),
289
+ allow_api_billing: opts.allowApiBilling,
290
+ policy,
291
+ isolation: opts.isolation,
292
+ approval_addressee: opts.approvalTo,
293
+ },
294
+ });
295
+ }
296
+ function hasServiceConfigOverrides(opts) {
297
+ return Boolean(opts.wakeIntervalMs ||
298
+ opts.heartbeatIntervalMs ||
299
+ opts.errorBackoffBaseMs ||
300
+ opts.errorBackoffMaxMs ||
301
+ opts.harness ||
302
+ opts.cwd ||
303
+ opts.subscriptionOnly ||
304
+ opts.allowApiBilling ||
305
+ opts.policy ||
306
+ opts.isolation ||
307
+ opts.approvalTo);
308
+ }
309
+ function serviceEngine(config, registry) {
310
+ return {
311
+ spawners: registry.spawners(),
312
+ defaultHarness: config.engine.default_harness,
313
+ cwd: config.engine.cwd,
314
+ subscriptionOnly: config.engine.subscription_only,
315
+ allowApiBilling: config.engine.allow_api_billing,
316
+ harnessEvidence: Object.fromEntries(registry
317
+ .list()
318
+ .map((adapter) => [
319
+ adapter.profile.id,
320
+ { toolEvidence: adapter.profile.capabilities.toolEvidence },
321
+ ])),
322
+ policy: config.engine.policy,
323
+ approvalMode: "park",
324
+ approvalAddressee: config.engine.approval_addressee,
325
+ isolation: config.engine.isolation,
326
+ networkAccess: "enabled",
327
+ };
328
+ }
329
+ function emitServiceStatus(status, json, emit) {
330
+ if (json) {
331
+ emit.config({ format: "json" });
332
+ emit.data(status);
333
+ return;
334
+ }
335
+ if (!status.config && !status.record) {
336
+ emit.text("supervisor service: unconfigured\n");
337
+ return;
338
+ }
339
+ const state = status.running
340
+ ? (status.record?.state ?? "running")
341
+ : status.stale
342
+ ? "stale"
343
+ : "stopped";
344
+ const lines = [
345
+ `supervisor service: ${state}`,
346
+ `goals: ${status.config?.goal_ids.join(", ") || "none"}`,
347
+ ];
348
+ if (status.record) {
349
+ lines.push(`pid: ${status.record.pid} on ${status.record.host}`);
350
+ lines.push(`heartbeat: ${status.record.heartbeat_at}`);
351
+ lines.push(`sweeps: ${status.record.sweep_count}`);
352
+ if (status.record.active_goal_id)
353
+ lines.push(`active: ${status.record.active_goal_id}`);
354
+ if (status.record.next_wake_at)
355
+ lines.push(`next wake: ${status.record.next_wake_at}`);
356
+ if (status.record.last_error)
357
+ lines.push(`error: ${status.record.last_error}`);
358
+ }
359
+ const backingOff = Object.entries(status.runtime?.goals ?? {}).filter(([, runtime]) => runtime.state === "backoff");
360
+ if (backingOff.length) {
361
+ lines.push(`backoff: ${backingOff.map(([goalId]) => goalId).join(", ")}`);
362
+ }
363
+ emit.text(`${lines.join("\n")}\n`);
364
+ }
90
365
  function registerRunCommand(supervisor, mode, registry, emit) {
91
366
  supervisor
92
367
  .command(`${mode} <goal-id>`)
@@ -150,6 +425,56 @@ function readTeamFile(path) {
150
425
  }
151
426
  return value;
152
427
  }
428
+ function readReplanningFile(path) {
429
+ const absolute = resolve(path);
430
+ let value;
431
+ try {
432
+ value = JSON.parse(readFileSync(absolute, "utf8"));
433
+ }
434
+ catch (error) {
435
+ throw new Error(`cannot read supervisor replanning policy at ${absolute}: ${error.message}`);
436
+ }
437
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
438
+ throw new Error("supervisor replanning policy must be a JSON object");
439
+ }
440
+ const config = value;
441
+ if (typeof config.planner_specialist !== "string") {
442
+ throw new Error("supervisor replanning planner_specialist must be a string");
443
+ }
444
+ if (!config.templates ||
445
+ typeof config.templates !== "object" ||
446
+ Array.isArray(config.templates)) {
447
+ throw new Error("supervisor replanning templates must be an object");
448
+ }
449
+ const base = dirname(absolute);
450
+ const templates = Object.fromEntries(Object.entries(config.templates).map(([id, raw]) => {
451
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
452
+ throw new Error(`supervisor replanning template ${id} must be an object`);
453
+ }
454
+ const template = raw;
455
+ if (typeof template.workflow !== "string") {
456
+ throw new Error(`supervisor replanning template ${id} workflow must be a string`);
457
+ }
458
+ return [
459
+ id,
460
+ {
461
+ workflowPath: isAbsolute(template.workflow)
462
+ ? template.workflow
463
+ : resolve(base, template.workflow),
464
+ maxAttempts: jsonInteger(template.max_attempts, `template ${id} max_attempts`),
465
+ root: jsonBoolean(template.root, `template ${id} root`),
466
+ },
467
+ ];
468
+ }));
469
+ return {
470
+ plannerSpecialist: config.planner_specialist,
471
+ autoApply: jsonBoolean(config.auto_apply, "replanning auto_apply"),
472
+ maxReplans: jsonInteger(config.max_replans, "replanning max_replans"),
473
+ maxWorkItemsPerPlan: jsonInteger(config.max_work_items_per_plan, "replanning max_work_items_per_plan"),
474
+ maxTotalWorkItems: jsonInteger(config.max_total_work_items, "replanning max_total_work_items"),
475
+ templates,
476
+ };
477
+ }
153
478
  function emitSupervisor(record, json, emit, detail = false) {
154
479
  if (json) {
155
480
  emit.config({ format: "json" });
@@ -164,7 +489,7 @@ function emitSupervisor(record, json, emit, detail = false) {
164
489
  const lines = [
165
490
  `${record.intent.id}: ${record.intent.title}`,
166
491
  `state: ${projection.state}`,
167
- `root: ${record.intent.root_work_id}`,
492
+ `root: ${projection.root_work_id}`,
168
493
  `reason: ${projection.reason}`,
169
494
  `next: ${projection.next_action}`,
170
495
  `work: ${projection.work_ids.length}`,
@@ -173,6 +498,14 @@ function emitSupervisor(record, json, emit, detail = false) {
173
498
  `automation: accept=${record.intent.automation.accept_passing_proof}, ` +
174
499
  `resume=${record.intent.automation.resume_approved}, retry=${record.intent.automation.retry_blocked}`,
175
500
  ];
501
+ if (record.intent.replanning) {
502
+ lines.push(`replanning: generation=${projection.plan_generation}, used=${projection.replans_used}/${record.intent.replanning.max_replans}, auto_apply=${record.intent.replanning.auto_apply}`);
503
+ if (projection.root_work_id !== record.intent.root_work_id) {
504
+ lines.push(`original root: ${record.intent.root_work_id}`);
505
+ }
506
+ if (projection.pending_plan_id)
507
+ lines.push(`pending plan: ${projection.pending_plan_id}`);
508
+ }
176
509
  if (projection.attention_work.length) {
177
510
  lines.push(`attention: ${projection.attention_work.join(", ")}`);
178
511
  }
@@ -187,6 +520,7 @@ function emitSupervisorReport(report, json, emit) {
187
520
  emit.text(`supervisor ${report.goal_id}: ${report.stop_reason}\n` +
188
521
  `reason: ${report.reason}\n` +
189
522
  `cycles: ${report.cycles}; dispatches: ${report.dispatches}; acceptances: ${report.acceptances}\n` +
523
+ `replans: ${report.replans}\n` +
190
524
  `state: ${report.projection.state}; next: ${report.projection.next_action}\n`);
191
525
  }
192
526
  function renderSupervisorRow(record) {
@@ -200,6 +534,61 @@ function integer(value) {
200
534
  throw new Error(`expected a positive integer, got ${JSON.stringify(value)}`);
201
535
  return Number.parseInt(value, 10);
202
536
  }
537
+ function jsonInteger(value, field) {
538
+ if (value === undefined)
539
+ return undefined;
540
+ if (!Number.isSafeInteger(value) || value < 1) {
541
+ throw new Error(`${field} must be a positive integer`);
542
+ }
543
+ return value;
544
+ }
545
+ function jsonBoolean(value, field) {
546
+ if (value === undefined)
547
+ return undefined;
548
+ if (typeof value !== "boolean")
549
+ throw new Error(`${field} must be boolean`);
550
+ return value;
551
+ }
552
+ function emitPlan(plan, json, emit) {
553
+ if (json) {
554
+ emit.config({ format: "json" });
555
+ emit.data(plan);
556
+ return;
557
+ }
558
+ const lines = [
559
+ `${plan.request.id}: ${plan.status}`,
560
+ `goal: ${plan.request.goal_id}`,
561
+ `sequence: ${plan.request.sequence}`,
562
+ `prior root: ${plan.request.prior_root_work_id}`,
563
+ `planner run: ${plan.request.workflow_run_id}`,
564
+ ];
565
+ if (plan.proposal) {
566
+ lines.push(`decision: ${plan.proposal.decision}`);
567
+ lines.push(`rationale: ${plan.proposal.rationale}`);
568
+ lines.push(`proposed work: ${plan.proposal.work.length}`);
569
+ }
570
+ if (plan.root_work_id)
571
+ lines.push(`applied root: ${plan.root_work_id}`);
572
+ if (plan.approval_id)
573
+ lines.push(`approval: ${plan.approval_id}`);
574
+ emit.text(`${lines.join("\n")}\n`);
575
+ }
576
+ function emitPlanOutcome(outcome, json, emit) {
577
+ if (json) {
578
+ emit.config({ format: "json" });
579
+ emit.data(outcome);
580
+ return;
581
+ }
582
+ emit.text(`supervisor plan ${outcome.plan_id}: ${outcome.status}\n` +
583
+ `${outcome.reason ? `reason: ${outcome.reason}\n` : ""}` +
584
+ `${outcome.root_work_id ? `root: ${outcome.root_work_id}\n` : ""}`);
585
+ }
586
+ function renderPlanRow(plan) {
587
+ return `${plan.request.id} ${plan.status.padEnd(18)} ${plan.request.prior_root_work_id}`;
588
+ }
589
+ function delay(ms) {
590
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
591
+ }
203
592
  function withSupervisorRoot(emit, fn) {
204
593
  const coordRoot = findCoordRoot();
205
594
  if (!coordRoot) {
@@ -1,3 +1,7 @@
1
+ export { approveSupervisorPlan, rejectSupervisorPlan } from "./plan-api.js";
2
+ export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
3
+ export { type CreateSupervisorPlanTemplateInput, type CreateSupervisorReplanningInput, SUPERVISOR_PLAN_SCHEMA_VERSION, type SupervisorPlanEvent, type SupervisorPlanEventType, type SupervisorPlanHistory, type SupervisorPlanOutcome, type SupervisorPlanProposal, type SupervisorPlanRecord, type SupervisorPlanRequest, type SupervisorPlanStatus, type SupervisorPlanTemplate, type SupervisorPlanWorkSpec, type SupervisorReplanningPolicy, } from "./plan-types.js";
1
4
  export { type RunSupervisorInput, runSupervisor, type SupervisorDispatchOutcome, type SupervisorRunReport, type SupervisorStopReason, } from "./runner.js";
5
+ export { type ConfigureSupervisorServiceInput, configureSupervisorService, type RunSupervisorServiceDaemonInput, type RunSupervisorServiceSweepInput, readSupervisorServiceConfig, readSupervisorServiceRuntime, readSupervisorServiceStatus, requestSupervisorServiceStop, runSupervisorServiceDaemon, runSupervisorServiceSweep, SUPERVISOR_SERVICE_CONFIG_SCHEMA_VERSION, SUPERVISOR_SERVICE_RUNTIME_SCHEMA_VERSION, SUPERVISOR_SERVICE_STATUS_SCHEMA_VERSION, type SupervisorServiceConfig, type SupervisorServiceEngineConfig, type SupervisorServiceGoalRuntime, type SupervisorServiceGoalState, type SupervisorServiceProcessState, type SupervisorServiceRuntime, type SupervisorServiceStatus, type SupervisorServiceStatusRecord, type SupervisorServiceSweepOutcome, type SupervisorServiceSweepReport, spawnSupervisorService, supervisorServiceLogPath, } from "./service.js";
2
6
  export { assertSupervisorId, type CreateSupervisorInput, collectSupervisorWork, createSupervisor, listSupervisors, newSupervisorId, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, type SupervisorAutomationPolicy, type SupervisorIntent, type SupervisorLimits, type SupervisorNextAction, type SupervisorProjection, type SupervisorRecord, type SupervisorState, } from "./state.js";
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EACvB,aAAa,EACb,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,gCAAgC,EAChC,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EACL,KAAK,iCAAiC,EACtC,KAAK,+BAA+B,EACpC,8BAA8B,EAC9B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,GAChC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EACvB,aAAa,EACb,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,+BAA+B,EACpC,0BAA0B,EAC1B,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,wCAAwC,EACxC,yCAAyC,EACzC,wCAAwC,EACxC,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EACjC,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,gCAAgC,EAChC,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
@@ -1,2 +1,6 @@
1
+ export { approveSupervisorPlan, rejectSupervisorPlan } from "./plan-api.js";
2
+ export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
3
+ export { SUPERVISOR_PLAN_SCHEMA_VERSION, } from "./plan-types.js";
1
4
  export { runSupervisor, } from "./runner.js";
5
+ export { configureSupervisorService, readSupervisorServiceConfig, readSupervisorServiceRuntime, readSupervisorServiceStatus, requestSupervisorServiceStop, runSupervisorServiceDaemon, runSupervisorServiceSweep, SUPERVISOR_SERVICE_CONFIG_SCHEMA_VERSION, SUPERVISOR_SERVICE_RUNTIME_SCHEMA_VERSION, SUPERVISOR_SERVICE_STATUS_SCHEMA_VERSION, spawnSupervisorService, supervisorServiceLogPath, } from "./service.js";
2
6
  export { assertSupervisorId, collectSupervisorWork, createSupervisor, listSupervisors, newSupervisorId, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, } from "./state.js";
@@ -0,0 +1,16 @@
1
+ import type { SupervisorPlanOutcome } from "./plan-types.js";
2
+ export declare function approveSupervisorPlan(input: {
3
+ coordRoot: string;
4
+ goalId: string;
5
+ planId: string;
6
+ actor?: string;
7
+ reason?: string;
8
+ }): SupervisorPlanOutcome;
9
+ export declare function rejectSupervisorPlan(input: {
10
+ coordRoot: string;
11
+ goalId: string;
12
+ planId: string;
13
+ actor?: string;
14
+ reason: string;
15
+ }): SupervisorPlanOutcome;
16
+ //# sourceMappingURL=plan-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-api.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/plan-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAI7D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,qBAAqB,CAcxB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,CAcxB"}
@@ -0,0 +1,34 @@
1
+ import { applySupervisorPlanProposal, rejectSupervisorPlanProposal } from "./planning.js";
2
+ import { acquireSupervisorLease, readSupervisorIgnoringLease } from "./state.js";
3
+ export function approveSupervisorPlan(input) {
4
+ const actor = input.actor?.trim() || `supervisor:${input.goalId}`;
5
+ const release = acquireSupervisorLease(input.coordRoot, input.goalId);
6
+ try {
7
+ return applySupervisorPlanProposal({
8
+ coordRoot: input.coordRoot,
9
+ record: readSupervisorIgnoringLease(input.coordRoot, input.goalId),
10
+ planId: input.planId,
11
+ actor,
12
+ reason: input.reason,
13
+ });
14
+ }
15
+ finally {
16
+ release();
17
+ }
18
+ }
19
+ export function rejectSupervisorPlan(input) {
20
+ const actor = input.actor?.trim() || `supervisor:${input.goalId}`;
21
+ const release = acquireSupervisorLease(input.coordRoot, input.goalId);
22
+ try {
23
+ return rejectSupervisorPlanProposal({
24
+ coordRoot: input.coordRoot,
25
+ goalId: input.goalId,
26
+ planId: input.planId,
27
+ actor,
28
+ reason: input.reason,
29
+ });
30
+ }
31
+ finally {
32
+ release();
33
+ }
34
+ }
@@ -0,0 +1,4 @@
1
+ import { type SupervisorPlanHistory, type SupervisorPlanRecord } from "./plan-types.js";
2
+ export declare function readSupervisorPlans(coordRootRaw: string, goalId: string, originalRootWorkId: string): SupervisorPlanHistory;
3
+ export declare function readSupervisorPlan(coordRootRaw: string, goalId: string, planId: string): SupervisorPlanRecord;
4
+ //# sourceMappingURL=plan-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-read.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/plan-read.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,qBAAqB,EAE1B,KAAK,oBAAoB,EAE1B,MAAM,iBAAiB,CAAC;AAsBzB,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,MAAM,GACzB,qBAAqB,CAoDvB;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,oBAAoB,CAetB"}