harnery 0.13.0 → 0.15.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.
Files changed (66) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +4 -0
  4. package/dist/commands/supervisor.d.ts +4 -0
  5. package/dist/commands/supervisor.d.ts.map +1 -0
  6. package/dist/commands/supervisor.js +238 -0
  7. package/dist/commands/work.d.ts +4 -0
  8. package/dist/commands/work.d.ts.map +1 -0
  9. package/dist/commands/work.js +281 -0
  10. package/dist/core/supervisor/index.d.ts +3 -0
  11. package/dist/core/supervisor/index.d.ts.map +1 -0
  12. package/dist/core/supervisor/index.js +2 -0
  13. package/dist/core/supervisor/read.d.ts +2 -0
  14. package/dist/core/supervisor/read.d.ts.map +1 -0
  15. package/dist/core/supervisor/read.js +1 -0
  16. package/dist/core/supervisor/runner.d.ts +34 -0
  17. package/dist/core/supervisor/runner.d.ts.map +1 -0
  18. package/dist/core/supervisor/runner.js +198 -0
  19. package/dist/core/supervisor/state.d.ts +69 -0
  20. package/dist/core/supervisor/state.d.ts.map +1 -0
  21. package/dist/core/supervisor/state.js +413 -0
  22. package/dist/core/work/index.d.ts +3 -0
  23. package/dist/core/work/index.d.ts.map +1 -0
  24. package/dist/core/work/index.js +2 -0
  25. package/dist/core/work/read.d.ts +2 -0
  26. package/dist/core/work/read.d.ts.map +1 -0
  27. package/dist/core/work/read.js +1 -0
  28. package/dist/core/work/runner.d.ts +10 -0
  29. package/dist/core/work/runner.d.ts.map +1 -0
  30. package/dist/core/work/runner.js +63 -0
  31. package/dist/core/work/state.d.ts +109 -0
  32. package/dist/core/work/state.d.ts.map +1 -0
  33. package/dist/core/work/state.js +698 -0
  34. package/dist/core/workflow/engine.d.ts.map +1 -1
  35. package/dist/core/workflow/engine.js +41 -9
  36. package/dist/core/workflow/index.d.ts +1 -1
  37. package/dist/core/workflow/index.d.ts.map +1 -1
  38. package/dist/core/workflow/proof.d.ts +1 -0
  39. package/dist/core/workflow/proof.d.ts.map +1 -1
  40. package/dist/core/workflow/proof.js +2 -0
  41. package/dist/core/workflow/run-state.d.ts +3 -0
  42. package/dist/core/workflow/run-state.d.ts.map +1 -1
  43. package/dist/core/workflow/run-state.js +14 -0
  44. package/dist/core/workflow/specialists.d.ts +7 -0
  45. package/dist/core/workflow/specialists.d.ts.map +1 -0
  46. package/dist/core/workflow/specialists.js +78 -0
  47. package/dist/core/workflow/types.d.ts +24 -0
  48. package/dist/core/workflow/types.d.ts.map +1 -1
  49. package/package.json +21 -1
  50. package/src/commander.ts +4 -0
  51. package/src/commands/supervisor.ts +315 -0
  52. package/src/commands/work.ts +367 -0
  53. package/src/core/supervisor/index.ts +24 -0
  54. package/src/core/supervisor/read.ts +14 -0
  55. package/src/core/supervisor/runner.ts +278 -0
  56. package/src/core/supervisor/state.ts +552 -0
  57. package/src/core/work/index.ts +24 -0
  58. package/src/core/work/read.ts +15 -0
  59. package/src/core/work/runner.ts +81 -0
  60. package/src/core/work/state.ts +900 -0
  61. package/src/core/workflow/engine.ts +42 -9
  62. package/src/core/workflow/index.ts +1 -0
  63. package/src/core/workflow/proof.ts +3 -0
  64. package/src/core/workflow/run-state.ts +20 -0
  65. package/src/core/workflow/specialists.ts +97 -0
  66. package/src/core/workflow/types.ts +25 -0
