harnery 0.16.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 +223 -5
- package/dist/core/supervisor/index.d.ts +4 -1
- package/dist/core/supervisor/index.d.ts.map +1 -1
- package/dist/core/supervisor/index.js +3 -0
- package/dist/core/supervisor/plan-api.d.ts +16 -0
- package/dist/core/supervisor/plan-api.d.ts.map +1 -0
- package/dist/core/supervisor/plan-api.js +34 -0
- package/dist/core/supervisor/plan-read.d.ts +4 -0
- package/dist/core/supervisor/plan-read.d.ts.map +1 -0
- package/dist/core/supervisor/plan-read.js +315 -0
- package/dist/core/supervisor/plan-types.d.ts +123 -0
- package/dist/core/supervisor/plan-types.d.ts.map +1 -0
- package/dist/core/supervisor/plan-types.js +14 -0
- package/dist/core/supervisor/planning.d.ts +26 -0
- package/dist/core/supervisor/planning.d.ts.map +1 -0
- package/dist/core/supervisor/planning.js +579 -0
- package/dist/core/supervisor/read.d.ts +3 -1
- package/dist/core/supervisor/read.d.ts.map +1 -1
- package/dist/core/supervisor/read.js +2 -0
- package/dist/core/supervisor/runner.d.ts +3 -0
- package/dist/core/supervisor/runner.d.ts.map +1 -1
- package/dist/core/supervisor/runner.js +42 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +10 -0
- package/dist/core/supervisor/state.d.ts +27 -2
- package/dist/core/supervisor/state.d.ts.map +1 -1
- package/dist/core/supervisor/state.js +302 -13
- package/package.json +1 -1
- package/src/commands/supervisor.ts +275 -5
- package/src/core/supervisor/index.ts +21 -0
- package/src/core/supervisor/plan-api.ts +47 -0
- package/src/core/supervisor/plan-read.ts +356 -0
- package/src/core/supervisor/plan-types.ts +157 -0
- package/src/core/supervisor/planning.ts +698 -0
- package/src/core/supervisor/read.ts +17 -0
- package/src/core/supervisor/runner.ts +46 -2
- package/src/core/supervisor/service.ts +10 -0
- package/src/core/supervisor/state.ts +389 -12
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync, chmodSync, existsSync, linkSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { createWorkItem, readWorkItem } from "../work/index.js";
|
|
5
|
+
import { readWorkflowProof, runWorkflow, WorkflowParkedError, workflowScriptDigest, } from "../workflow/index.js";
|
|
6
|
+
import { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
|
|
7
|
+
import { SUPERVISOR_PLAN_SCHEMA_VERSION, supervisorGraphFingerprint, } from "./plan-types.js";
|
|
8
|
+
const PLAN_KEY = /^[a-z][a-z0-9-]{0,31}$/;
|
|
9
|
+
const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
10
|
+
const MAX_RECORD_BYTES = 256 * 1024;
|
|
11
|
+
const MAX_EVENTS_BYTES = 512 * 1024;
|
|
12
|
+
const MAX_REASON = 2_000;
|
|
13
|
+
export async function runSupervisorPlanner(input) {
|
|
14
|
+
const coordRoot = resolve(input.coordRoot);
|
|
15
|
+
const policy = input.record.intent.replanning;
|
|
16
|
+
if (!policy)
|
|
17
|
+
throw new Error(`supervisor ${input.record.intent.id} does not allow replanning`);
|
|
18
|
+
const history = readSupervisorPlans(coordRoot, input.record.intent.id, input.record.intent.root_work_id);
|
|
19
|
+
const triggerFingerprint = supervisorGraphFingerprint({
|
|
20
|
+
rootWorkId: input.record.projection.root_work_id,
|
|
21
|
+
generation: input.record.projection.plan_generation,
|
|
22
|
+
work: input.record.work,
|
|
23
|
+
});
|
|
24
|
+
const trigger = planTrigger(input.record);
|
|
25
|
+
const resumable = history.latest?.status === "resumable" &&
|
|
26
|
+
history.latest.request.trigger_fingerprint === triggerFingerprint &&
|
|
27
|
+
history.latest.request.prior_root_work_id === input.record.projection.root_work_id
|
|
28
|
+
? history.latest
|
|
29
|
+
: undefined;
|
|
30
|
+
const request = resumable
|
|
31
|
+
? resumable.request
|
|
32
|
+
: createPlanRequest(coordRoot, input.record, history.plans.length + 1, triggerFingerprint, trigger);
|
|
33
|
+
const scriptPath = planScriptPath(coordRoot, input.record.intent.id, request.id);
|
|
34
|
+
if (resumable) {
|
|
35
|
+
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
36
|
+
event: "plan.resumed",
|
|
37
|
+
actor: input.actor,
|
|
38
|
+
reason: `resuming planner workflow after approval ${resumable.approval_id}`,
|
|
39
|
+
approval_id: resumable.approval_id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
let proposalWritten = false;
|
|
43
|
+
try {
|
|
44
|
+
const report = await runWorkflow(scriptPath, {
|
|
45
|
+
...input.engine,
|
|
46
|
+
coordRoot,
|
|
47
|
+
...(resumable
|
|
48
|
+
? { resumeRunId: request.workflow_run_id }
|
|
49
|
+
: { runId: request.workflow_run_id }),
|
|
50
|
+
specialists: input.record.intent.specialists,
|
|
51
|
+
maxAgents: 1,
|
|
52
|
+
concurrency: 1,
|
|
53
|
+
onLog: input.onLog,
|
|
54
|
+
});
|
|
55
|
+
const proposal = normalizeProposal(request.id, report.result, input.record, history.materialized_work_ids.length, request.trigger ?? "recovery");
|
|
56
|
+
writeExclusiveJson(planProposalPath(coordRoot, input.record.intent.id, request.id), proposal, "supervisor plan proposal");
|
|
57
|
+
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
58
|
+
event: "plan.proposed",
|
|
59
|
+
actor: input.actor,
|
|
60
|
+
reason: proposal.rationale,
|
|
61
|
+
});
|
|
62
|
+
proposalWritten = true;
|
|
63
|
+
if (proposal.decision === "attention") {
|
|
64
|
+
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
65
|
+
event: "plan.attention",
|
|
66
|
+
actor: input.actor,
|
|
67
|
+
reason: proposal.rationale,
|
|
68
|
+
});
|
|
69
|
+
return outcome(readSupervisorPlan(coordRoot, input.record.intent.id, request.id));
|
|
70
|
+
}
|
|
71
|
+
if (policy.auto_apply) {
|
|
72
|
+
return applySupervisorPlanProposal({
|
|
73
|
+
coordRoot,
|
|
74
|
+
record: input.record,
|
|
75
|
+
planId: request.id,
|
|
76
|
+
actor: input.actor,
|
|
77
|
+
reason: "proposal applied by frozen supervisor replanning policy",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return outcome(readSupervisorPlan(coordRoot, input.record.intent.id, request.id));
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (proposalWritten)
|
|
84
|
+
throw error;
|
|
85
|
+
if (error instanceof WorkflowParkedError) {
|
|
86
|
+
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
87
|
+
event: "plan.awaiting_approval",
|
|
88
|
+
actor: input.actor,
|
|
89
|
+
reason: `planner workflow parked for approval ${error.approvalId}`,
|
|
90
|
+
approval_id: error.approvalId,
|
|
91
|
+
});
|
|
92
|
+
return outcome(readSupervisorPlan(coordRoot, input.record.intent.id, request.id));
|
|
93
|
+
}
|
|
94
|
+
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
95
|
+
event: "plan.failed",
|
|
96
|
+
actor: input.actor,
|
|
97
|
+
reason: bounded(error.message, "plan failure", MAX_REASON),
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function applySupervisorPlanProposal(input) {
|
|
103
|
+
const coordRoot = resolve(input.coordRoot);
|
|
104
|
+
const policy = input.record.intent.replanning;
|
|
105
|
+
if (!policy)
|
|
106
|
+
throw new Error(`supervisor ${input.record.intent.id} does not allow replanning`);
|
|
107
|
+
const plan = readSupervisorPlan(coordRoot, input.record.intent.id, input.planId);
|
|
108
|
+
if (plan.status === "applied" || plan.status === "completed")
|
|
109
|
+
return outcome(plan);
|
|
110
|
+
if (plan.status !== "proposed" ||
|
|
111
|
+
!plan.proposal ||
|
|
112
|
+
!["apply", "complete"].includes(plan.proposal.decision)) {
|
|
113
|
+
throw new Error(`supervisor plan ${input.planId} cannot be applied from ${plan.status}`);
|
|
114
|
+
}
|
|
115
|
+
if (plan.request.prior_root_work_id !== input.record.projection.root_work_id) {
|
|
116
|
+
throw new Error(`supervisor plan ${input.planId} targets stale root ${plan.request.prior_root_work_id}`);
|
|
117
|
+
}
|
|
118
|
+
const history = readSupervisorPlans(coordRoot, input.record.intent.id, input.record.intent.root_work_id);
|
|
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
|
+
}
|
|
128
|
+
const ids = new Map();
|
|
129
|
+
const created = [];
|
|
130
|
+
for (const spec of proposal.work) {
|
|
131
|
+
const template = policy.templates[spec.template];
|
|
132
|
+
if (!template)
|
|
133
|
+
throw new Error(`plan work ${spec.key} references unknown template ${spec.template}`);
|
|
134
|
+
if (workflowScriptDigest(template.workflow.path) !== template.workflow.sha256) {
|
|
135
|
+
throw new Error(`replanning template ${spec.template} changed after goal creation`);
|
|
136
|
+
}
|
|
137
|
+
const workId = `${input.planId}-${spec.key}`;
|
|
138
|
+
const dependencies = spec.dependencies.map((dependency) => ids.get(dependency) ?? dependency);
|
|
139
|
+
const expected = {
|
|
140
|
+
title: spec.title,
|
|
141
|
+
objective: spec.objective,
|
|
142
|
+
acceptance: spec.acceptance,
|
|
143
|
+
dependencies,
|
|
144
|
+
workflow: template.workflow,
|
|
145
|
+
max_attempts: template.max_attempts,
|
|
146
|
+
source: {
|
|
147
|
+
kind: "workflow",
|
|
148
|
+
ref: `supervisor:${input.record.intent.id}/plan:${input.planId}`,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
if (existsSync(join(coordRoot, ".harnery", "work", workId, "intent.json"))) {
|
|
152
|
+
const current = readWorkItem(coordRoot, workId).intent;
|
|
153
|
+
const actual = {
|
|
154
|
+
title: current.title,
|
|
155
|
+
objective: current.objective,
|
|
156
|
+
acceptance: current.acceptance,
|
|
157
|
+
dependencies: current.dependencies,
|
|
158
|
+
workflow: current.workflow,
|
|
159
|
+
max_attempts: current.max_attempts,
|
|
160
|
+
source: current.source,
|
|
161
|
+
};
|
|
162
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
163
|
+
throw new Error(`partial plan work ${workId} conflicts with the durable proposal`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
createWorkItem({
|
|
168
|
+
coordRoot,
|
|
169
|
+
id: workId,
|
|
170
|
+
title: spec.title,
|
|
171
|
+
objective: spec.objective,
|
|
172
|
+
acceptance: spec.acceptance,
|
|
173
|
+
dependencies,
|
|
174
|
+
workflowPath: template.workflow.path,
|
|
175
|
+
maxAttempts: template.max_attempts,
|
|
176
|
+
source: expected.source,
|
|
177
|
+
actor: input.actor,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
ids.set(spec.key, workId);
|
|
181
|
+
created.push(workId);
|
|
182
|
+
}
|
|
183
|
+
const rootWorkId = ids.get(proposal.root);
|
|
184
|
+
if (!rootWorkId)
|
|
185
|
+
throw new Error(`plan root ${proposal.root} was not materialized`);
|
|
186
|
+
appendPlanEvent(coordRoot, input.record.intent.id, input.planId, {
|
|
187
|
+
event: "plan.applied",
|
|
188
|
+
actor: input.actor,
|
|
189
|
+
reason: input.reason ?? "supervisor plan explicitly approved",
|
|
190
|
+
root_work_id: rootWorkId,
|
|
191
|
+
work_ids: created,
|
|
192
|
+
});
|
|
193
|
+
return outcome(readSupervisorPlan(coordRoot, input.record.intent.id, input.planId));
|
|
194
|
+
}
|
|
195
|
+
export function rejectSupervisorPlanProposal(input) {
|
|
196
|
+
const plan = readSupervisorPlan(input.coordRoot, input.goalId, input.planId);
|
|
197
|
+
if (plan.status === "rejected")
|
|
198
|
+
return outcome(plan);
|
|
199
|
+
if (plan.status !== "proposed") {
|
|
200
|
+
throw new Error(`supervisor plan ${input.planId} cannot be rejected from ${plan.status}`);
|
|
201
|
+
}
|
|
202
|
+
appendPlanEvent(input.coordRoot, input.goalId, input.planId, {
|
|
203
|
+
event: "plan.rejected",
|
|
204
|
+
actor: input.actor,
|
|
205
|
+
reason: input.reason,
|
|
206
|
+
});
|
|
207
|
+
return outcome(readSupervisorPlan(input.coordRoot, input.goalId, input.planId));
|
|
208
|
+
}
|
|
209
|
+
function createPlanRequest(coordRoot, record, sequence, triggerFingerprint, trigger) {
|
|
210
|
+
const policy = record.intent.replanning;
|
|
211
|
+
if (!policy)
|
|
212
|
+
throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
|
|
213
|
+
if (sequence > policy.max_replans) {
|
|
214
|
+
throw new Error(`supervisor ${record.intent.id} exhausted its ${policy.max_replans} replans`);
|
|
215
|
+
}
|
|
216
|
+
const planId = `plan-${String(sequence).padStart(4, "0")}-${randomBytes(4).toString("hex")}`;
|
|
217
|
+
const request = {
|
|
218
|
+
schema_version: SUPERVISOR_PLAN_SCHEMA_VERSION,
|
|
219
|
+
id: planId,
|
|
220
|
+
goal_id: record.intent.id,
|
|
221
|
+
sequence,
|
|
222
|
+
trigger,
|
|
223
|
+
trigger_fingerprint: triggerFingerprint,
|
|
224
|
+
prior_root_work_id: record.projection.root_work_id,
|
|
225
|
+
workflow_run_id: `${planId}-workflow`,
|
|
226
|
+
created_at: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
const script = plannerScript(coordRoot, record, planId, trigger);
|
|
229
|
+
const dir = planDir(coordRoot, record.intent.id, planId);
|
|
230
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
231
|
+
chmodSync(dir, 0o700);
|
|
232
|
+
writeExclusiveJson(join(dir, "request.json"), request, "supervisor plan request");
|
|
233
|
+
writeExclusive(join(dir, "planner.mjs"), script, "supervisor planner script");
|
|
234
|
+
return request;
|
|
235
|
+
}
|
|
236
|
+
function plannerScript(coordRoot, record, planId, trigger) {
|
|
237
|
+
const policy = record.intent.replanning;
|
|
238
|
+
if (!policy)
|
|
239
|
+
throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
|
|
240
|
+
const prompt = [
|
|
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.",
|
|
246
|
+
"Dependencies may name an active work ID or an earlier key in your proposed work array.",
|
|
247
|
+
"Every proposed item must be reachable from root. The root must be a proposed key using a root-capable template.",
|
|
248
|
+
`Plan id: ${planId}`,
|
|
249
|
+
`Planning trigger: ${trigger}`,
|
|
250
|
+
`Goal: ${record.intent.title}`,
|
|
251
|
+
`Mission: ${JSON.stringify(record.intent.mission ?? null)}`,
|
|
252
|
+
`Original root: ${record.intent.root_work_id}`,
|
|
253
|
+
`Active root: ${record.projection.root_work_id}`,
|
|
254
|
+
`Remaining work attempts: ${record.projection.attempts_remaining}`,
|
|
255
|
+
`Remaining replans: ${record.projection.replans_remaining}`,
|
|
256
|
+
`Completed milestones: ${record.projection.milestones_completed}`,
|
|
257
|
+
`Remaining milestones: ${record.projection.milestones_remaining}`,
|
|
258
|
+
`Latest planner outcome: ${JSON.stringify(record.plans.at(-1)
|
|
259
|
+
? {
|
|
260
|
+
status: record.plans.at(-1)?.status,
|
|
261
|
+
reason: record.plans.at(-1)?.reason,
|
|
262
|
+
}
|
|
263
|
+
: null)}`,
|
|
264
|
+
`Templates: ${JSON.stringify(policy.templates)}`,
|
|
265
|
+
`Active work: ${JSON.stringify(record.work.map((work) => ({
|
|
266
|
+
id: work.intent.id,
|
|
267
|
+
title: work.intent.title,
|
|
268
|
+
objective: work.intent.objective,
|
|
269
|
+
acceptance: work.intent.acceptance,
|
|
270
|
+
dependencies: work.intent.dependencies,
|
|
271
|
+
state: work.projection.state,
|
|
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,
|
|
289
|
+
})))}`,
|
|
290
|
+
].join("\n\n");
|
|
291
|
+
const schema = {
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: {
|
|
294
|
+
decision: {
|
|
295
|
+
type: "string",
|
|
296
|
+
enum: record.intent.mission ? ["apply", "complete", "attention"] : ["apply", "attention"],
|
|
297
|
+
},
|
|
298
|
+
rationale: { type: "string" },
|
|
299
|
+
root: { type: "string" },
|
|
300
|
+
work: {
|
|
301
|
+
type: "array",
|
|
302
|
+
items: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: {
|
|
305
|
+
key: { type: "string" },
|
|
306
|
+
title: { type: "string" },
|
|
307
|
+
objective: { type: "string" },
|
|
308
|
+
acceptance: { type: "array", items: { type: "string" } },
|
|
309
|
+
dependencies: { type: "array", items: { type: "string" } },
|
|
310
|
+
template: { type: "string" },
|
|
311
|
+
},
|
|
312
|
+
required: ["key", "title", "objective", "acceptance", "dependencies", "template"],
|
|
313
|
+
},
|
|
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
|
+
},
|
|
325
|
+
},
|
|
326
|
+
required: ["decision", "rationale", "root", "work"],
|
|
327
|
+
};
|
|
328
|
+
return [
|
|
329
|
+
`export const meta = ${JSON.stringify({ name: `replan-${planId}` })};`,
|
|
330
|
+
"export default async ({ agent, stage }) => {",
|
|
331
|
+
` stage(${JSON.stringify(trigger === "initial"
|
|
332
|
+
? "Plan initial mission milestone"
|
|
333
|
+
: trigger === "milestone"
|
|
334
|
+
? "Reassess completed milestone"
|
|
335
|
+
: "Replan blocked goal")});`,
|
|
336
|
+
` return await agent(${JSON.stringify(prompt)}, ${JSON.stringify({
|
|
337
|
+
specialist: policy.planner_specialist,
|
|
338
|
+
schema,
|
|
339
|
+
label: "Produce bounded replacement plan",
|
|
340
|
+
})});`,
|
|
341
|
+
"};",
|
|
342
|
+
"",
|
|
343
|
+
].join("\n");
|
|
344
|
+
}
|
|
345
|
+
function normalizeProposal(planId, raw, record, previouslyApplied, trigger) {
|
|
346
|
+
const policy = record.intent.replanning;
|
|
347
|
+
if (!policy)
|
|
348
|
+
throw new Error(`supervisor ${record.intent.id} does not allow replanning`);
|
|
349
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
350
|
+
throw new Error("planner result must be an object");
|
|
351
|
+
}
|
|
352
|
+
const value = raw;
|
|
353
|
+
const decision = enumValue(value.decision, ["apply", "complete", "attention"], "plan decision");
|
|
354
|
+
const rationale = bounded(value.rationale, "plan rationale", MAX_REASON);
|
|
355
|
+
const root = typeof value.root === "string" ? value.root.trim() : "";
|
|
356
|
+
if (!Array.isArray(value.work))
|
|
357
|
+
throw new Error("plan work must be an array");
|
|
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
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
schema_version: SUPERVISOR_PLAN_SCHEMA_VERSION,
|
|
367
|
+
plan_id: planId,
|
|
368
|
+
decision,
|
|
369
|
+
rationale,
|
|
370
|
+
root: "",
|
|
371
|
+
work: [],
|
|
372
|
+
proposed_at: new Date().toISOString(),
|
|
373
|
+
};
|
|
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
|
+
}
|
|
384
|
+
if (value.work.length < 1 || value.work.length > policy.max_work_items_per_plan) {
|
|
385
|
+
throw new Error(`plan work must contain 1 to ${policy.max_work_items_per_plan} items`);
|
|
386
|
+
}
|
|
387
|
+
if (previouslyApplied + value.work.length > policy.max_total_work_items) {
|
|
388
|
+
throw new Error(`plan would exceed ${policy.max_total_work_items} total planned work items`);
|
|
389
|
+
}
|
|
390
|
+
const active = new Set(record.projection.work_ids);
|
|
391
|
+
const seen = new Set();
|
|
392
|
+
const work = value.work.map((candidate, index) => {
|
|
393
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
394
|
+
throw new Error(`plan work[${index}] must be an object`);
|
|
395
|
+
}
|
|
396
|
+
const item = candidate;
|
|
397
|
+
const key = bounded(item.key, `plan work[${index}].key`, 32);
|
|
398
|
+
if (!PLAN_KEY.test(key) || seen.has(key))
|
|
399
|
+
throw new Error(`invalid or duplicate plan key ${key}`);
|
|
400
|
+
const template = bounded(item.template, `plan work[${index}].template`, 64);
|
|
401
|
+
if (!TEMPLATE_ID.test(template) || !policy.templates[template]) {
|
|
402
|
+
throw new Error(`plan work ${key} references unknown template ${template}`);
|
|
403
|
+
}
|
|
404
|
+
if (!Array.isArray(item.dependencies))
|
|
405
|
+
throw new Error(`plan work ${key} dependencies must be an array`);
|
|
406
|
+
const dependencies = [
|
|
407
|
+
...new Set(item.dependencies.map((dependency, dependencyIndex) => bounded(dependency, `plan work ${key} dependency[${dependencyIndex}]`, 100))),
|
|
408
|
+
];
|
|
409
|
+
if (dependencies.length > 50)
|
|
410
|
+
throw new Error(`plan work ${key} exceeds 50 dependencies`);
|
|
411
|
+
for (const dependency of dependencies) {
|
|
412
|
+
if (!active.has(dependency) && !seen.has(dependency)) {
|
|
413
|
+
throw new Error(`plan work ${key} dependency ${dependency} is not active or earlier in the plan`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!Array.isArray(item.acceptance))
|
|
417
|
+
throw new Error(`plan work ${key} acceptance must be an array`);
|
|
418
|
+
const acceptance = item.acceptance.map((criterion, criterionIndex) => bounded(criterion, `plan work ${key} acceptance[${criterionIndex}]`, 500));
|
|
419
|
+
if (acceptance.length > 50)
|
|
420
|
+
throw new Error(`plan work ${key} exceeds 50 acceptance criteria`);
|
|
421
|
+
seen.add(key);
|
|
422
|
+
return {
|
|
423
|
+
key,
|
|
424
|
+
title: bounded(item.title, `plan work ${key} title`, 200),
|
|
425
|
+
objective: bounded(item.objective, `plan work ${key} objective`, 4_000),
|
|
426
|
+
acceptance,
|
|
427
|
+
dependencies,
|
|
428
|
+
template,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
if (!seen.has(root))
|
|
432
|
+
throw new Error(`plan root ${root || "(empty)"} is not a proposed key`);
|
|
433
|
+
const rootSpec = work.find((item) => item.key === root);
|
|
434
|
+
if (!rootSpec || !policy.templates[rootSpec.template]?.root) {
|
|
435
|
+
throw new Error(`plan root ${root} must use a root-capable template`);
|
|
436
|
+
}
|
|
437
|
+
const reachable = new Set();
|
|
438
|
+
const byKey = new Map(work.map((item) => [item.key, item]));
|
|
439
|
+
const visit = (key) => {
|
|
440
|
+
if (reachable.has(key))
|
|
441
|
+
return;
|
|
442
|
+
reachable.add(key);
|
|
443
|
+
for (const dependency of byKey.get(key)?.dependencies ?? []) {
|
|
444
|
+
if (byKey.has(dependency))
|
|
445
|
+
visit(dependency);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
visit(root);
|
|
449
|
+
const unused = work.filter((item) => !reachable.has(item.key));
|
|
450
|
+
if (unused.length)
|
|
451
|
+
throw new Error(`plan contains work not reachable from root: ${unused.map((item) => item.key).join(", ")}`);
|
|
452
|
+
return {
|
|
453
|
+
schema_version: SUPERVISOR_PLAN_SCHEMA_VERSION,
|
|
454
|
+
plan_id: planId,
|
|
455
|
+
decision,
|
|
456
|
+
rationale,
|
|
457
|
+
root,
|
|
458
|
+
work,
|
|
459
|
+
...(milestone ? { milestone } : {}),
|
|
460
|
+
proposed_at: new Date().toISOString(),
|
|
461
|
+
};
|
|
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
|
+
}
|
|
492
|
+
function appendPlanEvent(coordRootRaw, goalId, planId, input) {
|
|
493
|
+
const coordRoot = resolve(coordRootRaw);
|
|
494
|
+
const path = join(planDir(coordRoot, goalId, planId), "events.jsonl");
|
|
495
|
+
const current = readSupervisorPlan(coordRoot, goalId, planId).events;
|
|
496
|
+
const event = {
|
|
497
|
+
schema_version: SUPERVISOR_PLAN_SCHEMA_VERSION,
|
|
498
|
+
plan_id: planId,
|
|
499
|
+
seq: current.length + 1,
|
|
500
|
+
ts: new Date().toISOString(),
|
|
501
|
+
...input,
|
|
502
|
+
actor: bounded(input.actor, "plan actor", 200),
|
|
503
|
+
reason: bounded(input.reason, "plan reason", MAX_REASON),
|
|
504
|
+
};
|
|
505
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
506
|
+
const existing = existsSync(path) ? statSync(path).size : 0;
|
|
507
|
+
if (existing + Buffer.byteLength(line) > MAX_EVENTS_BYTES) {
|
|
508
|
+
throw new Error("supervisor plan event log would exceed its byte limit");
|
|
509
|
+
}
|
|
510
|
+
appendFileSync(path, line, { encoding: "utf8", mode: 0o600 });
|
|
511
|
+
chmodSync(path, 0o600);
|
|
512
|
+
}
|
|
513
|
+
function outcome(plan) {
|
|
514
|
+
return {
|
|
515
|
+
plan_id: plan.request.id,
|
|
516
|
+
status: plan.status,
|
|
517
|
+
workflow_run_id: plan.request.workflow_run_id,
|
|
518
|
+
reason: plan.reason,
|
|
519
|
+
root_work_id: plan.root_work_id,
|
|
520
|
+
work_ids: plan.work_ids,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function writeExclusiveJson(path, value, label) {
|
|
524
|
+
writeExclusive(path, `${JSON.stringify(value, null, 2)}\n`, label);
|
|
525
|
+
}
|
|
526
|
+
function writeExclusive(path, body, label) {
|
|
527
|
+
if (Buffer.byteLength(body) > MAX_RECORD_BYTES) {
|
|
528
|
+
throw new Error(`${label} exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
529
|
+
}
|
|
530
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
531
|
+
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
532
|
+
try {
|
|
533
|
+
writeFileSync(temporary, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
534
|
+
try {
|
|
535
|
+
linkSync(temporary, path);
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
if (error.code !== "EEXIST")
|
|
539
|
+
throw error;
|
|
540
|
+
const existing = readFileSync(path, "utf8");
|
|
541
|
+
if (existing !== body)
|
|
542
|
+
throw new Error(`${label} already exists with different content`);
|
|
543
|
+
}
|
|
544
|
+
chmodSync(path, 0o600);
|
|
545
|
+
}
|
|
546
|
+
finally {
|
|
547
|
+
try {
|
|
548
|
+
unlinkSync(temporary);
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
// The temporary may already be absent after a failed exclusive write.
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function planDir(coordRoot, goalId, planId) {
|
|
556
|
+
return join(coordRoot, ".harnery", "supervisors", goalId, "plans", planId);
|
|
557
|
+
}
|
|
558
|
+
function planScriptPath(coordRoot, goalId, planId) {
|
|
559
|
+
return join(planDir(coordRoot, goalId, planId), "planner.mjs");
|
|
560
|
+
}
|
|
561
|
+
function planProposalPath(coordRoot, goalId, planId) {
|
|
562
|
+
return join(planDir(coordRoot, goalId, planId), "proposal.json");
|
|
563
|
+
}
|
|
564
|
+
function bounded(value, field, max) {
|
|
565
|
+
if (typeof value !== "string")
|
|
566
|
+
throw new Error(`${field} must be a string`);
|
|
567
|
+
const normalized = value.trim();
|
|
568
|
+
if (!normalized)
|
|
569
|
+
throw new Error(`${field} must not be empty`);
|
|
570
|
+
if (normalized.length > max)
|
|
571
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
572
|
+
return normalized;
|
|
573
|
+
}
|
|
574
|
+
function enumValue(value, values, field) {
|
|
575
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
576
|
+
throw new Error(`${field} must be one of ${values.join(", ")}`);
|
|
577
|
+
}
|
|
578
|
+
return value;
|
|
579
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.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";
|
|
1
3
|
export type { SupervisorServiceConfig, SupervisorServiceGoalRuntime, SupervisorServiceGoalState, SupervisorServiceProcessState, SupervisorServiceRuntime, SupervisorServiceStatus, SupervisorServiceStatusRecord, } from "./service.js";
|
|
2
4
|
export { readSupervisorServiceConfig, readSupervisorServiceRuntime, readSupervisorServiceStatus, supervisorServiceLogPath, } from "./service-read.js";
|
|
3
|
-
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";
|
|
4
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,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"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export { readSupervisorPlan, readSupervisorPlans } from "./plan-read.js";
|
|
2
|
+
export { SUPERVISOR_PLAN_SCHEMA_VERSION, } from "./plan-types.js";
|
|
1
3
|
export { readSupervisorServiceConfig, readSupervisorServiceRuntime, readSupervisorServiceStatus, supervisorServiceLogPath, } from "./service-read.js";
|
|
2
4
|
export { assertSupervisorId, collectSupervisorWork, listSupervisors, readSupervisor, SUPERVISOR_INTENT_SCHEMA_VERSION, } from "./state.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RunWorkItemInput } from "../work/index.js";
|
|
2
|
+
import type { SupervisorPlanOutcome } from "./plan-types.js";
|
|
2
3
|
import { type SupervisorProjection } from "./state.js";
|
|
3
4
|
export type SupervisorStopReason = "succeeded" | "awaiting_attention" | "blocked" | "budget_exhausted" | "no_progress" | "tick_complete";
|
|
4
5
|
export interface SupervisorDispatchOutcome {
|
|
@@ -19,7 +20,9 @@ export interface SupervisorRunReport {
|
|
|
19
20
|
cycles: number;
|
|
20
21
|
dispatches: number;
|
|
21
22
|
acceptances: number;
|
|
23
|
+
replans: number;
|
|
22
24
|
outcomes: SupervisorDispatchOutcome[];
|
|
25
|
+
plan_outcomes: SupervisorPlanOutcome[];
|
|
23
26
|
projection: SupervisorProjection;
|
|
24
27
|
}
|
|
25
28
|
export interface RunSupervisorInput {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/runner.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,oBAAoB,GACpB,SAAS,GACT,kBAAkB,GAClB,aAAa,GACb,eAAe,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACnC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,WAAW,EAAE,oBAAoB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,UAAU,EAAE,oBAAoB,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC;IACtF,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAQD,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/runner.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,oBAAoB,GACpB,SAAS,GACT,kBAAkB,GAClB,aAAa,GACb,eAAe,CAAC;AAEpB,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACnC,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,WAAW,EAAE,oBAAoB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,aAAa,EAAE,qBAAqB,EAAE,CAAC;IACvC,UAAU,EAAE,oBAAoB,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC;IACtF,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAQD,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA4L3F"}
|