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 +1 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +40 -3
- package/dist/core/supervisor/index.d.ts +2 -2
- package/dist/core/supervisor/index.d.ts.map +1 -1
- package/dist/core/supervisor/plan-read.d.ts.map +1 -1
- package/dist/core/supervisor/plan-read.js +47 -1
- package/dist/core/supervisor/plan-types.d.ts +13 -3
- package/dist/core/supervisor/plan-types.d.ts.map +1 -1
- package/dist/core/supervisor/planning.d.ts.map +1 -1
- package/dist/core/supervisor/planning.js +113 -20
- package/dist/core/supervisor/read.d.ts +2 -2
- package/dist/core/supervisor/read.d.ts.map +1 -1
- package/dist/core/supervisor/runner.d.ts.map +1 -1
- package/dist/core/supervisor/runner.js +7 -2
- package/dist/core/supervisor/service.js +3 -0
- package/dist/core/supervisor/state.d.ts +17 -2
- package/dist/core/supervisor/state.d.ts.map +1 -1
- package/dist/core/supervisor/state.js +137 -27
- package/package.json +1 -1
- package/src/commands/supervisor.ts +47 -4
- package/src/core/supervisor/index.ts +3 -0
- package/src/core/supervisor/plan-read.ts +58 -1
- package/src/core/supervisor/plan-types.ts +14 -1
- package/src/core/supervisor/planning.ts +136 -14
- package/src/core/supervisor/read.ts +2 -0
- package/src/core/supervisor/runner.ts +9 -2
- package/src/core/supervisor/service.ts +3 -0
- package/src/core/supervisor/state.ts +176 -28
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
|
|
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;
|
|
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
|
|
15
|
-
.description("Freeze
|
|
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":"
|
|
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":"AAAA,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,iCAAiC;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,+BAA+B;IAC9C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iCAAiC,CAAC,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,OAAO,8BAA8B,CAAC;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,OAAO,8BAA8B,CAAC;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,WAAW,CAAC;
|
|
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,
|
|
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" ||
|
|
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
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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: {
|
|
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
|
-
|
|
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(
|
|
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"}
|