harnery 0.17.0 → 0.19.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/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +187 -4
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +76 -4
- package/dist/core/supervisor/index.d.ts +3 -3
- package/dist/core/supervisor/index.d.ts.map +1 -1
- package/dist/core/supervisor/index.js +2 -2
- package/dist/core/supervisor/plan-read.d.ts +2 -1
- package/dist/core/supervisor/plan-read.d.ts.map +1 -1
- package/dist/core/supervisor/plan-read.js +222 -3
- package/dist/core/supervisor/plan-types.d.ts +60 -3
- package/dist/core/supervisor/plan-types.d.ts.map +1 -1
- package/dist/core/supervisor/plan-types.js +2 -0
- package/dist/core/supervisor/planning.d.ts.map +1 -1
- package/dist/core/supervisor/planning.js +804 -56
- package/dist/core/supervisor/read.d.ts +3 -3
- package/dist/core/supervisor/read.d.ts.map +1 -1
- package/dist/core/supervisor/read.js +2 -2
- 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 +211 -33
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +24 -6
- package/dist/core/workflow/types.d.ts +6 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/validate.d.ts +3 -2
- package/dist/core/workflow/validate.d.ts.map +1 -1
- package/dist/core/workflow/validate.js +31 -3
- package/dist/lib/browser/client.d.ts +18 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +39 -0
- package/dist/lib/browser/geometry.d.ts +146 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -0
- package/dist/lib/browser/geometry.js +525 -0
- package/dist/lib/browser/index.d.ts +2 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/target-size.d.ts +65 -0
- package/dist/lib/browser/target-size.d.ts.map +1 -0
- package/dist/lib/browser/target-size.js +108 -0
- package/package.json +7 -1
- package/src/commands/browse.ts +319 -0
- package/src/commands/supervisor.ts +92 -5
- package/src/core/supervisor/index.ts +17 -1
- package/src/core/supervisor/plan-read.ts +292 -2
- package/src/core/supervisor/plan-types.ts +69 -1
- package/src/core/supervisor/planning.ts +1012 -51
- package/src/core/supervisor/read.ts +16 -1
- package/src/core/supervisor/runner.ts +9 -2
- package/src/core/supervisor/service.ts +3 -0
- package/src/core/supervisor/state.ts +272 -31
- package/src/core/workflow/engine.ts +29 -6
- package/src/core/workflow/types.ts +6 -0
- package/src/core/workflow/validate.ts +34 -3
- package/src/lib/browser/client.ts +62 -0
- package/src/lib/browser/geometry.ts +695 -0
- package/src/lib/browser/index.ts +25 -0
- package/src/lib/browser/target-size.ts +180 -0
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.ts";
|
|
2
1
|
export {
|
|
2
|
+
readSupervisorPlan,
|
|
3
|
+
readSupervisorPlanReviewReceipt,
|
|
4
|
+
readSupervisorPlans,
|
|
5
|
+
} from "./plan-read.ts";
|
|
6
|
+
export {
|
|
7
|
+
MAX_SUPERVISOR_PLAN_REVIEWERS,
|
|
8
|
+
MAX_SUPERVISOR_PLAN_REVISION_ROUNDS,
|
|
3
9
|
SUPERVISOR_PLAN_SCHEMA_VERSION,
|
|
4
10
|
type SupervisorPlanEvent,
|
|
5
11
|
type SupervisorPlanEventType,
|
|
6
12
|
type SupervisorPlanHistory,
|
|
13
|
+
type SupervisorPlanMilestone,
|
|
7
14
|
type SupervisorPlanOutcome,
|
|
8
15
|
type SupervisorPlanProposal,
|
|
9
16
|
type SupervisorPlanRecord,
|
|
10
17
|
type SupervisorPlanRequest,
|
|
18
|
+
type SupervisorPlanReviewFinding,
|
|
19
|
+
type SupervisorPlanReviewReceipt,
|
|
20
|
+
type SupervisorPlanReviewReviewer,
|
|
21
|
+
type SupervisorPlanReviewRound,
|
|
22
|
+
type SupervisorPlanReviewStatus,
|
|
23
|
+
type SupervisorPlanReviewSummary,
|
|
24
|
+
type SupervisorPlanReviewVerdict,
|
|
11
25
|
type SupervisorPlanStatus,
|
|
12
26
|
type SupervisorPlanTemplate,
|
|
13
27
|
type SupervisorPlanWorkSpec,
|
|
@@ -37,6 +51,7 @@ export {
|
|
|
37
51
|
type SupervisorAutomationPolicy,
|
|
38
52
|
type SupervisorIntent,
|
|
39
53
|
type SupervisorLimits,
|
|
54
|
+
type SupervisorMission,
|
|
40
55
|
type SupervisorNextAction,
|
|
41
56
|
type SupervisorProjection,
|
|
42
57
|
type SupervisorRecord,
|
|
@@ -97,9 +97,13 @@ export async function runSupervisor(input: RunSupervisorInput): Promise<Supervis
|
|
|
97
97
|
|
|
98
98
|
const before = graphFingerprint(record);
|
|
99
99
|
let changed = false;
|
|
100
|
-
if (
|
|
100
|
+
if (
|
|
101
|
+
record.projection.next_action === "replan" ||
|
|
102
|
+
record.projection.next_action === "plan_initial" ||
|
|
103
|
+
record.projection.next_action === "plan_milestone"
|
|
104
|
+
) {
|
|
101
105
|
log(
|
|
102
|
-
`[${record.intent.title}]
|
|
106
|
+
`[${record.intent.title}] ${record.projection.next_action} at graph generation ${record.projection.plan_generation}`,
|
|
103
107
|
);
|
|
104
108
|
const plan = await runSupervisorPlanner({
|
|
105
109
|
coordRoot,
|
|
@@ -118,6 +122,9 @@ export async function runSupervisor(input: RunSupervisorInput): Promise<Supervis
|
|
|
118
122
|
record,
|
|
119
123
|
);
|
|
120
124
|
}
|
|
125
|
+
if (record.projection.state === "succeeded") {
|
|
126
|
+
return report("succeeded", record.projection.reason, record);
|
|
127
|
+
}
|
|
121
128
|
if (record.projection.state !== "ready") {
|
|
122
129
|
return report(stopForProjection(record.projection), record.projection.reason, record);
|
|
123
130
|
}
|
|
@@ -616,9 +616,12 @@ function projectionFingerprint(projection: SupervisorRecord["projection"]): stri
|
|
|
616
616
|
projection.attempts_used,
|
|
617
617
|
projection.attempts_remaining,
|
|
618
618
|
projection.root_work_id,
|
|
619
|
+
projection.root_materialized,
|
|
619
620
|
projection.plan_generation,
|
|
620
621
|
projection.replans_used,
|
|
621
622
|
projection.replans_remaining,
|
|
623
|
+
projection.milestones_completed,
|
|
624
|
+
projection.milestones_remaining,
|
|
622
625
|
projection.pending_plan_id,
|
|
623
626
|
projection.latest_plan_status,
|
|
624
627
|
projection.ready_work,
|
|
@@ -20,6 +20,8 @@ import type { WorkflowSpecialistProfile } from "../workflow/types.ts";
|
|
|
20
20
|
import { readSupervisorPlans } from "./plan-read.ts";
|
|
21
21
|
import {
|
|
22
22
|
type CreateSupervisorReplanningInput,
|
|
23
|
+
MAX_SUPERVISOR_PLAN_REVIEWERS,
|
|
24
|
+
MAX_SUPERVISOR_PLAN_REVISION_ROUNDS,
|
|
23
25
|
type SupervisorPlanHistory,
|
|
24
26
|
type SupervisorPlanRecord,
|
|
25
27
|
type SupervisorReplanningPolicy,
|
|
@@ -30,6 +32,8 @@ export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1 as const;
|
|
|
30
32
|
|
|
31
33
|
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
32
34
|
const MAX_TITLE = 200;
|
|
35
|
+
const MAX_OBJECTIVE = 4_000;
|
|
36
|
+
const MAX_ACCEPTANCE = 50;
|
|
33
37
|
const MAX_INTENT_BYTES = 256 * 1024;
|
|
34
38
|
const MAX_TEMPLATES = 20;
|
|
35
39
|
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -50,6 +54,18 @@ export interface SupervisorAutomationPolicy {
|
|
|
50
54
|
retry_blocked: boolean;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
export interface SupervisorMission {
|
|
58
|
+
objective: string;
|
|
59
|
+
acceptance: string[];
|
|
60
|
+
max_milestones: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CreateSupervisorMissionInput {
|
|
64
|
+
objective: string;
|
|
65
|
+
acceptance: readonly string[];
|
|
66
|
+
maxMilestones?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
export interface SupervisorIntent {
|
|
54
70
|
schema_version: typeof SUPERVISOR_INTENT_SCHEMA_VERSION;
|
|
55
71
|
id: string;
|
|
@@ -58,6 +74,7 @@ export interface SupervisorIntent {
|
|
|
58
74
|
specialists: Record<string, WorkflowSpecialistProfile>;
|
|
59
75
|
limits: SupervisorLimits;
|
|
60
76
|
automation: SupervisorAutomationPolicy;
|
|
77
|
+
mission?: SupervisorMission;
|
|
61
78
|
replanning?: SupervisorReplanningPolicy;
|
|
62
79
|
created_at: string;
|
|
63
80
|
}
|
|
@@ -77,6 +94,8 @@ export type SupervisorNextAction =
|
|
|
77
94
|
| "review"
|
|
78
95
|
| "retry"
|
|
79
96
|
| "replan"
|
|
97
|
+
| "plan_initial"
|
|
98
|
+
| "plan_milestone"
|
|
80
99
|
| "review_plan"
|
|
81
100
|
| "none";
|
|
82
101
|
|
|
@@ -84,6 +103,7 @@ export interface SupervisorProjection {
|
|
|
84
103
|
id: string;
|
|
85
104
|
title: string;
|
|
86
105
|
root_work_id: string;
|
|
106
|
+
root_materialized: boolean;
|
|
87
107
|
state: SupervisorState;
|
|
88
108
|
reason: string;
|
|
89
109
|
next_action: SupervisorNextAction;
|
|
@@ -98,6 +118,8 @@ export interface SupervisorProjection {
|
|
|
98
118
|
plan_generation: number;
|
|
99
119
|
replans_used: number;
|
|
100
120
|
replans_remaining: number;
|
|
121
|
+
milestones_completed: number;
|
|
122
|
+
milestones_remaining: number;
|
|
101
123
|
pending_plan_id?: string;
|
|
102
124
|
latest_plan_status?: SupervisorPlanRecord["status"];
|
|
103
125
|
governed_work_ids: string[];
|
|
@@ -112,32 +134,58 @@ export interface SupervisorRecord {
|
|
|
112
134
|
|
|
113
135
|
export interface CreateSupervisorInput {
|
|
114
136
|
coordRoot: string;
|
|
115
|
-
rootWorkId
|
|
137
|
+
rootWorkId?: string;
|
|
116
138
|
specialists: Readonly<Record<string, WorkflowSpecialistProfile>>;
|
|
117
139
|
title?: string;
|
|
118
140
|
id?: string;
|
|
119
141
|
limits?: Partial<SupervisorLimits>;
|
|
120
142
|
automation?: Partial<SupervisorAutomationPolicy>;
|
|
143
|
+
mission?: CreateSupervisorMissionInput;
|
|
121
144
|
replanning?: CreateSupervisorReplanningInput;
|
|
122
145
|
}
|
|
123
146
|
|
|
124
147
|
export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord {
|
|
125
148
|
const coordRoot = resolve(input.coordRoot);
|
|
126
|
-
assertWorkId(input.rootWorkId);
|
|
127
|
-
const root = readWorkItem(coordRoot, input.rootWorkId);
|
|
128
149
|
const id = input.id ?? newSupervisorId();
|
|
129
150
|
assertSupervisorId(id);
|
|
151
|
+
const mission = input.mission ? normalizeMission(input.mission) : undefined;
|
|
152
|
+
if (mission && !input.replanning) {
|
|
153
|
+
throw new Error("supervisor mission requires a replanning policy");
|
|
154
|
+
}
|
|
155
|
+
if (!input.rootWorkId && !mission) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"supervisor creation without root work requires a mission and replanning policy",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (input.rootWorkId) assertWorkId(input.rootWorkId);
|
|
161
|
+
const root = input.rootWorkId ? readWorkItem(coordRoot, input.rootWorkId) : undefined;
|
|
162
|
+
const limits = normalizeLimits(input.limits);
|
|
130
163
|
const replanning = input.replanning
|
|
131
|
-
? normalizeReplanning(input.replanning, input.specialists)
|
|
164
|
+
? normalizeReplanning(input.replanning, input.specialists, limits)
|
|
132
165
|
: undefined;
|
|
166
|
+
if (
|
|
167
|
+
mission &&
|
|
168
|
+
!input.rootWorkId &&
|
|
169
|
+
replanning &&
|
|
170
|
+
replanning.max_replans <= mission.max_milestones
|
|
171
|
+
) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"objective-first mission max_replans must exceed max_milestones to reserve completion review",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
133
176
|
const intent: SupervisorIntent = {
|
|
134
177
|
schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
|
|
135
178
|
id,
|
|
136
|
-
title: bounded(
|
|
137
|
-
|
|
179
|
+
title: bounded(
|
|
180
|
+
input.title ?? root?.intent.title ?? missionTitle(mission!),
|
|
181
|
+
"supervisor title",
|
|
182
|
+
MAX_TITLE,
|
|
183
|
+
),
|
|
184
|
+
root_work_id: input.rootWorkId ?? missionRootId(id),
|
|
138
185
|
specialists: normalizeWorkflowSpecialists(input.specialists),
|
|
139
|
-
limits
|
|
186
|
+
limits,
|
|
140
187
|
automation: normalizeAutomation(input.automation),
|
|
188
|
+
...(mission ? { mission } : {}),
|
|
141
189
|
...(replanning ? { replanning } : {}),
|
|
142
190
|
created_at: new Date().toISOString(),
|
|
143
191
|
};
|
|
@@ -248,7 +296,11 @@ function readSupervisorInternal(
|
|
|
248
296
|
const coordRoot = resolve(coordRootRaw);
|
|
249
297
|
const intent = readSupervisorIntent(coordRoot, goalId);
|
|
250
298
|
const plans = readSupervisorPlans(coordRoot, intent.id, intent.root_work_id);
|
|
251
|
-
const
|
|
299
|
+
const activeRootExists = workIntentExists(coordRoot, plans.active_root_work_id);
|
|
300
|
+
if (!activeRootExists && (!intent.mission || plans.generation > 0)) {
|
|
301
|
+
throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
302
|
+
}
|
|
303
|
+
const work = activeRootExists ? collectSupervisorWork(coordRoot, plans.active_root_work_id) : [];
|
|
252
304
|
const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
|
|
253
305
|
return {
|
|
254
306
|
intent,
|
|
@@ -267,7 +319,6 @@ function deriveProjection(
|
|
|
267
319
|
ignoreLease: boolean,
|
|
268
320
|
): SupervisorProjection {
|
|
269
321
|
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
322
|
const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
272
323
|
const readyWork = work
|
|
273
324
|
.filter((record) => record.projection.state === "ready")
|
|
@@ -311,10 +362,18 @@ function deriveProjection(
|
|
|
311
362
|
...cancelled,
|
|
312
363
|
...terminalBlocked,
|
|
313
364
|
]);
|
|
365
|
+
const initialRootAccepted =
|
|
366
|
+
intent.mission !== undefined &&
|
|
367
|
+
governed.some(
|
|
368
|
+
(record) =>
|
|
369
|
+
record.intent.id === intent.root_work_id && record.projection.state === "succeeded",
|
|
370
|
+
);
|
|
371
|
+
const milestonesCompleted = plans.milestones_completed + (initialRootAccepted ? 1 : 0);
|
|
314
372
|
const base = {
|
|
315
373
|
id: intent.id,
|
|
316
374
|
title: intent.title,
|
|
317
375
|
root_work_id: plans.active_root_work_id,
|
|
376
|
+
root_materialized: root !== undefined,
|
|
318
377
|
work_ids: work.map((record) => record.intent.id),
|
|
319
378
|
ready_work: readyWork,
|
|
320
379
|
resumable_work: resumableWork,
|
|
@@ -326,6 +385,8 @@ function deriveProjection(
|
|
|
326
385
|
plan_generation: plans.generation,
|
|
327
386
|
replans_used: plans.plans.length,
|
|
328
387
|
replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - plans.plans.length),
|
|
388
|
+
milestones_completed: milestonesCompleted,
|
|
389
|
+
milestones_remaining: Math.max(0, (intent.mission?.max_milestones ?? 0) - milestonesCompleted),
|
|
329
390
|
pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
|
|
330
391
|
latest_plan_status: plans.latest?.status,
|
|
331
392
|
governed_work_ids: governed.map((record) => record.intent.id),
|
|
@@ -338,11 +399,11 @@ function deriveProjection(
|
|
|
338
399
|
next_action: "wait_for_run",
|
|
339
400
|
};
|
|
340
401
|
}
|
|
341
|
-
if (
|
|
402
|
+
if (intent.mission && plans.completed) {
|
|
342
403
|
return {
|
|
343
404
|
...base,
|
|
344
405
|
state: "succeeded",
|
|
345
|
-
reason: "
|
|
406
|
+
reason: "mission completion was explicitly accepted",
|
|
346
407
|
next_action: "none",
|
|
347
408
|
};
|
|
348
409
|
}
|
|
@@ -370,6 +431,64 @@ function deriveProjection(
|
|
|
370
431
|
next_action: "replan",
|
|
371
432
|
};
|
|
372
433
|
}
|
|
434
|
+
const triggerFingerprint = supervisorGraphFingerprint({
|
|
435
|
+
rootWorkId: plans.active_root_work_id,
|
|
436
|
+
generation: plans.generation,
|
|
437
|
+
work,
|
|
438
|
+
});
|
|
439
|
+
const latestHandledSameGraph =
|
|
440
|
+
plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
|
|
441
|
+
plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
|
|
442
|
+
plans.latest.status === "attention";
|
|
443
|
+
if (latestHandledSameGraph && plans.latest) {
|
|
444
|
+
return {
|
|
445
|
+
...base,
|
|
446
|
+
state: "awaiting_attention",
|
|
447
|
+
reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
|
|
448
|
+
next_action: "none",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (!root && intent.mission) {
|
|
452
|
+
if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
|
|
453
|
+
return {
|
|
454
|
+
...base,
|
|
455
|
+
state: "budget_exhausted",
|
|
456
|
+
reason: "mission exhausted its planning budget before creating an initial milestone",
|
|
457
|
+
next_action: "none",
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
...base,
|
|
462
|
+
state: "ready",
|
|
463
|
+
reason: "mission is ready for its initial bounded milestone plan",
|
|
464
|
+
next_action: "plan_initial",
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
468
|
+
if (root.projection.state === "succeeded" && intent.mission) {
|
|
469
|
+
if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
|
|
470
|
+
return {
|
|
471
|
+
...base,
|
|
472
|
+
state: "budget_exhausted",
|
|
473
|
+
reason: "mission exhausted its planning budget at a milestone boundary",
|
|
474
|
+
next_action: "none",
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
...base,
|
|
479
|
+
state: "ready",
|
|
480
|
+
reason: `milestone ${plans.milestones_completed} is accepted and requires mission reassessment`,
|
|
481
|
+
next_action: "plan_milestone",
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (root.projection.state === "succeeded") {
|
|
485
|
+
return {
|
|
486
|
+
...base,
|
|
487
|
+
state: "succeeded",
|
|
488
|
+
reason: "root work was explicitly accepted",
|
|
489
|
+
next_action: "none",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
373
492
|
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
374
493
|
const attemptDispatchable = [
|
|
375
494
|
...readyWork,
|
|
@@ -448,15 +567,6 @@ function deriveProjection(
|
|
|
448
567
|
next_action: "none",
|
|
449
568
|
};
|
|
450
569
|
}
|
|
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
570
|
const canReplan =
|
|
461
571
|
intent.replanning !== undefined &&
|
|
462
572
|
plans.plans.length < intent.replanning.max_replans &&
|
|
@@ -484,14 +594,6 @@ function deriveProjection(
|
|
|
484
594
|
next_action: "none",
|
|
485
595
|
};
|
|
486
596
|
}
|
|
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
597
|
return {
|
|
496
598
|
...base,
|
|
497
599
|
state: "blocked",
|
|
@@ -531,6 +633,19 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
531
633
|
}
|
|
532
634
|
bounded(intent.title, "supervisor title", MAX_TITLE);
|
|
533
635
|
assertWorkId(intent.root_work_id);
|
|
636
|
+
if (intent.mission) {
|
|
637
|
+
const normalizedMission = normalizeMission({
|
|
638
|
+
objective: intent.mission.objective,
|
|
639
|
+
acceptance: intent.mission.acceptance,
|
|
640
|
+
maxMilestones: intent.mission.max_milestones,
|
|
641
|
+
});
|
|
642
|
+
if (JSON.stringify(normalizedMission) !== JSON.stringify(intent.mission)) {
|
|
643
|
+
throw new Error(`supervisor intent ${goalId} mission is not canonical`);
|
|
644
|
+
}
|
|
645
|
+
if (!intent.replanning) {
|
|
646
|
+
throw new Error(`supervisor intent ${goalId} mission requires replanning`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
534
649
|
const specialists = normalizeWorkflowSpecialists(intent.specialists);
|
|
535
650
|
if (JSON.stringify(specialists) !== JSON.stringify(intent.specialists)) {
|
|
536
651
|
throw new Error(`supervisor intent ${goalId} specialists are not canonical`);
|
|
@@ -543,7 +658,33 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
|
|
|
543
658
|
) {
|
|
544
659
|
throw new Error(`supervisor intent ${goalId} automation policy is not canonical`);
|
|
545
660
|
}
|
|
546
|
-
if (intent.replanning) validateReplanning(intent.replanning, specialists, goalId);
|
|
661
|
+
if (intent.replanning) validateReplanning(intent.replanning, specialists, intent.limits, goalId);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function normalizeMission(input: CreateSupervisorMissionInput): SupervisorMission {
|
|
665
|
+
if (!Array.isArray(input.acceptance)) {
|
|
666
|
+
throw new Error("supervisor mission acceptance must be an array");
|
|
667
|
+
}
|
|
668
|
+
if (input.acceptance.length < 1 || input.acceptance.length > MAX_ACCEPTANCE) {
|
|
669
|
+
throw new Error(`supervisor mission acceptance must contain 1 to ${MAX_ACCEPTANCE} criteria`);
|
|
670
|
+
}
|
|
671
|
+
const acceptance = input.acceptance.map((criterion, index) =>
|
|
672
|
+
bounded(criterion, `supervisor mission acceptance[${index}]`, 500),
|
|
673
|
+
);
|
|
674
|
+
if (new Set(acceptance).size !== acceptance.length) {
|
|
675
|
+
throw new Error("supervisor mission acceptance criteria must be unique");
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
objective: bounded(input.objective, "supervisor mission objective", MAX_OBJECTIVE),
|
|
679
|
+
acceptance,
|
|
680
|
+
max_milestones: positive(input.maxMilestones ?? 4, "supervisor mission max_milestones", 20),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function missionTitle(mission: SupervisorMission): string {
|
|
685
|
+
return mission.objective.length <= MAX_TITLE
|
|
686
|
+
? mission.objective
|
|
687
|
+
: `${mission.objective.slice(0, MAX_TITLE - 1).trimEnd()}…`;
|
|
547
688
|
}
|
|
548
689
|
|
|
549
690
|
function normalizeLimits(input: Partial<SupervisorLimits> | undefined): SupervisorLimits {
|
|
@@ -582,6 +723,7 @@ function normalizeAutomation(
|
|
|
582
723
|
function normalizeReplanning(
|
|
583
724
|
input: CreateSupervisorReplanningInput,
|
|
584
725
|
specialistsInput: Readonly<Record<string, WorkflowSpecialistProfile>>,
|
|
726
|
+
limits: SupervisorLimits,
|
|
585
727
|
): SupervisorReplanningPolicy {
|
|
586
728
|
const specialists = normalizeWorkflowSpecialists(specialistsInput);
|
|
587
729
|
const plannerSpecialist = bounded(input.plannerSpecialist, "planner specialist", 100);
|
|
@@ -631,6 +773,9 @@ function normalizeReplanning(
|
|
|
631
773
|
if (maxWorkItemsPerPlan > maxTotalWorkItems) {
|
|
632
774
|
throw new Error("replanning max_work_items_per_plan cannot exceed max_total_work_items");
|
|
633
775
|
}
|
|
776
|
+
const review = input.review
|
|
777
|
+
? normalizeReviewPolicy(input.review, specialists, plannerSpecialist, limits)
|
|
778
|
+
: undefined;
|
|
634
779
|
return {
|
|
635
780
|
planner_specialist: plannerSpecialist,
|
|
636
781
|
auto_apply: boolean(input.autoApply ?? false, "replanning auto_apply"),
|
|
@@ -638,12 +783,60 @@ function normalizeReplanning(
|
|
|
638
783
|
max_work_items_per_plan: maxWorkItemsPerPlan,
|
|
639
784
|
max_total_work_items: maxTotalWorkItems,
|
|
640
785
|
templates,
|
|
786
|
+
...(review ? { review } : {}),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function normalizeReviewPolicy(
|
|
791
|
+
input: NonNullable<CreateSupervisorReplanningInput["review"]>,
|
|
792
|
+
specialists: Record<string, WorkflowSpecialistProfile>,
|
|
793
|
+
plannerSpecialist: string,
|
|
794
|
+
limits: SupervisorLimits,
|
|
795
|
+
): NonNullable<SupervisorReplanningPolicy["review"]> {
|
|
796
|
+
if (!Array.isArray(input.reviewerSpecialists) || input.reviewerSpecialists.length < 1) {
|
|
797
|
+
throw new Error("replanning review requires at least one reviewer specialist");
|
|
798
|
+
}
|
|
799
|
+
if (input.reviewerSpecialists.length > MAX_SUPERVISOR_PLAN_REVIEWERS) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
`replanning review cannot exceed ${MAX_SUPERVISOR_PLAN_REVIEWERS} reviewer specialists`,
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
const reviewerSpecialists = input.reviewerSpecialists.map((specialist, index) =>
|
|
805
|
+
bounded(specialist, `reviewer specialist[${index}]`, 100),
|
|
806
|
+
);
|
|
807
|
+
if (new Set(reviewerSpecialists).size !== reviewerSpecialists.length) {
|
|
808
|
+
throw new Error("replanning reviewer specialists must be unique");
|
|
809
|
+
}
|
|
810
|
+
for (const specialist of reviewerSpecialists) {
|
|
811
|
+
if (!specialists[specialist]) {
|
|
812
|
+
throw new Error(`reviewer specialist ${specialist} is not present in the frozen team`);
|
|
813
|
+
}
|
|
814
|
+
if (specialist === plannerSpecialist) {
|
|
815
|
+
throw new Error("reviewer specialist cannot be the planner specialist");
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const maxRevisionRounds = nonNegative(
|
|
819
|
+
input.maxRevisionRounds,
|
|
820
|
+
"replanning review max_revision_rounds",
|
|
821
|
+
MAX_SUPERVISOR_PLAN_REVISION_ROUNDS,
|
|
822
|
+
);
|
|
823
|
+
const worstCaseAgents =
|
|
824
|
+
1 + reviewerSpecialists.length * (maxRevisionRounds + 1) + maxRevisionRounds;
|
|
825
|
+
if (worstCaseAgents > limits.max_agents_per_work) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
`replanning review worst-case ${worstCaseAgents} agents exceeds max_agents_per_work ${limits.max_agents_per_work}`,
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
reviewer_specialists: reviewerSpecialists,
|
|
832
|
+
max_revision_rounds: maxRevisionRounds,
|
|
641
833
|
};
|
|
642
834
|
}
|
|
643
835
|
|
|
644
836
|
function validateReplanning(
|
|
645
837
|
policy: SupervisorReplanningPolicy,
|
|
646
838
|
specialists: Record<string, WorkflowSpecialistProfile>,
|
|
839
|
+
limits: SupervisorLimits,
|
|
647
840
|
goalId: string,
|
|
648
841
|
): void {
|
|
649
842
|
if (!specialists[policy.planner_specialist]) {
|
|
@@ -676,6 +869,37 @@ function validateReplanning(
|
|
|
676
869
|
rootCapable ||= template.root;
|
|
677
870
|
}
|
|
678
871
|
if (!rootCapable) throw new Error(`supervisor ${goalId} has no root-capable replanning template`);
|
|
872
|
+
if (policy.review !== undefined) {
|
|
873
|
+
const review = policy.review;
|
|
874
|
+
if (
|
|
875
|
+
!Array.isArray(review.reviewer_specialists) ||
|
|
876
|
+
review.reviewer_specialists.length < 1 ||
|
|
877
|
+
review.reviewer_specialists.length > MAX_SUPERVISOR_PLAN_REVIEWERS ||
|
|
878
|
+
!Number.isSafeInteger(review.max_revision_rounds) ||
|
|
879
|
+
review.max_revision_rounds < 0 ||
|
|
880
|
+
review.max_revision_rounds > MAX_SUPERVISOR_PLAN_REVISION_ROUNDS
|
|
881
|
+
) {
|
|
882
|
+
throw new Error(`supervisor ${goalId} replanning review policy is invalid`);
|
|
883
|
+
}
|
|
884
|
+
if (new Set(review.reviewer_specialists).size !== review.reviewer_specialists.length) {
|
|
885
|
+
throw new Error(`supervisor ${goalId} replanning reviewers are not unique`);
|
|
886
|
+
}
|
|
887
|
+
for (const specialist of review.reviewer_specialists) {
|
|
888
|
+
if (!specialists[specialist]) {
|
|
889
|
+
throw new Error(`supervisor ${goalId} reviewer specialist is not in the frozen team`);
|
|
890
|
+
}
|
|
891
|
+
if (specialist === policy.planner_specialist) {
|
|
892
|
+
throw new Error(`supervisor ${goalId} reviewer specialist cannot be planner`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const worstCaseAgents =
|
|
896
|
+
1 +
|
|
897
|
+
review.reviewer_specialists.length * (review.max_revision_rounds + 1) +
|
|
898
|
+
review.max_revision_rounds;
|
|
899
|
+
if (worstCaseAgents > limits.max_agents_per_work) {
|
|
900
|
+
throw new Error(`supervisor ${goalId} review policy exceeds the agent budget`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
679
903
|
}
|
|
680
904
|
|
|
681
905
|
function collectGovernedWork(
|
|
@@ -685,8 +909,10 @@ function collectGovernedWork(
|
|
|
685
909
|
active: WorkRecord[],
|
|
686
910
|
): WorkRecord[] {
|
|
687
911
|
const governed = new Map<string, WorkRecord>();
|
|
688
|
-
|
|
689
|
-
|
|
912
|
+
if (workIntentExists(coordRoot, originalRootWorkId)) {
|
|
913
|
+
for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
|
|
914
|
+
governed.set(record.intent.id, record);
|
|
915
|
+
}
|
|
690
916
|
}
|
|
691
917
|
for (const workId of plans.applied_work_ids)
|
|
692
918
|
governed.set(workId, readWorkItem(coordRoot, workId));
|
|
@@ -694,6 +920,14 @@ function collectGovernedWork(
|
|
|
694
920
|
return [...governed.values()];
|
|
695
921
|
}
|
|
696
922
|
|
|
923
|
+
function workIntentExists(coordRoot: string, workId: string): boolean {
|
|
924
|
+
return existsSync(join(resolve(coordRoot), ".harnery", "work", workId, "intent.json"));
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function missionRootId(goalId: string): string {
|
|
928
|
+
return `${goalId.slice(0, 87)}-mission-root`;
|
|
929
|
+
}
|
|
930
|
+
|
|
697
931
|
function supervisorDir(coordRoot: string, goalId: string): string {
|
|
698
932
|
assertSupervisorId(goalId);
|
|
699
933
|
return join(resolve(coordRoot), ".harnery", "supervisors", goalId);
|
|
@@ -767,6 +1001,13 @@ function positive(value: unknown, field: string, max: number): number {
|
|
|
767
1001
|
return value as number;
|
|
768
1002
|
}
|
|
769
1003
|
|
|
1004
|
+
function nonNegative(value: unknown, field: string, max: number): number {
|
|
1005
|
+
if (!Number.isSafeInteger(value) || (value as number) < 0 || (value as number) > max) {
|
|
1006
|
+
throw new Error(`${field} must be an integer between 0 and ${max}`);
|
|
1007
|
+
}
|
|
1008
|
+
return value as number;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
770
1011
|
function boolean(value: unknown, field: string): boolean {
|
|
771
1012
|
if (typeof value !== "boolean") throw new Error(`supervisor ${field} must be boolean`);
|
|
772
1013
|
return value;
|
|
@@ -58,6 +58,7 @@ import type {
|
|
|
58
58
|
HarnessName,
|
|
59
59
|
RunReport,
|
|
60
60
|
SpawnResult,
|
|
61
|
+
StageSchema,
|
|
61
62
|
WorkflowAgentProof,
|
|
62
63
|
WorkflowContext,
|
|
63
64
|
WorkflowEvidenceRecord,
|
|
@@ -423,6 +424,9 @@ async function executeWorkflow(
|
|
|
423
424
|
const assignment = resolveSpecialistAssignment(specialists, prompt, requestedOpts);
|
|
424
425
|
const agentOpts = assignment.opts;
|
|
425
426
|
const assignmentPrompt = assignment.prompt;
|
|
427
|
+
const dispatchPrompt = agentOpts.schema
|
|
428
|
+
? withSchemaContract(assignmentPrompt, agentOpts.schema)
|
|
429
|
+
: assignmentPrompt;
|
|
426
430
|
let reservedForDispatch = 0;
|
|
427
431
|
let spawnCountClaimed = false;
|
|
428
432
|
const harness = agentOpts.harness ?? defaultHarness;
|
|
@@ -487,7 +491,7 @@ async function executeWorkflow(
|
|
|
487
491
|
max_attempts: maxAttempts,
|
|
488
492
|
max_turns: agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
489
493
|
timeout_ms: agentOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
490
|
-
prompt_bytes: Buffer.byteLength(
|
|
494
|
+
prompt_bytes: Buffer.byteLength(dispatchPrompt),
|
|
491
495
|
isolation,
|
|
492
496
|
network_access: networkAccess,
|
|
493
497
|
current_cost_usd: round4(costUsd + reservedCostUsd),
|
|
@@ -609,7 +613,7 @@ async function executeWorkflow(
|
|
|
609
613
|
});
|
|
610
614
|
log(`[${name}] ${currentStage || "(no stage)"} → ${id} [${harness}] ${label}`);
|
|
611
615
|
|
|
612
|
-
let attemptPrompt =
|
|
616
|
+
let attemptPrompt = dispatchPrompt;
|
|
613
617
|
let last: SpawnResult | null = null;
|
|
614
618
|
let agentCostUsd = 0;
|
|
615
619
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -678,12 +682,16 @@ async function executeWorkflow(
|
|
|
678
682
|
}
|
|
679
683
|
|
|
680
684
|
journal("agent.schema_retry", { id, attempt, problems });
|
|
681
|
-
//
|
|
682
|
-
//
|
|
685
|
+
// Keep the original cache identity while making the actual output
|
|
686
|
+
// contract explicit. A full bounded copy of the rejected value lets the
|
|
687
|
+
// child repair omissions instead of reconstructing an unseen object.
|
|
683
688
|
attemptPrompt =
|
|
684
|
-
`${
|
|
689
|
+
`${dispatchPrompt}\n\nYour previous reply failed validation. ` +
|
|
690
|
+
`Return a complete replacement value, not a patch.\n\n` +
|
|
691
|
+
`Previous reply (JSON string): ${JSON.stringify(boundedSchemaReply(last.text))}\n\n` +
|
|
692
|
+
`Validation problems:\n` +
|
|
685
693
|
`${problems.map((p) => ` - ${p}`).join("\n")}\n` +
|
|
686
|
-
`Reply with ONLY the corrected JSON
|
|
694
|
+
`Reply with ONLY the corrected JSON value. No prose, no code fences.`;
|
|
687
695
|
}
|
|
688
696
|
|
|
689
697
|
const reason = last?.ok
|
|
@@ -892,6 +900,21 @@ async function executeWorkflow(
|
|
|
892
900
|
}
|
|
893
901
|
}
|
|
894
902
|
|
|
903
|
+
const MAX_SCHEMA_RETRY_REPLY = 16_000;
|
|
904
|
+
|
|
905
|
+
function withSchemaContract(prompt: string, schema: StageSchema): string {
|
|
906
|
+
return (
|
|
907
|
+
`${prompt}\n\nOutput contract:\n` +
|
|
908
|
+
`Return ONLY one JSON value matching this schema. Do not use prose or code fences.\n` +
|
|
909
|
+
`${JSON.stringify(schema)}`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function boundedSchemaReply(value: string): string {
|
|
914
|
+
if (value.length <= MAX_SCHEMA_RETRY_REPLY) return value;
|
|
915
|
+
return `${value.slice(0, MAX_SCHEMA_RETRY_REPLY)}\n[truncated by Harnery]`;
|
|
916
|
+
}
|
|
917
|
+
|
|
895
918
|
/** Stable identity for one agent() call, for the resume cache. The ORIGINAL
|
|
896
919
|
* prompt (never a retry-mutated one) plus everything that changes behavior. */
|
|
897
920
|
function agentCallKey(
|
|
@@ -206,6 +206,12 @@ export interface StageSchema {
|
|
|
206
206
|
required?: string[];
|
|
207
207
|
/** type=array */
|
|
208
208
|
items?: StageSchema;
|
|
209
|
+
minItems?: number;
|
|
210
|
+
maxItems?: number;
|
|
211
|
+
/** type=string */
|
|
212
|
+
minLength?: number;
|
|
213
|
+
maxLength?: number;
|
|
214
|
+
pattern?: string;
|
|
209
215
|
/** any type: closed value set (compared with ===) */
|
|
210
216
|
enum?: Array<string | number | boolean>;
|
|
211
217
|
}
|