harnery 0.17.0 → 0.18.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.
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 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.
26
+ - **Bounded goal supervision:** `harn supervisor` freezes a mission or existing root graph, specialist team, and safety limits, then runs scheduling cycles until reviewed mission success, human attention, no progress, or budget exhaustion. Its optional planner can create the first milestone, reassess accepted milestone proof, or recover a terminal graph using only a frozen workflow-template catalog. Proposals require review by default, old generations remain immutable evidence, and all 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;AAmEnD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8FnF"}
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;AAqEnD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAgGnF"}
@@ -11,8 +11,8 @@ export function registerSupervisorCommand(program, emit) {
11
11
  .command("supervisor")
12
12
  .description("Run a bounded specialist team over a durable-work dependency graph.");
13
13
  supervisor
14
- .command("create <root-work-id>")
15
- .description("Freeze a goal, specialist team, automation policy, and execution limits.")
14
+ .command("create [root-work-id]")
15
+ .description("Freeze an existing-root goal or an objective-first bounded mission.")
16
16
  .requiredOption("--team <file>", "JSON object mapping specialist ids to profiles")
17
17
  .option("--id <goal-id>", "Stable goal id (generated by default)")
18
18
  .option("--title <text>", "Goal title (defaults to root work title)")
@@ -26,6 +26,7 @@ export function registerSupervisorCommand(program, emit) {
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
28
  .option("--replanning <file>", "Frozen planner policy and allowed workflow-template catalog")
29
+ .option("--mission <file>", "Frozen objective, acceptance criteria, and milestone bound")
29
30
  .option("--json", "Emit the complete supervisor record as JSON")
30
31
  .action((rootWorkId, opts) => {
31
32
  withSupervisorRoot(emit, (coordRoot) => {
@@ -54,6 +55,7 @@ export function registerSupervisorCommand(program, emit) {
54
55
  resume_approved: opts.resumeApproved,
55
56
  retry_blocked: opts.retryBlocked,
56
57
  },
58
+ mission: opts.mission ? readMissionFile(opts.mission) : undefined,
57
59
  replanning: opts.replanning ? readReplanningFile(opts.replanning) : undefined,
58
60
  }), opts.json, emit, true);
59
61
  });
@@ -475,6 +477,32 @@ function readReplanningFile(path) {
475
477
  templates,
476
478
  };
477
479
  }