@@ -0,0 +1,14 @@
1
+ export {
2
+ assertSupervisorId,
3
+ collectSupervisorWork,
4
+ listSupervisors,
5
+ readSupervisor,
6
+ SUPERVISOR_INTENT_SCHEMA_VERSION,
7
+ type SupervisorAutomationPolicy,
8
+ type SupervisorIntent,
9
+ type SupervisorLimits,
10
+ type SupervisorNextAction,
11
+ type SupervisorProjection,
12
+ type SupervisorRecord,
13
+ type SupervisorState,
14
+ } from "./state.ts";
@@ -0,0 +1,278 @@
1
+ import { resolve } from "node:path";
2
+ import {
3
+ acceptWorkItem,
4
+ type RunWorkItemInput,
5
+ reconcileWorkItem,
6
+ runWorkItem,
7
+ type WorkRecord,
8
+ } from "../work/index.ts";
9
+ import {
10
+ acquireSupervisorLease,
11
+ readSupervisorIgnoringLease,
12
+ type SupervisorProjection,
13
+ type SupervisorRecord,
14
+ } from "./state.ts";
15
+
16
+ export type SupervisorStopReason =
17
+ | "succeeded"
18
+ | "awaiting_attention"
19
+ | "blocked"
20
+ | "budget_exhausted"
21
+ | "no_progress"
22
+ | "tick_complete";
23
+
24
+ export interface SupervisorDispatchOutcome {
25
+ work_id: string;
26
+ action: "run" | "resume" | "retry";
27
+ status: "completed" | "parked" | "failed";
28
+ run_id?: string;
29
+ error?: string;
30
+ }
31
+
32
+ export interface SupervisorRunReport {
33
+ goal_id: string;
34
+ mode: "tick" | "run";
35
+ stop_reason: SupervisorStopReason;
36
+ reason: string;
37
+ started_at: string;
38
+ ended_at: string;
39
+ duration_ms: number;
40
+ cycles: number;
41
+ dispatches: number;
42
+ acceptances: number;
43
+ outcomes: SupervisorDispatchOutcome[];
44
+ projection: SupervisorProjection;
45
+ }
46
+
47
+ export interface RunSupervisorInput {
48
+ coordRoot: string;
49
+ goalId: string;
50
+ mode?: "tick" | "run";
51
+ actor?: string;
52
+ engine: Omit<RunWorkItemInput["engine"], "maxAgents" | "concurrency" | "specialists">;
53
+ onLog?: (line: string) => void;
54
+ }
55
+
56
+ interface Candidate {
57
+ record: WorkRecord;
58
+ action: "run" | "resume" | "retry";
59
+ consumesAttempt: boolean;
60
+ }
61
+
62
+ export async function runSupervisor(input: RunSupervisorInput): Promise<SupervisorRunReport> {
63
+ const coordRoot = resolve(input.coordRoot);
64
+ const mode = input.mode ?? "run";
65
+ const actor = input.actor?.trim() || `supervisor:${input.goalId}`;
66
+ const log = input.onLog ?? (() => undefined);
67
+ const startedAt = new Date().toISOString();
68
+ const startedMs = Date.now();
69
+ const release = acquireSupervisorLease(coordRoot, input.goalId);
70
+ let cycles = 0;
71
+ let dispatches = 0;
72
+ let acceptances = 0;
73
+ const outcomes: SupervisorDispatchOutcome[] = [];
74
+ try {
75
+ const initial = readSupervisorIgnoringLease(coordRoot, input.goalId);
76
+ const cycleLimit = mode === "tick" ? 1 : initial.intent.limits.max_cycles;
77
+ const deadline = startedMs + initial.intent.limits.max_runtime_ms;
78
+ while (cycles < cycleLimit) {
79
+ cycles++;
80
+ let record = refreshGraph(coordRoot, input.goalId, actor);
81
+ log(
82
+ `[${record.intent.title}] cycle ${cycles}: ${record.projection.state}; ` +
83
+ `${record.projection.ready_work.length} ready, ${record.projection.attention_work.length} attention`,
84
+ );
85
+ if (record.projection.state === "succeeded") {
86
+ return report("succeeded", record.projection.reason, record);
87
+ }
88
+ if (Date.now() >= deadline) {
89
+ return report("budget_exhausted", "supervisor wall-time limit reached", record);
90
+ }
91
+
92
+ const before = graphFingerprint(record);
93
+ let changed = false;
94
+ if (record.intent.automation.accept_passing_proof) {
95
+ for (const work of record.work) {
96
+ if (work.projection.state !== "in_review") continue;
97
+ acceptWorkItem(coordRoot, work.intent.id, {
98
+ actor,
99
+ reason: "passing proof accepted by frozen supervisor policy",
100
+ });
101
+ acceptances++;
102
+ changed = true;
103
+ log(`[${record.intent.title}] accepted ${work.intent.id} from passing proof`);
104
+ }
105
+ if (changed) record = refreshGraph(coordRoot, input.goalId, actor);
106
+ if (record.projection.state === "succeeded") {
107
+ return report("succeeded", record.projection.reason, record);
108
+ }
109
+ }
110
+
111
+ const candidates = selectCandidates(record);
112
+ const selected = selectWithinAttemptBudget(record, candidates).slice(
113
+ 0,
114
+ record.intent.limits.max_parallel_work,
115
+ );
116
+ if (selected.length > 0 && Date.now() < deadline) {
117
+ const settled = await Promise.all(
118
+ selected.map(async (candidate) => {
119
+ dispatches++;
120
+ log(`[${record.intent.title}] ${candidate.action} ${candidate.record.intent.id}`);
121
+ try {
122
+ const result = await runWorkItem({
123
+ coordRoot,
124
+ workId: candidate.record.intent.id,
125
+ retry: candidate.action === "retry",
126
+ actor,
127
+ engine: {
128
+ ...input.engine,
129
+ specialists: record.intent.specialists,
130
+ maxAgents: record.intent.limits.max_agents_per_work,
131
+ concurrency: record.intent.limits.agent_concurrency,
132
+ },
133
+ });
134
+ return {
135
+ work_id: candidate.record.intent.id,
136
+ action: candidate.action,
137
+ status: "completed",
138
+ run_id: result.runId,
139
+ } satisfies SupervisorDispatchOutcome;
140
+ } catch (error) {
141
+ const latest = reconcileWorkItem(coordRoot, candidate.record.intent.id, actor);
142
+ return {
143
+ work_id: candidate.record.intent.id,
144
+ action: candidate.action,
145
+ status: latest.projection.state === "awaiting_approval" ? "parked" : "failed",
146
+ run_id: latest.projection.latest_run_id,
147
+ error: boundedError((error as Error).message),
148
+ } satisfies SupervisorDispatchOutcome;
149
+ }
150
+ }),
151
+ );
152
+ outcomes.push(...settled);
153
+ changed = true;
154
+ }
155
+
156
+ record = refreshGraph(coordRoot, input.goalId, actor);
157
+ const after = graphFingerprint(record);
158
+ if (record.projection.state === "succeeded") {
159
+ return report("succeeded", record.projection.reason, record);
160
+ }
161
+ if (mode === "tick") {
162
+ return report(
163
+ before === after ? "no_progress" : "tick_complete",
164
+ before === after
165
+ ? "the tick found no legal progress action"
166
+ : "one bounded supervisor cycle completed",
167
+ record,
168
+ );
169
+ }
170
+ if (Date.now() >= deadline) {
171
+ return report("budget_exhausted", "supervisor wall-time limit reached", record);
172
+ }
173
+ if (record.projection.state === "budget_exhausted") {
174
+ return report("budget_exhausted", record.projection.reason, record);
175
+ }
176
+ if (selected.length === 0 && !changed) {
177
+ return report(stopForProjection(record.projection), record.projection.reason, record);
178
+ }
179
+ if (selected.length === 0 && record.projection.state !== "ready") {
180
+ return report(stopForProjection(record.projection), record.projection.reason, record);
181
+ }
182
+ if (before === after) {
183
+ return report("no_progress", "a supervisor cycle produced no durable state change", record);
184
+ }
185
+ }
186
+ const final = refreshGraph(coordRoot, input.goalId, actor);
187
+ return report("budget_exhausted", `supervisor cycle limit reached (${cycleLimit})`, final);
188
+ } finally {
189
+ release();
190
+ }
191
+
192
+ function report(
193
+ stopReason: SupervisorStopReason,
194
+ reason: string,
195
+ record: SupervisorRecord,
196
+ ): SupervisorRunReport {
197
+ const endedAt = new Date().toISOString();
198
+ return {
199
+ goal_id: input.goalId,
200
+ mode,
201
+ stop_reason: stopReason,
202
+ reason,
203
+ started_at: startedAt,
204
+ ended_at: endedAt,
205
+ duration_ms: Date.now() - startedMs,
206
+ cycles,
207
+ dispatches,
208
+ acceptances,
209
+ outcomes,
210
+ projection: record.projection,
211
+ };
212
+ }
213
+ }
214
+
215
+ function refreshGraph(coordRoot: string, goalId: string, actor: string): SupervisorRecord {
216
+ const before = readSupervisorIgnoringLease(coordRoot, goalId);
217
+ for (const work of before.work) reconcileWorkItem(coordRoot, work.intent.id, actor);
218
+ return readSupervisorIgnoringLease(coordRoot, goalId);
219
+ }
220
+
221
+ function selectCandidates(record: SupervisorRecord): Candidate[] {
222
+ const candidates: Candidate[] = [];
223
+ for (const work of record.work) {
224
+ if (work.projection.state === "ready") {
225
+ candidates.push({ record: work, action: "run", consumesAttempt: true });
226
+ continue;
227
+ }
228
+ if (
229
+ work.projection.state === "awaiting_approval" &&
230
+ work.projection.next_action === "resume" &&
231
+ record.intent.automation.resume_approved
232
+ ) {
233
+ candidates.push({ record: work, action: "resume", consumesAttempt: false });
234
+ continue;
235
+ }
236
+ if (
237
+ work.projection.state === "blocked" &&
238
+ work.projection.next_action === "retry" &&
239
+ record.intent.automation.retry_blocked
240
+ ) {
241
+ candidates.push({ record: work, action: "retry", consumesAttempt: true });
242
+ }
243
+ }
244
+ return candidates;
245
+ }
246
+
247
+ function selectWithinAttemptBudget(record: SupervisorRecord, candidates: Candidate[]): Candidate[] {
248
+ let remaining = record.projection.attempts_remaining;
249
+ return candidates.filter((candidate) => {
250
+ if (!candidate.consumesAttempt) return true;
251
+ if (remaining <= 0) return false;
252
+ remaining--;
253
+ return true;
254
+ });
255
+ }
256
+
257
+ function graphFingerprint(record: SupervisorRecord): string {
258
+ return JSON.stringify(
259
+ record.work.map((work) => [
260
+ work.intent.id,
261
+ work.projection.state,
262
+ work.projection.next_action,
263
+ work.projection.attempts_used,
264
+ work.events.length,
265
+ ]),
266
+ );
267
+ }
268
+
269
+ function stopForProjection(projection: SupervisorProjection): SupervisorStopReason {
270
+ if (projection.state === "budget_exhausted") return "budget_exhausted";
271
+ if (projection.state === "blocked") return "blocked";
272
+ return "awaiting_attention";
273
+ }
274
+
275
+ function boundedError(message: string): string {
276
+ const normalized = message.replace(/\s+/g, " ").trim();
277
+ return normalized.length > 2_000 ? `${normalized.slice(0, 1_999)}…` : normalized;
278
+ }