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
|
@@ -30,6 +30,8 @@ export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1 as const;
|
|
|
30
30
|
|
|
31
31
|
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
32
32
|
const MAX_TITLE = 200;
|
|
33
|
+
const MAX_OBJECTIVE = 4_000;
|
|
34
|
+
const MAX_ACCEPTANCE = 50;
|
|
33
35
|
const MAX_INTENT_BYTES = 256 * 1024;
|
|
34
36
|
const MAX_TEMPLATES = 20;
|
|
35
37
|
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -50,6 +52,18 @@ export interface SupervisorAutomationPolicy {
|
|
|
50
52
|
retry_blocked: boolean;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
export interface SupervisorMission {
|
|
56
|
+
objective: string;
|
|
57
|
+
acceptance: string[];
|
|
58
|
+
max_milestones: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CreateSupervisorMissionInput {
|
|
62
|
+
objective: string;
|
|
63
|
+
acceptance: readonly string[];
|
|
64
|
+
maxMilestones?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
export interface SupervisorIntent {
|
|
54
68
|
schema_version: typeof SUPERVISOR_INTENT_SCHEMA_VERSION;
|
|
55
69
|
id: string;
|
|
@@ -58,6 +72,7 @@ export interface SupervisorIntent {
|
|
|
58
72
|
specialists: Record<string, WorkflowSpecialistProfile>;
|
|
59
73
|
limits: SupervisorLimits;
|
|
60
74
|
automation: SupervisorAutomationPolicy;
|
|
75
|
+
mission?: SupervisorMission;
|
|
61
76
|
replanning?: SupervisorReplanningPolicy;
|
|
62
77
|
created_at: string;
|
|
63
78
|
}
|
|
@@ -77,6 +92,8 @@ export type SupervisorNextAction =
|
|
|
77
92
|
| "review"
|
|
78
93
|
| "retry"
|
|
79
94
|
| "replan"
|
|
95
|
+
| "plan_initial"
|
|
96
|
+
| "plan_milestone"
|
|
80
97
|
| "review_plan"
|
|
81
98
|
| "none";
|
|
82
99
|
|
|
@@ -84,6 +101,7 @@ export interface SupervisorProjection {
|
|
|
84
101
|
id: string;
|
|
85
102
|
title: string;
|
|
86
103
|
root_work_id: string;
|
|
104
|
+
root_materialized: boolean;
|
|
87
105
|
state: SupervisorState;
|
|
88
106
|
reason: string;
|
|
89
107
|
next_action: SupervisorNextAction;
|
|
@@ -98,6 +116,8 @@ export interface SupervisorProjection {
|
|
|
98
116
|
plan_generation: number;
|
|
99
117
|
replans_used: number;
|
|
100
118
|
replans_remaining: number;
|
|
119
|
+
milestones_completed: number;
|
|
120
|
+
milestones_remaining: number;
|
|
101
121
|
pending_plan_id?: string;
|
|
102
122
|
latest_plan_status?: SupervisorPlanRecord["status"];
|
|
103
123
|
governed_work_ids: string[];
|
|
@@ -112,32 +132,57 @@ export interface SupervisorRecord {
|
|
|
112
132
|
|
|
113
133
|
export interface CreateSupervisorInput {
|
|
114
134
|
coordRoot: string;
|
|
115
|
-
rootWorkId
|
|
135
|
+
rootWorkId?: string;
|
|
116
136
|
specialists: Readonly<Record<string, WorkflowSpecialistProfile>>;
|
|
117
137
|
title?: string;
|
|
118
138
|
id?: string;
|
|
119
139
|
limits?: Partial<SupervisorLimits>;
|
|
120
140
|
automation?: Partial<SupervisorAutomationPolicy>;
|
|
141
|
+
mission?: CreateSupervisorMissionInput;
|
|
121
142
|
replanning?: CreateSupervisorReplanningInput;
|
|
122
143
|
}
|
|
123
144
|
|
|
124
145
|
export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord {
|
|
125
146
|
const coordRoot = resolve(input.coordRoot);
|
|
126
|
-
assertWorkId(input.rootWorkId);
|
|
127
|
-
const root = readWorkItem(coordRoot, input.rootWorkId);
|
|
128
147
|
const id = input.id ?? newSupervisorId();
|
|
129
148
|
assertSupervisorId(id);
|
|
149
|
+
const mission = input.mission ? normalizeMission(input.mission) : undefined;
|
|
150
|
+
if (mission && !input.replanning) {
|
|
151
|
+
throw new Error("supervisor mission requires a replanning policy");
|
|
152
|
+
}
|
|
153
|
+
if (!input.rootWorkId && !mission) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"supervisor creation without root work requires a mission and replanning policy",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (input.rootWorkId) assertWorkId(input.rootWorkId);
|
|
159
|
+
const root = input.rootWorkId ? readWorkItem(coordRoot, input.rootWorkId) : undefined;
|
|
130
160
|
const replanning = input.replanning
|
|
131
161
|
? normalizeReplanning(input.replanning, input.specialists)
|
|
132
162
|
: undefined;
|
|
163
|
+
if (
|
|
164
|
+
mission &&
|
|
165
|
+
!input.rootWorkId &&
|
|
166
|
+
replanning &&
|
|
167
|
+
replanning.max_replans <= mission.max_milestones
|
|
168
|
+
) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"objective-first mission max_replans must exceed max_milestones to reserve completion review",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
133
173
|
const intent: SupervisorIntent = {
|
|
134
174
|
schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
|
|
135
175
|
id,
|
|
136
|
-
title: bounded(
|
|
137
|
-
|
|
176
|
+
title: bounded(
|
|
177
|
+
input.title ?? root?.intent.title ?? missionTitle(mission!),
|
|
178
|
+
"supervisor title",
|
|
179
|
+
MAX_TITLE,
|
|
180
|
+
),
|
|
181
|
+
root_work_id: input.rootWorkId ?? missionRootId(id),
|
|
138
182
|
specialists: normalizeWorkflowSpecialists(input.specialists),
|
|
139
183
|
limits: normalizeLimits(input.limits),
|
|
140
184
|
automation: normalizeAutomation(input.automation),
|
|
185
|
+
...(mission ? { mission } : {}),
|
|
141
186
|
...(replanning ? { replanning } : {}),
|
|
142
187
|
created_at: new Date().toISOString(),
|
|
143
188
|
};
|
|
@@ -248,7 +293,11 @@ function readSupervisorInternal(
|
|
|
248
293
|
const coordRoot = resolve(coordRootRaw);
|
|
249
294
|
const intent = readSupervisorIntent(coordRoot, goalId);
|
|
250
295
|
const plans = readSupervisorPlans(coordRoot, intent.id, intent.root_work_id);
|
|
251
|
-
const
|
|
296
|
+
const activeRootExists = workIntentExists(coordRoot, plans.active_root_work_id);
|
|
297
|
+
if (!activeRootExists && (!intent.mission || plans.generation > 0)) {
|
|
298
|
+
throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
299
|
+
}
|
|
300
|
+
const work = activeRootExists ? collectSupervisorWork(coordRoot, plans.active_root_work_id) : [];
|
|
252
301
|
const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
|
|
253
302
|
return {
|
|
254
303
|
intent,
|
|
@@ -267,7 +316,6 @@ function deriveProjection(
|
|
|
267
316
|
ignoreLease: boolean,
|
|
268
317
|
): SupervisorProjection {
|
|
269
318
|
const root = work.find((record) => record.intent.id === plans.active_root_work_id);
|
|
270
|
-
if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
271
319
|
const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
272
320
|
const readyWork = work
|
|
273
321
|
.filter((record) => record.projection.state === "ready")
|
|
@@ -311,10 +359,18 @@ function deriveProjection(
|
|
|
311
359
|
...cancelled,
|
|
312
360
|
...terminalBlocked,
|
|
313
361
|
]);
|
|
362
|
+
const initialRootAccepted =
|
|
363
|
+
intent.mission !== undefined &&
|
|
364
|
+
governed.some(
|
|
365
|
+
(record) =>
|
|
366
|
+
record.intent.id === intent.root_work_id && record.projection.state === "succeeded",
|
|
367
|
+
);
|
|
368
|
+
const milestonesCompleted = plans.milestones_completed + (initialRootAccepted ? 1 : 0);
|
|
314
369
|
const base = {
|
|
315
370
|
id: intent.id,
|
|
316
371
|
title: intent.title,
|
|
317
372
|
root_work_id: plans.active_root_work_id,
|
|
373
|
+
root_materialized: root !== undefined,
|
|
318
374
|
work_ids: work.map((record) => record.intent.id),
|
|
319
375
|
ready_work: readyWork,
|
|
320
376
|
resumable_work: resumableWork,
|
|
@@ -326,6 +382,8 @@ function deriveProjection(
|
|
|
326
382
|
plan_generation: plans.generation,
|
|
327
383
|
replans_used: plans.plans.length,
|
|
328
384
|
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - plans.plans.length),
|
|
385
|
+
milestones_completed: milestonesCompleted,
|
|
386
|
+
milestones_remaining: Math.max(0, (intent.mission?.max_milestones ?? 0) - milestonesCompleted),
|
|
329
387
|
pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
|
|
330
388
|
latest_plan_status: plans.latest?.status,
|
|
331
389
|
governed_work_ids: governed.map((record) => record.intent.id),
|
|
@@ -338,11 +396,11 @@ function deriveProjection(
|
|
|
338
396
|
next_action: "wait_for_run",
|
|
339
397
|
};
|
|
340
398
|
}
|
|
341
|
-
if (
|
|
399
|
+
if (intent.mission && plans.completed) {
|
|
342
400
|
return {
|
|
343
401
|
...base,
|
|
344
402
|
state: "succeeded",
|
|
345
|
-
reason: "
|
|
403
|
+
reason: "mission completion was explicitly accepted",
|
|
346
404
|
next_action: "none",
|
|
347
405
|
};
|
|
348
406
|
}
|
|
@@ -370,6 +428,64 @@ function deriveProjection(
|
|
|
370
428
|
next_action: "replan",
|
|
371
429
|
};
|
|
372
430
|
}
|
|
431
|
+
const triggerFingerprint = supervisorGraphFingerprint({
|
|
432
|
+
rootWorkId: plans.active_root_work_id,
|
|
433
|
+
generation: plans.generation,
|
|
434
|
+
work,
|
|
435
|
+
});
|
|
436
|
+
const latestHandledSameGraph =
|
|
437
|
+
plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
|
|
438
|
+
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
439
|
+
plans.latest.status === "attention";
|
|
440
|
+
if (latestHandledSameGraph && plans.latest) {
|
|
441
|
+
return {
|
|
442
|
+
...base,
|
|
443
|
+
state: "awaiting_attention",
|
|
444
|
+
reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
|
|
445
|
+
next_action: "none",
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (!root && intent.mission) {
|
|
449
|
+
if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
|
|
450
|
+
return {
|
|
451
|
+
...base,
|
|
452
|
+
state: "budget_exhausted",
|
|
453
|
+
reason: "mission exhausted its planning budget before creating an initial milestone",
|
|
454
|
+
next_action: "none",
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
...base,
|
|
459
|
+
state: "ready",
|
|
460
|
+
reason: "mission is ready for its initial bounded milestone plan",
|
|
461
|
+
next_action: "plan_initial",
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
465
|
+
if (root.projection.state === "succeeded" && intent.mission) {
|
|
466
|
+
if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
|
|
467
|
+
return {
|
|
468
|
+
...base,
|
|
469
|
+
state: "budget_exhausted",
|
|
470
|
+
reason: "mission exhausted its planning budget at a milestone boundary",
|
|
471
|
+
next_action: "none",
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
...base,
|
|
476
|
+
state: "ready",
|
|
477
|
+
reason: `milestone ${plans.milestones_completed} is accepted and requires mission reassessment`,
|
|
478
|
+
next_action: "plan_milestone",
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (root.projection.state === "succeeded") {
|
|
482
|
+
return {
|
|
483
|
+
...base,
|
|
484
|
+
state: "succeeded",
|
|
485
|
+
reason: "root work was explicitly accepted",
|
|
486
|
+
next_action: "none",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
373
489
|
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
374
490
|
const attemptDispatchable = [
|
|
375
491
|
...readyWork,
|
|
@@ -448,15 +564,6 @@ function deriveProjection(
|
|
|
448
564
|
next_action: "none",
|
|
449
565
|
};
|
|
450
566
|
}
|
|
451
|
-
const triggerFingerprint = supervisorGraphFingerprint({
|
|
452
|
-
rootWorkId: plans.active_root_work_id,
|
|
453
|
-
generation: plans.generation,
|
|
454
|
-
work,
|
|
455
|
-
});
|
|
456
|
-
const latestHandledSameGraph =
|
|
457
|
-
plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
|
|
458
|
-
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
459
|
-
plans.latest.status === "attention";
|
|
460
567
|
const canReplan =
|
|
461
568
|
intent.replanning !== undefined &&
|
|
462
569
|
plans.plans.length < intent.replanning.max_replans &&
|
|
@@ -484,14 +591,6 @@ function deriveProjection(
|
|
|
484
591
|
next_action: "none",
|
|
485
592
|
};
|
|
486
593
|
}
|
|
487
|
-
if (latestHandledSameGraph && plans.latest) {
|
|
488
|
-
return {
|
|
489
|
-
...base,
|
|
490
|
-
state: "awaiting_attention",
|
|
491
|
-
reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
|
|
492
|
-
next_action: "none",
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
594
|
return {
|
|
496
595
|
...base,
|
|
497
596
|
state: "blocked",
|
|
@@ -531,6 +630,19 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
531
630
|
}
|
|
532
631
|
bounded(intent.title, "supervisor title", MAX_TITLE);
|
|
533
632
|
assertWorkId(intent.root_work_id);
|
|
633
|
+
if (intent.mission) {
|
|
634
|
+
const normalizedMission = normalizeMission({
|
|
635
|
+
objective: intent.mission.objective,
|
|
636
|
+
acceptance: intent.mission.acceptance,
|
|
637
|
+
maxMilestones: intent.mission.max_milestones,
|
|
638
|
+
});
|
|
639
|
+
if (JSON.stringify(normalizedMission) !== JSON.stringify(intent.mission)) {
|
|
640
|
+
throw new Error(`supervisor intent ${goalId} mission is not canonical`);
|
|
641
|
+
}
|
|
642
|
+
if (!intent.replanning) {
|
|
643
|
+
throw new Error(`supervisor intent ${goalId} mission requires replanning`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
534
646
|
const specialists = normalizeWorkflowSpecialists(intent.specialists);
|
|
535
647
|
if (JSON.stringify(specialists) !== JSON.stringify(intent.specialists)) {
|
|
536
648
|
throw new Error(`supervisor intent ${goalId} specialists are not canonical`);
|
|
@@ -546,6 +658,32 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
546
658
|
if (intent.replanning) validateReplanning(intent.replanning, specialists, goalId);
|
|
547
659
|
}
|
|
548
660
|
|
|
661
|
+
function normalizeMission(input: CreateSupervisorMissionInput): SupervisorMission {
|
|
662
|
+
if (!Array.isArray(input.acceptance)) {
|
|
663
|
+
throw new Error("supervisor mission acceptance must be an array");
|
|
664
|
+
}
|
|
665
|
+
if (input.acceptance.length < 1 || input.acceptance.length > MAX_ACCEPTANCE) {
|
|
666
|
+
throw new Error(`supervisor mission acceptance must contain 1 to ${MAX_ACCEPTANCE} criteria`);
|
|
667
|
+
}
|
|
668
|
+
const acceptance = input.acceptance.map((criterion, index) =>
|
|
669
|
+
bounded(criterion, `supervisor mission acceptance[${index}]`, 500),
|
|
670
|
+
);
|
|
671
|
+
if (new Set(acceptance).size !== acceptance.length) {
|
|
672
|
+
throw new Error("supervisor mission acceptance criteria must be unique");
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
objective: bounded(input.objective, "supervisor mission objective", MAX_OBJECTIVE),
|
|
676
|
+
acceptance,
|
|
677
|
+
max_milestones: positive(input.maxMilestones ?? 4, "supervisor mission max_milestones", 20),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function missionTitle(mission: SupervisorMission): string {
|
|
682
|
+
return mission.objective.length <= MAX_TITLE
|
|
683
|
+
? mission.objective
|
|
684
|
+
: `${mission.objective.slice(0, MAX_TITLE - 1).trimEnd()}…`;
|
|
685
|
+
}
|
|
686
|
+
|
|
549
687
|
function normalizeLimits(input: Partial<SupervisorLimits> | undefined): SupervisorLimits {
|
|
550
688
|
return {
|
|
551
689
|
max_cycles: positive(input?.max_cycles ?? 50, "supervisor max_cycles", 1_000),
|
|
@@ -685,8 +823,10 @@ function collectGovernedWork(
|
|
|
685
823
|
active: WorkRecord[],
|
|
686
824
|
): WorkRecord[] {
|
|
687
825
|
const governed = new Map<string, WorkRecord>();
|
|
688
|
-
|
|
689
|
-
|
|
826
|
+
if (workIntentExists(coordRoot, originalRootWorkId)) {
|
|
827
|
+
for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
|
|
828
|
+
governed.set(record.intent.id, record);
|
|
829
|
+
}
|
|
690
830
|
}
|
|
691
831
|
for (const workId of plans.applied_work_ids)
|
|
692
832
|
governed.set(workId, readWorkItem(coordRoot, workId));
|
|
@@ -694,6 +834,14 @@ function collectGovernedWork(
|
|
|
694
834
|
return [...governed.values()];
|
|
695
835
|
}
|
|
696
836
|
|
|
837
|
+
function workIntentExists(coordRoot: string, workId: string): boolean {
|
|
838
|
+
return existsSync(join(resolve(coordRoot), ".harnery", "work", workId, "intent.json"));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function missionRootId(goalId: string): string {
|
|
842
|
+
return `${goalId.slice(0, 87)}-mission-root`;
|
|
843
|
+
}
|
|
844
|
+
|
|
697
845
|
function supervisorDir(coordRoot: string, goalId: string): string {
|
|
698
846
|
assertSupervisorId(goalId);
|
|
699
847
|
return join(resolve(coordRoot), ".harnery", "supervisors", goalId);
|