480
+ function readMissionFile(path) {
481
+ const absolute = resolve(path);
482
+ let value;
483
+ try {
484
+ value = JSON.parse(readFileSync(absolute, "utf8"));
485
+ }
486
+ catch (error) {
487
+ throw new Error(`cannot read supervisor mission at ${absolute}: ${error.message}`);
488
+ }
489
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
490
+ throw new Error("supervisor mission must be a JSON object");
491
+ }
492
+ const config = value;
493
+ if (typeof config.objective !== "string") {
494
+ throw new Error("supervisor mission objective must be a string");
495
+ }
496
+ if (!Array.isArray(config.acceptance) ||
497
+ config.acceptance.some((item) => typeof item !== "string")) {
498
+ throw new Error("supervisor mission acceptance must be an array of strings");
499
+ }
500
+ return {
501
+ objective: config.objective,
502
+ acceptance: config.acceptance,
503
+ maxMilestones: jsonInteger(config.max_milestones, "mission max_milestones"),
504
+ };
505
+ }
478
506
  function emitSupervisor(record, json, emit, detail = false) {
479
507
  if (json) {
480
508
  emit.config({ format: "json" });
@@ -489,7 +517,7 @@ function emitSupervisor(record, json, emit, detail = false) {
489
517
  const lines = [
490
518
  `${record.intent.id}: ${record.intent.title}`,
491
519
  `state: ${projection.state}`,
492
- `root: ${projection.root_work_id}`,
520
+ `root: ${projection.root_materialized ? projection.root_work_id : "pending initial plan"}`,
493
521
  `reason: ${projection.reason}`,
494
522
  `next: ${projection.next_action}`,
495
523
  `work: ${projection.work_ids.length}`,
@@ -506,6 +534,11 @@ function emitSupervisor(record, json, emit, detail = false) {
506
534
  if (projection.pending_plan_id)
507
535
  lines.push(`pending plan: ${projection.pending_plan_id}`);
508
536
  }
537
+ if (record.intent.mission) {
538
+ lines.push(`mission: ${record.intent.mission.objective}`);
539
+ lines.push(`milestones: ${projection.milestones_completed}/${record.intent.mission.max_milestones}`);
540
+ lines.push(`mission acceptance: ${record.intent.mission.acceptance.length}`);
541
+ }
509
542
  if (projection.attention_work.length) {
510
543
  lines.push(`attention: ${projection.attention_work.join(", ")}`);
511
544
  }
@@ -559,6 +592,7 @@ function emitPlan(plan, json, emit) {
559
592
  `${plan.request.id}: ${plan.status}`,
560
593
  `goal: ${plan.request.goal_id}`,
561
594
  `sequence: ${plan.request.sequence}`,
595
+ `trigger: ${plan.request.trigger ?? "recovery"}`,
562
596
  `prior root: ${plan.request.prior_root_work_id}`,
563
597
  `planner run: ${plan.request.workflow_run_id}`,
564
598
  ];
@@ -566,6 +600,9 @@ function emitPlan(plan, json, emit) {
566
600
  lines.push(`decision: ${plan.proposal.decision}`);
567
601
  lines.push(`rationale: ${plan.proposal.rationale}`);
568
602
  lines.push(`proposed work: ${plan.proposal.work.length}`);
603
+ if (plan.proposal.milestone) {
604
+ lines.push(`milestone: ${plan.proposal.milestone.sequence} ${plan.proposal.milestone.title}`);
605
+ }
569
606
  }
570
607
  if (plan.root_work_id)
571
608
  lines.push(`applied root: ${plan.root_work_id}`);
@@ -1,7 +1,7 @@
1
1
  export { approveSupervisorPlan, rejectSupervisorPlan } from "./plan-api.js";
2
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";
3
+ export { type CreateSupervisorPlanTemplateInput, type CreateSupervisorReplanningInput, SUPERVISOR_PLAN_SCHEMA_VERSION, type SupervisorPlanEvent, type SupervisorPlanEventType, type SupervisorPlanHistory, type SupervisorPlanMilestone, type SupervisorPlanOutcome, type SupervisorPlanProposal, type SupervisorPlanRecord, type SupervisorPlanRequest, type SupervisorPlanStatus, type SupervisorPlanTemplate, type SupervisorPlanWorkSpec, type SupervisorReplanningPolicy, } from "./plan-types.js";
4
4
  export { type RunSupervisorInput, runSupervisor, type SupervisorDispatchOutcome, type SupervisorRunReport, type SupervisorStopReason, } from "./runner.js";
5
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";
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";
6
+ export { assertSupervisorId, type CreateSupervisorInput, type CreateSupervisorMissionInput, collectSupervisorWork, createSupervisor, listSupervisors, newSupervisorId, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, type SupervisorAutomationPolicy, type SupervisorIntent, type SupervisorLimits, type SupervisorMission, type SupervisorNextAction, type SupervisorProjection, type SupervisorRecord, type SupervisorState, } from "./state.js";
7
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,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
+ {"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,uBAAuB,EAC5B,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,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,gCAAgC,EAChC,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
@@ -1 +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"}
1
+ {"version":3,"file":"plan-read.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/plan-read.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,qBAAqB,EAE1B,KAAK,oBAAoB,EAE1B,MAAM,iBAAiB,CAAC;AAuBzB,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,MAAM,GACzB,qBAAqB,CAwEvB;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,oBAAoB,CAetB"}
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { readWorkItem } from "../work/read.js";
3
4
  import { SUPERVISOR_PLAN_SCHEMA_VERSION, } from "./plan-types.js";
4
5
  const PLAN_ID = /^plan-[0-9]{4}-[a-f0-9]{8}$/;
5
6
  const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
@@ -16,6 +17,7 @@ const EVENT_TYPES = new Set([
16
17
  "plan.resumed",
17
18
  "plan.proposed",
18
19
  "plan.applied",
20
+ "plan.completed",
19
21
  "plan.rejected",
20
22
  "plan.attention",
21
23
  "plan.failed",
@@ -32,6 +34,8 @@ export function readSupervisorPlans(coordRootRaw, goalId, originalRootWorkId) {
32
34
  generation: 0,
33
35
  applied_work_ids: [],
34
36
  materialized_work_ids: [],
37
+ milestones_completed: 0,
38
+ completed: false,
35
39
  };
36
40
  }
37
41
  const entries = readdirSync(root, { withFileTypes: true })
@@ -51,6 +55,8 @@ export function readSupervisorPlans(coordRootRaw, goalId, originalRootWorkId) {
51
55
  }
52
56
  let activeRoot = originalRootWorkId;
53
57
  let generation = 0;
58
+ let milestonesCompleted = 0;
59
+ let completed = false;
54
60
  const appliedWork = new Set();
55
61
  const materializedWork = new Set();
56
62
  for (const plan of plans) {
@@ -64,15 +70,27 @@ export function readSupervisorPlans(coordRootRaw, goalId, originalRootWorkId) {
64
70
  continue;
65
71
  activeRoot = plan.root_work_id;
66
72
  generation++;
73
+ if (plan.proposal?.milestone &&
74
+ readWorkItem(coordRoot, plan.root_work_id).projection.state === "succeeded") {
75
+ milestonesCompleted++;
76
+ }
67
77
  for (const workId of plan.work_ids)
68
78
  appliedWork.add(workId);
69
79
  }
80
+ const completedPlans = plans.filter((plan) => plan.status === "completed");
81
+ if (completedPlans.length > 1 ||
82
+ (completedPlans.length === 1 && completedPlans[0] !== plans.at(-1))) {
83
+ throw new Error(`supervisor ${goalId} has invalid completion history`);
84
+ }
85
+ completed = completedPlans.length === 1;
70
86
  return {
71
87
  plans,
72
88
  active_root_work_id: activeRoot,
73
89
  generation,
74
90
  applied_work_ids: [...appliedWork],
75
91
  materialized_work_ids: [...materializedWork],
92
+ milestones_completed: milestonesCompleted,
93
+ completed,
76
94
  latest: plans.at(-1),
77
95
  };
78
96
  }
@@ -95,6 +113,10 @@ export function readSupervisorPlan(coordRootRaw, goalId, planId) {
95
113
  }
96
114
  function deriveStatus(coordRoot, events, proposal) {
97
115
  const latest = events.at(-1);
116
+ const completed = [...events].reverse().find((event) => event.event === "plan.completed");
117
+ if (completed) {
118
+ return { status: "completed", work_ids: [], reason: completed.reason };
119
+ }
98
120
  const applied = [...events].reverse().find((event) => event.event === "plan.applied");
99
121
  if (applied) {
100
122
  return {
@@ -159,6 +181,8 @@ function validateRequest(request, goalId, planId) {
159
181
  !Number.isSafeInteger(request.sequence) ||
160
182
  request.sequence < 1 ||
161
183
  request.sequence > MAX_PLANS ||
184
+ (request.trigger !== undefined &&
185
+ !["initial", "recovery", "milestone"].includes(request.trigger)) ||
162
186
  typeof request.trigger_fingerprint !== "string" ||
163
187
  request.trigger_fingerprint.length < 1 ||
164
188
  request.trigger_fingerprint.length > 64_000 ||
@@ -171,7 +195,7 @@ function validateRequest(request, goalId, planId) {
171
195
  function validateProposalEnvelope(proposal, planId) {
172
196
  if (proposal.schema_version !== SUPERVISOR_PLAN_SCHEMA_VERSION ||
173
197
  proposal.plan_id !== planId ||
174
- !["apply", "attention"].includes(proposal.decision) ||
198
+ !["apply", "complete", "attention"].includes(proposal.decision) ||
175
199
  typeof proposal.rationale !== "string" ||
176
200
  proposal.rationale.length < 1 ||
177
201
  proposal.rationale.length > 2_000 ||
@@ -182,6 +206,28 @@ function validateProposalEnvelope(proposal, planId) {
182
206
  !validTimestamp(proposal.proposed_at)) {
183
207
  throw new Error(`supervisor plan proposal ${planId} has an unsupported schema`);
184
208
  }
209
+ if (proposal.milestone !== undefined) {
210
+ const milestone = proposal.milestone;
211
+ if (!Number.isSafeInteger(milestone.sequence) ||
212
+ milestone.sequence < 1 ||
213
+ milestone.sequence > 20 ||
214
+ typeof milestone.title !== "string" ||
215
+ milestone.title.length < 1 ||
216
+ milestone.title.length > 200 ||
217
+ typeof milestone.objective !== "string" ||
218
+ milestone.objective.length < 1 ||
219
+ milestone.objective.length > 4_000 ||
220
+ !Array.isArray(milestone.acceptance) ||
221
+ milestone.acceptance.length < 1 ||
222
+ milestone.acceptance.length > 50 ||
223
+ milestone.acceptance.some((criterion) => typeof criterion !== "string" || criterion.length < 1 || criterion.length > 500)) {
224
+ throw new Error(`supervisor plan proposal ${planId} milestone is invalid`);
225
+ }
226
+ }
227
+ if ((proposal.decision === "attention" || proposal.decision === "complete") &&
228
+ (proposal.root !== "" || proposal.work.length > 0 || proposal.milestone !== undefined)) {
229
+ throw new Error(`supervisor plan proposal ${planId} terminal decision contains work`);
230
+ }
185
231
  for (const [index, spec] of proposal.work.entries()) {
186
232
  if (!spec ||
187
233
  typeof spec !== "object" ||
@@ -33,11 +33,18 @@ export interface SupervisorPlanRequest {
33
33
  id: string;
34
34
  goal_id: string;
35
35
  sequence: number;
36
+ trigger?: "initial" | "recovery" | "milestone";
36
37
  trigger_fingerprint: string;
37
38
  prior_root_work_id: string;
38
39
  workflow_run_id: string;
39
40
  created_at: string;
40
41
  }
42
+ export interface SupervisorPlanMilestone {
43
+ sequence: number;
44
+ title: string;
45
+ objective: string;
46
+ acceptance: string[];
47
+ }
41
48
  export interface SupervisorPlanWorkSpec {
42
49
  key: string;
43
50
  title: string;
@@ -49,13 +56,14 @@ export interface SupervisorPlanWorkSpec {
49
56
  export interface SupervisorPlanProposal {
50
57
  schema_version: typeof SUPERVISOR_PLAN_SCHEMA_VERSION;
51
58
  plan_id: string;
52
- decision: "apply" | "attention";
59
+ decision: "apply" | "complete" | "attention";
53
60
  rationale: string;
54
61
  root: string;
55
62
  work: SupervisorPlanWorkSpec[];
63
+ milestone?: SupervisorPlanMilestone;
56
64
  proposed_at: string;
57
65
  }
58
- export type SupervisorPlanEventType = "plan.awaiting_approval" | "plan.resumed" | "plan.proposed" | "plan.applied" | "plan.rejected" | "plan.attention" | "plan.failed";
66
+ export type SupervisorPlanEventType = "plan.awaiting_approval" | "plan.resumed" | "plan.proposed" | "plan.applied" | "plan.completed" | "plan.rejected" | "plan.attention" | "plan.failed";
59
67
  export interface SupervisorPlanEvent {
60
68
  schema_version: typeof SUPERVISOR_PLAN_SCHEMA_VERSION;
61
69
  plan_id: string;
@@ -68,7 +76,7 @@ export interface SupervisorPlanEvent {
68
76
  root_work_id?: string;
69
77
  work_ids?: string[];
70
78
  }
71
- export type SupervisorPlanStatus = "interrupted" | "awaiting_approval" | "resumable" | "proposed" | "applied" | "rejected" | "attention" | "failed";
79
+ export type SupervisorPlanStatus = "interrupted" | "awaiting_approval" | "resumable" | "proposed" | "applied" | "completed" | "rejected" | "attention" | "failed";
72
80
  export interface SupervisorPlanRecord {
73
81
  request: SupervisorPlanRequest;
74
82
  proposal?: SupervisorPlanProposal;
@@ -85,6 +93,8 @@ export interface SupervisorPlanHistory {
85
93
  generation: number;
86
94
  applied_work_ids: string[];
87
95
  materialized_work_ids: string[];
96
+ milestones_completed: number;
97
+ completed: boolean;
88
98
  latest?: SupervisorPlanRecord;
89
99
  }
90
100
  export interface SupervisorPlanOutcome {
@@ -1 +1 @@
1
- {"version":3,"file":"plan-types.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/plan-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan-types.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/plan-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/planning.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGL,KAAK,qBAAqB,EAK3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAQnD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAkGhC;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,qBAAqB,CAyFxB;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,CAYxB"}
1
+ {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/planning.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAIL,KAAK,qBAAqB,EAK3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAQnD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CA0GhC;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,qBAAqB,CAsGxB;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,CAYxB"}
@@ -2,7 +2,7 @@ import { randomBytes, randomUUID } from "node:crypto";
2
2
  import { appendFileSync, chmodSync, existsSync, linkSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { createWorkItem, readWorkItem } from "../work/index.js";
5
- import { runWorkflow, WorkflowParkedError, workflowScriptDigest, } from "../workflow/index.js";
5
+ import { readWorkflowProof, runWorkflow, WorkflowParkedError, workflowScriptDigest, } from "../workflow/index.js";
6
6
  import { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
7
7
  import { SUPERVISOR_PLAN_SCHEMA_VERSION, supervisorGraphFingerprint, } from "./plan-types.js";
8
8
  const PLAN_KEY = /^[a-z][a-z0-9-]{0,31}$/;
@@ -21,6 +21,7 @@ export async function runSupervisorPlanner(input) {
21
21
  generation: input.record.projection.plan_generation,
22
22
  work: input.record.work,
23
23
  });
24
+ const trigger = planTrigger(input.record);
24
25
  const resumable = history.latest?.status === "resumable" &&
25
26
  history.latest.request.trigger_fingerprint === triggerFingerprint &&
26
27
  history.latest.request.prior_root_work_id === input.record.projection.root_work_id
@@ -28,7 +29,7 @@ export async function runSupervisorPlanner(input) {
28
29
  : undefined;
29
30
  const request = resumable
30
31
  ? resumable.request
31
- : createPlanRequest(coordRoot, input.record, history.plans.length + 1, triggerFingerprint);
32
+ : createPlanRequest(coordRoot, input.record, history.plans.length + 1, triggerFingerprint, trigger);
32
33
  const scriptPath = planScriptPath(coordRoot, input.record.intent.id, request.id);
33
34
  if (resumable) {
34
35
  appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
@@ -51,7 +52,7 @@ export async function runSupervisorPlanner(input) {
51
52
  concurrency: 1,
52
53
  onLog: input.onLog,
53
54
  });
54
- const proposal = normalizeProposal(request.id, report.result, input.record, history.materialized_work_ids.length);
55
+ const proposal = normalizeProposal(request.id, report.result, input.record, history.materialized_work_ids.length, request.trigger ?? "recovery");
55
56
  writeExclusiveJson(planProposalPath(coordRoot, input.record.intent.id, request.id), proposal, "supervisor plan proposal");
56
57
  appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
57
58
  event: "plan.proposed",
@@ -104,16 +105,26 @@ export function applySupervisorPlanProposal(input) {
104
105
  if (!policy)
105
106
  throw new Error(`supervisor ${input.record.intent.id} does not allow replanning`);
106
107
  const plan = readSupervisorPlan(coordRoot, input.record.intent.id, input.planId);
107
- if (plan.status === "applied")
108
+ if (plan.status === "applied" || plan.status === "completed")
108
109
  return outcome(plan);
109
- if (plan.status !== "proposed" || !plan.proposal || plan.proposal.decision !== "apply") {
110
+ if (plan.status !== "proposed" ||
111
+ !plan.proposal ||
112
+ !["apply", "complete"].includes(plan.proposal.decision)) {
110
113
  throw new Error(`supervisor plan ${input.planId} cannot be applied from ${plan.status}`);
111
114
  }
112
115
  if (plan.request.prior_root_work_id !== input.record.projection.root_work_id) {
113
116
  throw new Error(`supervisor plan ${input.planId} targets stale root ${plan.request.prior_root_work_id}`);
114
117
  }
115
118
  const history = readSupervisorPlans(coordRoot, input.record.intent.id, input.record.intent.root_work_id);
116
- const proposal = normalizeProposal(input.planId, plan.proposal, input.record, history.materialized_work_ids.filter((workId) => !workId.startsWith(`${input.planId}-`)).length);
119
+ const proposal = normalizeProposal(input.planId, plan.proposal, input.record, history.materialized_work_ids.filter((workId) => !workId.startsWith(`${input.planId}-`)).length, plan.request.trigger ?? "recovery");
120
+ if (proposal.decision === "complete") {
121
+ appendPlanEvent(coordRoot, input.record.intent.id, input.planId, {
122
+ event: "plan.completed",
123
+ actor: input.actor,
124
+ reason: input.reason ?? proposal.rationale,
125
+ });
126
+ return outcome(readSupervisorPlan(coordRoot, input.record.intent.id, input.planId));
127
+ }
117
128
  const ids = new Map();
118
129
  const created = [];
119
130
  for (const spec of proposal.work) {
@@ -195,7 +206,7 @@ export function rejectSupervisorPlanProposal(input) {
195
206
  });
196
207
  return outcome(readSupervisorPlan(input.coordRoot, input.goalId, input.planId));
197
208
  }
198
- function createPlanRequest(coordRoot, record, sequence, triggerFingerprint) {
209
+ function createPlanRequest(coordRoot, record, sequence, triggerFingerprint, trigger) {
199
210
  const policy = record.intent.replanning;
200
211
  if (!policy)
201
212
  throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
@@ -208,36 +219,42 @@ function createPlanRequest(coordRoot, record, sequence, triggerFingerprint) {
208
219
  id: planId,
209
220
  goal_id: record.intent.id,
210
221
  sequence,
222
+ trigger,
211
223
  trigger_fingerprint: triggerFingerprint,
212
224
  prior_root_work_id: record.projection.root_work_id,
213
225
  workflow_run_id: `${planId}-workflow`,
214
226
  created_at: new Date().toISOString(),
215
227
  };
228
+ const script = plannerScript(coordRoot, record, planId, trigger);
216
229
  const dir = planDir(coordRoot, record.intent.id, planId);
217
230
  mkdirSync(dir, { recursive: true, mode: 0o700 });
218
231
  chmodSync(dir, 0o700);
219
232
  writeExclusiveJson(join(dir, "request.json"), request, "supervisor plan request");
220
- const script = plannerScript(record, planId);
221
233
  writeExclusive(join(dir, "planner.mjs"), script, "supervisor planner script");
222
234
  return request;
223
235
  }
224
- function plannerScript(record, planId) {
236
+ function plannerScript(coordRoot, record, planId, trigger) {
225
237
  const policy = record.intent.replanning;
226
238
  if (!policy)
227
239
  throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
228
240
  const prompt = [
229
- "You are producing a bounded replacement plan for a durable supervised goal.",
230
- "The current graph is blocked and cannot make a legal scheduling step.",
231
- "Return decision=apply with a new immutable root graph, or decision=attention when human judgment is required.",
232
- "Never claim work is complete. Never request a workflow outside the frozen template catalog.",
241
+ "You are producing one bounded replacement plan or milestone decision for a durable supervised goal.",
242
+ record.intent.mission
243
+ ? "Return decision=apply with the next immutable milestone graph, decision=complete only when mission acceptance is met, or decision=attention when human judgment is required."
244
+ : "Return decision=apply with a replacement immutable root graph, or decision=attention when human judgment is required.",
245
+ "Never claim mission completion unless the frozen mission acceptance is met. Never request a workflow outside the frozen template catalog.",
233
246
  "Dependencies may name an active work ID or an earlier key in your proposed work array.",
234
247
  "Every proposed item must be reachable from root. The root must be a proposed key using a root-capable template.",
235
248
  `Plan id: ${planId}`,
249
+ `Planning trigger: ${trigger}`,
236
250
  `Goal: ${record.intent.title}`,
251
+ `Mission: ${JSON.stringify(record.intent.mission ?? null)}`,
237
252
  `Original root: ${record.intent.root_work_id}`,
238
253
  `Active root: ${record.projection.root_work_id}`,
239
254
  `Remaining work attempts: ${record.projection.attempts_remaining}`,
240
255
  `Remaining replans: ${record.projection.replans_remaining}`,
256
+ `Completed milestones: ${record.projection.milestones_completed}`,
257
+ `Remaining milestones: ${record.projection.milestones_remaining}`,
241
258
  `Latest planner outcome: ${JSON.stringify(record.plans.at(-1)
242
259
  ? {
243
260
  status: record.plans.at(-1)?.status,
@@ -253,12 +270,31 @@ function plannerScript(record, planId) {
253
270
  dependencies: work.intent.dependencies,
254
271
  state: work.projection.state,
255
272
  reason: work.projection.reason,
273
+ proof: work.projection.proof_path
274
+ ? (() => {
275
+ const proof = readWorkflowProof(coordRoot, work.projection.latest_run_id);
276
+ return {
277
+ run_id: proof.run.id,
278
+ status: proof.run.status,
279
+ acceptance: proof.acceptance.summary,
280
+ repository: {
281
+ head_changed: proof.repository.drift.head_changed,
282
+ dirty_paths_added: proof.repository.drift.dirty_paths_added,
283
+ incomplete: proof.repository.drift.incomplete,
284
+ },
285
+ unknowns: proof.unknowns.map((unknown) => unknown.code),
286
+ };
287
+ })()
288
+ : null,
256
289
  })))}`,
257
290
  ].join("\n\n");
258
291
  const schema = {
259
292
  type: "object",
260
293
  properties: {
261
- decision: { type: "string", enum: ["apply", "attention"] },
294
+ decision: {
295
+ type: "string",
296
+ enum: record.intent.mission ? ["apply", "complete", "attention"] : ["apply", "attention"],
297
+ },
262
298
  rationale: { type: "string" },
263
299
  root: { type: "string" },
264
300
  work: {
@@ -276,13 +312,27 @@ function plannerScript(record, planId) {
276
312
  required: ["key", "title", "objective", "acceptance", "dependencies", "template"],
277
313
  },
278
314
  },
315
+ milestone: {
316
+ type: "object",
317
+ properties: {
318
+ sequence: { type: "number" },
319
+ title: { type: "string" },
320
+ objective: { type: "string" },
321
+ acceptance: { type: "array", items: { type: "string" } },
322
+ },
323
+ required: ["sequence", "title", "objective", "acceptance"],
324
+ },
279
325
  },
280
326
  required: ["decision", "rationale", "root", "work"],
281
327
  };
282
328
  return [
283
329
  `export const meta = ${JSON.stringify({ name: `replan-${planId}` })};`,
284
330
  "export default async ({ agent, stage }) => {",
285
- ' stage("Replan blocked goal");',
331
+ ` stage(${JSON.stringify(trigger === "initial"
332
+ ? "Plan initial mission milestone"
333
+ : trigger === "milestone"
334
+ ? "Reassess completed milestone"
335
+ : "Replan blocked goal")});`,
286
336
  ` return await agent(${JSON.stringify(prompt)}, ${JSON.stringify({
287
337
  specialist: policy.planner_specialist,
288
338
  schema,
@@ -292,7 +342,7 @@ function plannerScript(record, planId) {
292
342
  "",
293
343
  ].join("\n");
294
344
  }
295
- function normalizeProposal(planId, raw, record, previouslyApplied) {
345
+ function normalizeProposal(planId, raw, record, previouslyApplied, trigger) {
296
346
  const policy = record.intent.replanning;
297
347
  if (!policy)
298
348
  throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
@@ -300,14 +350,18 @@ function normalizeProposal(planId, raw, record, previouslyApplied) {
300
350
  throw new Error("planner result must be an object");
301
351
  }
302
352
  const value = raw;
303
- const decision = enumValue(value.decision, ["apply", "attention"], "plan decision");
353
+ const decision = enumValue(value.decision, ["apply", "complete", "attention"], "plan decision");
304
354
  const rationale = bounded(value.rationale, "plan rationale", MAX_REASON);
305
355
  const root = typeof value.root === "string" ? value.root.trim() : "";
306
356
  if (!Array.isArray(value.work))
307
357
  throw new Error("plan work must be an array");
308
- if (decision === "attention") {
309
- if (root || value.work.length > 0)
310
- throw new Error("attention plan must not contain work");
358
+ if (decision === "attention" || decision === "complete") {
359
+ if (root || value.work.length > 0 || value.milestone !== undefined) {
360
+ throw new Error(`${decision} plan must not contain work or milestone data`);
361
+ }
362
+ if (decision === "complete" && (!record.intent.mission || trigger !== "milestone")) {
363
+ throw new Error("mission completion may be proposed only at a milestone boundary");
364
+ }
311
365
  return {
312
366
  schema_version: SUPERVISOR_PLAN_SCHEMA_VERSION,
313
367
  plan_id: planId,
@@ -318,6 +372,15 @@ function normalizeProposal(planId, raw, record, previouslyApplied) {
318
372
  proposed_at: new Date().toISOString(),
319
373
  };
320
374
  }
375
+ const milestone = record.intent.mission
376
+ ? normalizeMilestone(value.milestone, record.projection.milestones_completed + 1)
377
+ : undefined;
378
+ if (!record.intent.mission && value.milestone !== undefined) {
379
+ throw new Error("non-mission replacement plan must not contain milestone data");
380
+ }
381
+ if (milestone && milestone.sequence > record.intent.mission.max_milestones) {
382
+ throw new Error(`milestone ${milestone.sequence} exceeds mission limit ${record.intent.mission.max_milestones}`);
383
+ }
321
384
  if (value.work.length < 1 || value.work.length > policy.max_work_items_per_plan) {
322
385
  throw new Error(`plan work must contain 1 to ${policy.max_work_items_per_plan} items`);
323
386
  }
@@ -393,9 +456,39 @@ function normalizeProposal(planId, raw, record, previouslyApplied) {
393
456
  rationale,
394
457
  root,
395
458
  work,
459
+ ...(milestone ? { milestone } : {}),
396
460
  proposed_at: new Date().toISOString(),
397
461
  };
398
462
  }
463
+ function normalizeMilestone(raw, expectedSequence) {
464
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
465
+ throw new Error("mission plan must contain a milestone object");
466
+ }
467
+ const value = raw;
468
+ if (value.sequence !== expectedSequence) {
469
+ throw new Error(`milestone sequence must be ${expectedSequence}`);
470
+ }
471
+ if (!Array.isArray(value.acceptance) || value.acceptance.length < 1) {
472
+ throw new Error("milestone acceptance must be a non-empty array");
473
+ }
474
+ if (value.acceptance.length > 50)
475
+ throw new Error("milestone acceptance exceeds 50 criteria");
476
+ return {
477
+ sequence: expectedSequence,
478
+ title: bounded(value.title, "milestone title", 200),
479
+ objective: bounded(value.objective, "milestone objective", 4_000),
480
+ acceptance: value.acceptance.map((criterion, index) => bounded(criterion, `milestone acceptance[${index}]`, 500)),
481
+ };
482
+ }
483
+ function planTrigger(record) {
484
+ if (record.intent.mission &&
485
+ record.projection.plan_generation === 0 &&
486
+ record.work.length === 0) {
487
+ return "initial";
488
+ }
489
+ const root = record.work.find((work) => work.intent.id === record.projection.root_work_id);
490
+ return record.intent.mission && root?.projection.state === "succeeded" ? "milestone" : "recovery";
491
+ }
399
492
  function appendPlanEvent(coordRootRaw, goalId, planId, input) {
400
493
  const coordRoot = resolve(coordRootRaw);
401
494
  const path = join(planDir(coordRoot, goalId, planId), "events.jsonl");
@@ -1,6 +1,6 @@
1
1
  export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
2
- export { 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";
2
+ export { SUPERVISOR_PLAN_SCHEMA_VERSION, type SupervisorPlanEvent, type SupervisorPlanEventType, type SupervisorPlanHistory, type SupervisorPlanMilestone, type SupervisorPlanOutcome, type SupervisorPlanProposal, type SupervisorPlanRecord, type SupervisorPlanRequest, type SupervisorPlanStatus, type SupervisorPlanTemplate, type SupervisorPlanWorkSpec, type SupervisorReplanningPolicy, } from "./plan-types.js";
3
3
  export type { SupervisorServiceConfig, SupervisorServiceGoalRuntime, SupervisorServiceGoalState, SupervisorServiceProcessState, SupervisorServiceRuntime, SupervisorServiceStatus, SupervisorServiceStatusRecord, } from "./service.js";
4
4
  export { readSupervisorServiceConfig, readSupervisorServiceRuntime, readSupervisorServiceStatus, supervisorServiceLogPath, } from "./service-read.js";
5
- export { assertSupervisorId, collectSupervisorWork, listSupervisors, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, type SupervisorAutomationPolicy, type SupervisorIntent, type SupervisorLimits, type SupervisorNextAction, type SupervisorProjection, type SupervisorRecord, type SupervisorState, } from "./state.js";
5
+ export { assertSupervisorId, collectSupervisorWork, listSupervisors, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, type SupervisorAutomationPolicy, type SupervisorIntent, type SupervisorLimits, type SupervisorMission, type SupervisorNextAction, type SupervisorProjection, type SupervisorRecord, type SupervisorState, } from "./state.js";
6
6
  //# sourceMappingURL=read.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EACL,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,YAAY,EACV,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,6BAA6B,EAC7B,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,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":"read.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EACL,8BAA8B,EAC9B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,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,YAAY,EACV,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,6BAA6B,EAC7B,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,gCAAgC,EAChC,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}