harnery 0.14.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.
- package/README.md +1 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/supervisor.d.ts +4 -0
- package/dist/commands/supervisor.d.ts.map +1 -0
- package/dist/commands/supervisor.js +238 -0
- package/dist/core/supervisor/index.d.ts +3 -0
- package/dist/core/supervisor/index.d.ts.map +1 -0
- package/dist/core/supervisor/index.js +2 -0
- package/dist/core/supervisor/read.d.ts +2 -0
- package/dist/core/supervisor/read.d.ts.map +1 -0
- package/dist/core/supervisor/read.js +1 -0
- package/dist/core/supervisor/runner.d.ts +34 -0
- package/dist/core/supervisor/runner.d.ts.map +1 -0
- package/dist/core/supervisor/runner.js +198 -0
- package/dist/core/supervisor/state.d.ts +69 -0
- package/dist/core/supervisor/state.d.ts.map +1 -0
- package/dist/core/supervisor/state.js +413 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +21 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +1 -0
- package/dist/core/workflow/run-state.d.ts +2 -0
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +12 -0
- package/dist/core/workflow/specialists.d.ts +7 -0
- package/dist/core/workflow/specialists.d.ts.map +1 -0
- package/dist/core/workflow/specialists.js +78 -0
- package/dist/core/workflow/types.d.ts +17 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/package.json +11 -1
- package/src/commander.ts +2 -0
- package/src/commands/supervisor.ts +315 -0
- package/src/core/supervisor/index.ts +24 -0
- package/src/core/supervisor/read.ts +14 -0
- package/src/core/supervisor/runner.ts +278 -0
- package/src/core/supervisor/state.ts +552 -0
- package/src/core/workflow/engine.ts +21 -8
- package/src/core/workflow/index.ts +1 -0
- package/src/core/workflow/proof.ts +1 -0
- package/src/core/workflow/run-state.ts +17 -0
- package/src/core/workflow/specialists.ts +97 -0
- package/src/core/workflow/types.ts +18 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { hostname } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { assertWorkId, readWorkItem } from "../work/read.js";
|
|
6
|
+
import { normalizeWorkflowSpecialists } from "../workflow/specialists.js";
|
|
7
|
+
export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1;
|
|
8
|
+
const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
9
|
+
const MAX_TITLE = 200;
|
|
10
|
+
const MAX_INTENT_BYTES = 256 * 1024;
|
|
11
|
+
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
12
|
+
export function createSupervisor(input) {
|
|
13
|
+
const coordRoot = resolve(input.coordRoot);
|
|
14
|
+
assertWorkId(input.rootWorkId);
|
|
15
|
+
const root = readWorkItem(coordRoot, input.rootWorkId);
|
|
16
|
+
const id = input.id ?? newSupervisorId();
|
|
17
|
+
assertSupervisorId(id);
|
|
18
|
+
const intent = {
|
|
19
|
+
schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
|
|
20
|
+
id,
|
|
21
|
+
title: bounded(input.title ?? root.intent.title, "supervisor title", MAX_TITLE),
|
|
22
|
+
root_work_id: input.rootWorkId,
|
|
23
|
+
specialists: normalizeWorkflowSpecialists(input.specialists),
|
|
24
|
+
limits: normalizeLimits(input.limits),
|
|
25
|
+
automation: normalizeAutomation(input.automation),
|
|
26
|
+
created_at: new Date().toISOString(),
|
|
27
|
+
};
|
|
28
|
+
const path = supervisorIntentPath(coordRoot, id);
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
30
|
+
chmodSync(dirname(path), 0o700);
|
|
31
|
+
writePrivateJson(path, intent);
|
|
32
|
+
return readSupervisor(coordRoot, id);
|
|
33
|
+
}
|
|
34
|
+
export function readSupervisor(coordRoot, goalId) {
|
|
35
|
+
return readSupervisorInternal(coordRoot, goalId, false);
|
|
36
|
+
}
|
|
37
|
+
/** @internal Runner seam; ignores the lease held by this supervisor process. */
|
|
38
|
+
export function readSupervisorIgnoringLease(coordRoot, goalId) {
|
|
39
|
+
return readSupervisorInternal(coordRoot, goalId, true);
|
|
40
|
+
}
|
|
41
|
+
export function listSupervisors(coordRoot) {
|
|
42
|
+
const base = join(resolve(coordRoot), ".harnery", "supervisors");
|
|
43
|
+
if (!existsSync(base))
|
|
44
|
+
return [];
|
|
45
|
+
const records = [];
|
|
46
|
+
for (const name of readdirSync(base)) {
|
|
47
|
+
if (!GOAL_ID.test(name) || !existsSync(supervisorIntentPath(coordRoot, name)))
|
|
48
|
+
continue;
|
|
49
|
+
records.push(readSupervisor(coordRoot, name));
|
|
50
|
+
}
|
|
51
|
+
return records.sort((left, right) => right.intent.created_at.localeCompare(left.intent.created_at));
|
|
52
|
+
}
|
|
53
|
+
export function collectSupervisorWork(coordRoot, rootWorkId) {
|
|
54
|
+
const records = new Map();
|
|
55
|
+
const visiting = new Set();
|
|
56
|
+
const visit = (workId) => {
|
|
57
|
+
if (records.has(workId))
|
|
58
|
+
return;
|
|
59
|
+
if (visiting.has(workId))
|
|
60
|
+
throw new Error(`supervisor work graph contains a cycle at ${workId}`);
|
|
61
|
+
visiting.add(workId);
|
|
62
|
+
const record = readWorkItem(coordRoot, workId);
|
|
63
|
+
for (const dependency of record.intent.dependencies)
|
|
64
|
+
visit(dependency);
|
|
65
|
+
visiting.delete(workId);
|
|
66
|
+
records.set(workId, record);
|
|
67
|
+
};
|
|
68
|
+
visit(rootWorkId);
|
|
69
|
+
return Array.from(records.values());
|
|
70
|
+
}
|
|
71
|
+
export function assertSupervisorId(goalId) {
|
|
72
|
+
if (!GOAL_ID.test(goalId))
|
|
73
|
+
throw new Error(`invalid supervisor id ${JSON.stringify(goalId)}`);
|
|
74
|
+
}
|
|
75
|
+
export function newSupervisorId() {
|
|
76
|
+
return `goal-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
|
|
77
|
+
}
|
|
78
|
+
/** @internal Runner seam; one exclusive crash-recoverable supervisor lease. */
|
|
79
|
+
export function acquireSupervisorLease(coordRoot, goalId) {
|
|
80
|
+
readSupervisorIntent(coordRoot, goalId);
|
|
81
|
+
const path = join(supervisorDir(coordRoot, goalId), "lease.json");
|
|
82
|
+
const owner = {
|
|
83
|
+
pid: process.pid,
|
|
84
|
+
host: hostname(),
|
|
85
|
+
created_at: new Date().toISOString(),
|
|
86
|
+
nonce: randomUUID(),
|
|
87
|
+
};
|
|
88
|
+
const acquire = () => {
|
|
89
|
+
try {
|
|
90
|
+
const fd = openSync(path, "wx", 0o600);
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(fd, `${JSON.stringify(owner)}\n`, "utf8");
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
closeSync(fd);
|
|
96
|
+
}
|
|
97
|
+
chmodSync(path, 0o600);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code !== "EEXIST")
|
|
102
|
+
throw error;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
if (!acquire()) {
|
|
107
|
+
const existing = readLease(path);
|
|
108
|
+
if (existing && leaseIsLive(existing)) {
|
|
109
|
+
throw new Error(`supervisor ${goalId} is already running under pid ${existing.pid} on ${existing.host}`);
|
|
110
|
+
}
|
|
111
|
+
unlinkSync(path);
|
|
112
|
+
if (!acquire())
|
|
113
|
+
throw new Error(`supervisor ${goalId} lease raced with another process`);
|
|
114
|
+
}
|
|
115
|
+
return () => {
|
|
116
|
+
try {
|
|
117
|
+
const existing = readLease(path);
|
|
118
|
+
if (existing?.nonce === owner.nonce)
|
|
119
|
+
unlinkSync(path);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// A stale private lease is recoverable by the next explicit run.
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function readSupervisorInternal(coordRootRaw, goalId, ignoreLease) {
|
|
127
|
+
const coordRoot = resolve(coordRootRaw);
|
|
128
|
+
const intent = readSupervisorIntent(coordRoot, goalId);
|
|
129
|
+
const work = collectSupervisorWork(coordRoot, intent.root_work_id);
|
|
130
|
+
return {
|
|
131
|
+
intent,
|
|
132
|
+
work,
|
|
133
|
+
projection: deriveProjection(coordRoot, intent, work, ignoreLease),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function deriveProjection(coordRoot, intent, work, ignoreLease) {
|
|
137
|
+
const root = work.find((record) => record.intent.id === intent.root_work_id);
|
|
138
|
+
if (!root)
|
|
139
|
+
throw new Error(`supervisor ${intent.id} root work is missing`);
|
|
140
|
+
const attemptsUsed = work.reduce((sum, record) => sum + record.projection.attempts_used, 0);
|
|
141
|
+
const readyWork = work
|
|
142
|
+
.filter((record) => record.projection.state === "ready")
|
|
143
|
+
.map((record) => record.intent.id);
|
|
144
|
+
const resumableWork = work
|
|
145
|
+
.filter((record) => record.projection.state === "awaiting_approval" &&
|
|
146
|
+
record.projection.next_action === "resume")
|
|
147
|
+
.map((record) => record.intent.id);
|
|
148
|
+
const retryableWork = work
|
|
149
|
+
.filter((record) => record.projection.state === "blocked" && record.projection.next_action === "retry")
|
|
150
|
+
.map((record) => record.intent.id);
|
|
151
|
+
const pendingApproval = work
|
|
152
|
+
.filter((record) => record.projection.state === "awaiting_approval" &&
|
|
153
|
+
record.projection.next_action === "resolve_approval")
|
|
154
|
+
.map((record) => record.intent.id);
|
|
155
|
+
const reviews = work
|
|
156
|
+
.filter((record) => record.projection.state === "in_review")
|
|
157
|
+
.map((record) => record.intent.id);
|
|
158
|
+
const cancelled = work
|
|
159
|
+
.filter((record) => record.projection.state === "cancelled")
|
|
160
|
+
.map((record) => record.intent.id);
|
|
161
|
+
const terminalBlocked = work
|
|
162
|
+
.filter((record) => record.projection.state === "blocked" && record.projection.next_action === "none")
|
|
163
|
+
.map((record) => record.intent.id);
|
|
164
|
+
const attentionWork = unique([
|
|
165
|
+
...pendingApproval,
|
|
166
|
+
...(intent.automation.accept_passing_proof ? [] : reviews),
|
|
167
|
+
...(intent.automation.resume_approved ? [] : resumableWork),
|
|
168
|
+
...(intent.automation.retry_blocked ? [] : retryableWork),
|
|
169
|
+
...cancelled,
|
|
170
|
+
...terminalBlocked,
|
|
171
|
+
]);
|
|
172
|
+
const base = {
|
|
173
|
+
id: intent.id,
|
|
174
|
+
title: intent.title,
|
|
175
|
+
root_work_id: intent.root_work_id,
|
|
176
|
+
work_ids: work.map((record) => record.intent.id),
|
|
177
|
+
ready_work: readyWork,
|
|
178
|
+
resumable_work: resumableWork,
|
|
179
|
+
retryable_work: retryableWork,
|
|
180
|
+
attention_work: attentionWork,
|
|
181
|
+
attempts_used: attemptsUsed,
|
|
182
|
+
attempts_remaining: Math.max(0, intent.limits.max_total_attempts - attemptsUsed),
|
|
183
|
+
specialists: Object.keys(intent.specialists),
|
|
184
|
+
};
|
|
185
|
+
if (!ignoreLease && supervisorLeaseIsLive(coordRoot, intent.id)) {
|
|
186
|
+
return {
|
|
187
|
+
...base,
|
|
188
|
+
state: "running",
|
|
189
|
+
reason: "the foreground supervisor holds the goal lease",
|
|
190
|
+
next_action: "wait_for_run",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (root.projection.state === "succeeded") {
|
|
194
|
+
return {
|
|
195
|
+
...base,
|
|
196
|
+
state: "succeeded",
|
|
197
|
+
reason: "root work was explicitly accepted",
|
|
198
|
+
next_action: "none",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
|
|
202
|
+
const attemptDispatchable = [
|
|
203
|
+
...readyWork,
|
|
204
|
+
...(intent.automation.retry_blocked ? retryableWork : []),
|
|
205
|
+
];
|
|
206
|
+
const dispatchable = [...resumableDispatchable, ...attemptDispatchable];
|
|
207
|
+
if (intent.automation.accept_passing_proof && reviews.length > 0) {
|
|
208
|
+
return {
|
|
209
|
+
...base,
|
|
210
|
+
state: "ready",
|
|
211
|
+
reason: `${reviews.length} passing work item${reviews.length === 1 ? "" : "s"} may be accepted by frozen policy`,
|
|
212
|
+
next_action: "run",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (resumableDispatchable.length > 0) {
|
|
216
|
+
return {
|
|
217
|
+
...base,
|
|
218
|
+
state: "ready",
|
|
219
|
+
reason: `${resumableDispatchable.length} parked run${resumableDispatchable.length === 1 ? " is" : "s are"} resumable`,
|
|
220
|
+
next_action: "run",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (attemptsUsed >= intent.limits.max_total_attempts && attemptDispatchable.length > 0) {
|
|
224
|
+
return {
|
|
225
|
+
...base,
|
|
226
|
+
state: "budget_exhausted",
|
|
227
|
+
reason: `goal exhausted its ${intent.limits.max_total_attempts} total attempts`,
|
|
228
|
+
next_action: "none",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (dispatchable.length > 0) {
|
|
232
|
+
return {
|
|
233
|
+
...base,
|
|
234
|
+
state: "ready",
|
|
235
|
+
reason: `${dispatchable.length} work item${dispatchable.length === 1 ? " is" : "s are"} dispatchable`,
|
|
236
|
+
next_action: "run",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (pendingApproval.length > 0) {
|
|
240
|
+
return {
|
|
241
|
+
...base,
|
|
242
|
+
state: "awaiting_attention",
|
|
243
|
+
reason: `${pendingApproval.length} work item${pendingApproval.length === 1 ? " needs" : "s need"} approval`,
|
|
244
|
+
next_action: "resolve_approval",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (reviews.length > 0) {
|
|
248
|
+
return {
|
|
249
|
+
...base,
|
|
250
|
+
state: "awaiting_attention",
|
|
251
|
+
reason: `${reviews.length} work item${reviews.length === 1 ? " awaits" : "s await"} explicit review`,
|
|
252
|
+
next_action: "review",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (resumableWork.length > 0) {
|
|
256
|
+
return {
|
|
257
|
+
...base,
|
|
258
|
+
state: "awaiting_attention",
|
|
259
|
+
reason: `${resumableWork.length} resolved approval${resumableWork.length === 1 ? " requires" : "s require"} explicit resume`,
|
|
260
|
+
next_action: "run",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (retryableWork.length > 0) {
|
|
264
|
+
return {
|
|
265
|
+
...base,
|
|
266
|
+
state: "blocked",
|
|
267
|
+
reason: `${retryableWork.length} blocked work item${retryableWork.length === 1 ? " requires" : "s require"} explicit retry`,
|
|
268
|
+
next_action: "retry",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
...base,
|
|
273
|
+
state: "blocked",
|
|
274
|
+
reason: attentionWork.length > 0
|
|
275
|
+
? `${attentionWork.length} work item${attentionWork.length === 1 ? " needs" : "s need"} intervention`
|
|
276
|
+
: "goal graph has no legal progress action",
|
|
277
|
+
next_action: "none",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function readSupervisorIntent(coordRoot, goalId) {
|
|
281
|
+
assertSupervisorId(goalId);
|
|
282
|
+
const path = supervisorIntentPath(coordRoot, goalId);
|
|
283
|
+
if (!existsSync(path))
|
|
284
|
+
throw new Error(`supervisor ${goalId} does not exist`);
|
|
285
|
+
const size = statSync(path).size;
|
|
286
|
+
if (size <= 0 || size > MAX_INTENT_BYTES) {
|
|
287
|
+
throw new Error(`supervisor intent has invalid size ${size}`);
|
|
288
|
+
}
|
|
289
|
+
let intent;
|
|
290
|
+
try {
|
|
291
|
+
intent = JSON.parse(readFileSync(path, "utf8"));
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
throw new Error(`cannot parse supervisor intent ${goalId}: ${error.message}`);
|
|
295
|
+
}
|
|
296
|
+
validateIntent(intent, goalId);
|
|
297
|
+
return intent;
|
|
298
|
+
}
|
|
299
|
+
function validateIntent(intent, goalId) {
|
|
300
|
+
if (intent.schema_version !== SUPERVISOR_INTENT_SCHEMA_VERSION ||
|
|
301
|
+
intent.id !== goalId ||
|
|
302
|
+
!validTimestamp(intent.created_at)) {
|
|
303
|
+
throw new Error(`supervisor intent ${goalId} has an unsupported or mismatched schema`);
|
|
304
|
+
}
|
|
305
|
+
bounded(intent.title, "supervisor title", MAX_TITLE);
|
|
306
|
+
assertWorkId(intent.root_work_id);
|
|
307
|
+
const specialists = normalizeWorkflowSpecialists(intent.specialists);
|
|
308
|
+
if (JSON.stringify(specialists) !== JSON.stringify(intent.specialists)) {
|
|
309
|
+
throw new Error(`supervisor intent ${goalId} specialists are not canonical`);
|
|
310
|
+
}
|
|
311
|
+
if (JSON.stringify(normalizeLimits(intent.limits)) !== JSON.stringify(intent.limits)) {
|
|
312
|
+
throw new Error(`supervisor intent ${goalId} limits are not canonical`);
|
|
313
|
+
}
|
|
314
|
+
if (JSON.stringify(normalizeAutomation(intent.automation)) !== JSON.stringify(intent.automation)) {
|
|
315
|
+
throw new Error(`supervisor intent ${goalId} automation policy is not canonical`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function normalizeLimits(input) {
|
|
319
|
+
return {
|
|
320
|
+
max_cycles: positive(input?.max_cycles ?? 50, "supervisor max_cycles", 1_000),
|
|
321
|
+
max_runtime_ms: positive(input?.max_runtime_ms ?? 4 * 60 * 60 * 1_000, "supervisor max_runtime_ms", 7 * 24 * 60 * 60 * 1_000),
|
|
322
|
+
max_parallel_work: positive(input?.max_parallel_work ?? 1, "supervisor max_parallel_work", 20),
|
|
323
|
+
max_total_attempts: positive(input?.max_total_attempts ?? 100, "supervisor max_total_attempts", 10_000),
|
|
324
|
+
max_agents_per_work: positive(input?.max_agents_per_work ?? 20, "supervisor max_agents_per_work", 1_000),
|
|
325
|
+
agent_concurrency: positive(input?.agent_concurrency ?? 4, "supervisor agent_concurrency", 100),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function normalizeAutomation(input) {
|
|
329
|
+
return {
|
|
330
|
+
accept_passing_proof: boolean(input?.accept_passing_proof ?? false, "accept_passing_proof"),
|
|
331
|
+
resume_approved: boolean(input?.resume_approved ?? true, "resume_approved"),
|
|
332
|
+
retry_blocked: boolean(input?.retry_blocked ?? false, "retry_blocked"),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function supervisorDir(coordRoot, goalId) {
|
|
336
|
+
assertSupervisorId(goalId);
|
|
337
|
+
return join(resolve(coordRoot), ".harnery", "supervisors", goalId);
|
|
338
|
+
}
|
|
339
|
+
function supervisorIntentPath(coordRoot, goalId) {
|
|
340
|
+
return join(supervisorDir(coordRoot, goalId), "intent.json");
|
|
341
|
+
}
|
|
342
|
+
function supervisorLeaseIsLive(coordRoot, goalId) {
|
|
343
|
+
const lease = readLease(join(supervisorDir(coordRoot, goalId), "lease.json"));
|
|
344
|
+
return lease ? leaseIsLive(lease) : false;
|
|
345
|
+
}
|
|
346
|
+
function readLease(path) {
|
|
347
|
+
try {
|
|
348
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
349
|
+
return typeof value.pid === "number" &&
|
|
350
|
+
Number.isSafeInteger(value.pid) &&
|
|
351
|
+
value.pid > 0 &&
|
|
352
|
+
typeof value.host === "string" &&
|
|
353
|
+
typeof value.created_at === "string"
|
|
354
|
+
? {
|
|
355
|
+
pid: value.pid,
|
|
356
|
+
host: value.host,
|
|
357
|
+
created_at: value.created_at,
|
|
358
|
+
nonce: typeof value.nonce === "string" ? value.nonce : undefined,
|
|
359
|
+
}
|
|
360
|
+
: null;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function leaseIsLive(lease) {
|
|
367
|
+
if (lease.host !== hostname()) {
|
|
368
|
+
const age = Date.now() - Date.parse(lease.created_at);
|
|
369
|
+
return Number.isFinite(age) && age < FOREIGN_LEASE_STALE_MS;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
process.kill(lease.pid, 0);
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return error.code === "EPERM";
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function writePrivateJson(path, value) {
|
|
380
|
+
const body = `${JSON.stringify(value, null, 2)}\n`;
|
|
381
|
+
if (Buffer.byteLength(body) > MAX_INTENT_BYTES) {
|
|
382
|
+
throw new Error(`supervisor intent exceeds ${MAX_INTENT_BYTES} bytes`);
|
|
383
|
+
}
|
|
384
|
+
writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
385
|
+
chmodSync(path, 0o600);
|
|
386
|
+
}
|
|
387
|
+
function bounded(value, field, max) {
|
|
388
|
+
if (typeof value !== "string")
|
|
389
|
+
throw new Error(`${field} must be a string`);
|
|
390
|
+
const normalized = value.trim();
|
|
391
|
+
if (!normalized)
|
|
392
|
+
throw new Error(`${field} must not be empty`);
|
|
393
|
+
if (normalized.length > max)
|
|
394
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
395
|
+
return normalized;
|
|
396
|
+
}
|
|
397
|
+
function positive(value, field, max) {
|
|
398
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > max) {
|
|
399
|
+
throw new Error(`${field} must be an integer from 1 to ${max}`);
|
|
400
|
+
}
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
function boolean(value, field) {
|
|
404
|
+
if (typeof value !== "boolean")
|
|
405
|
+
throw new Error(`supervisor ${field} must be boolean`);
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
function validTimestamp(value) {
|
|
409
|
+
return typeof value === "string" && value.length <= 40 && Number.isFinite(Date.parse(value));
|
|
410
|
+
}
|
|
411
|
+
function unique(values) {
|
|
412
|
+
return [...new Set(values)];
|
|
413
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqCH,OAAO,KAAK,EAEV,UAAU,EAEV,SAAS,EAOV,MAAM,YAAY,CAAC;AAYpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAOpF;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAyC1F"}
|
|
@@ -34,6 +34,7 @@ import { assertWorkflowRunId, createWorkflowApproval } from "./approvals.js";
|
|
|
34
34
|
import { probeBilling } from "./billing.js";
|
|
35
35
|
import { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, writeWorkflowProof, } from "./proof.js";
|
|
36
36
|
import { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
|
|
37
|
+
import { normalizeWorkflowSpecialists, resolveSpecialistAssignment } from "./specialists.js";
|
|
37
38
|
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
38
39
|
import { parseStageOutput, validateAgainstSchema } from "./validate.js";
|
|
39
40
|
const DEFAULT_MAX_AGENTS = 50;
|
|
@@ -130,6 +131,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
130
131
|
const cwd = frozen?.cwd ?? opts.cwd ?? opts.coordRoot;
|
|
131
132
|
const log = opts.onLog ?? ((line) => process.stderr.write(`${line}\n`));
|
|
132
133
|
const defaultHarness = frozen?.default_harness ?? opts.defaultHarness ?? "claude-code";
|
|
134
|
+
const specialists = frozen?.specialists ?? (resumeState ? {} : normalizeWorkflowSpecialists(opts.specialists));
|
|
133
135
|
const isolation = frozen?.isolation ?? opts.isolation ?? "shared";
|
|
134
136
|
const networkAccess = frozen?.network_access ?? opts.networkAccess ?? "unknown";
|
|
135
137
|
const policy = frozen?.policy ?? (opts.policy ? normalizePolicy(opts.policy, { baseDir: cwd }) : undefined);
|
|
@@ -163,6 +165,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
163
165
|
isolation,
|
|
164
166
|
network_access: networkAccess,
|
|
165
167
|
policy: policy ? policy : undefined,
|
|
168
|
+
specialists,
|
|
166
169
|
},
|
|
167
170
|
},
|
|
168
171
|
});
|
|
@@ -343,7 +346,10 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
343
346
|
}
|
|
344
347
|
return decision;
|
|
345
348
|
};
|
|
346
|
-
const agent = async (prompt,
|
|
349
|
+
const agent = async (prompt, requestedOpts = {}) => {
|
|
350
|
+
const assignment = resolveSpecialistAssignment(specialists, prompt, requestedOpts);
|
|
351
|
+
const agentOpts = assignment.opts;
|
|
352
|
+
const assignmentPrompt = assignment.prompt;
|
|
347
353
|
let reservedForDispatch = 0;
|
|
348
354
|
let spawnCountClaimed = false;
|
|
349
355
|
const harness = agentOpts.harness ?? defaultHarness;
|
|
@@ -359,6 +365,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
359
365
|
id,
|
|
360
366
|
label: proofLabel,
|
|
361
367
|
stage: currentStage || undefined,
|
|
368
|
+
specialist: agentOpts.specialist,
|
|
362
369
|
harness,
|
|
363
370
|
model: agentOpts.model,
|
|
364
371
|
status: "failed",
|
|
@@ -368,7 +375,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
368
375
|
agentProofs.set(id, agentProof);
|
|
369
376
|
// Call identity for resume: same stage + harness + model + effort + turns + schema
|
|
370
377
|
// + ORIGINAL prompt → same key. Retry-mutated prompts never enter the key.
|
|
371
|
-
const key = agentCallKey(currentStage, harness, agentOpts,
|
|
378
|
+
const key = agentCallKey(currentStage, harness, agentOpts, assignmentPrompt);
|
|
372
379
|
const cached = resumeCache.get(key);
|
|
373
380
|
if (cached) {
|
|
374
381
|
// Exact-run replay skips dispatch authorization because no dispatch
|
|
@@ -384,6 +391,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
384
391
|
label,
|
|
385
392
|
key,
|
|
386
393
|
harness,
|
|
394
|
+
specialist: agentOpts.specialist ?? null,
|
|
387
395
|
model: agentOpts.model ?? null,
|
|
388
396
|
kind: cached.kind,
|
|
389
397
|
});
|
|
@@ -401,7 +409,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
401
409
|
max_attempts: maxAttempts,
|
|
402
410
|
max_turns: agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
403
411
|
timeout_ms: agentOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
404
|
-
prompt_bytes: Buffer.byteLength(
|
|
412
|
+
prompt_bytes: Buffer.byteLength(assignmentPrompt),
|
|
405
413
|
isolation,
|
|
406
414
|
network_access: networkAccess,
|
|
407
415
|
current_cost_usd: round4(costUsd + reservedCostUsd),
|
|
@@ -516,11 +524,12 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
516
524
|
label,
|
|
517
525
|
key,
|
|
518
526
|
harness,
|
|
527
|
+
specialist: agentOpts.specialist ?? null,
|
|
519
528
|
model: agentOpts.model ?? null,
|
|
520
529
|
effort: agentOpts.effort ?? null,
|
|
521
530
|
});
|
|
522
531
|
log(`[${name}] ${currentStage || "(no stage)"} → ${id} [${harness}] ${label}`);
|
|
523
|
-
let attemptPrompt =
|
|
532
|
+
let attemptPrompt = assignmentPrompt;
|
|
524
533
|
let last = null;
|
|
525
534
|
let agentCostUsd = 0;
|
|
526
535
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -588,7 +597,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
588
597
|
// Feed the validation failure back verbatim — the retry prompt carries
|
|
589
598
|
// exactly what was wrong, which is what makes bounded retry converge.
|
|
590
599
|
attemptPrompt =
|
|
591
|
-
`${
|
|
600
|
+
`${assignmentPrompt}\n\nYour previous reply failed validation:\n` +
|
|
592
601
|
`${problems.map((p) => ` - ${p}`).join("\n")}\n` +
|
|
593
602
|
`Reply with ONLY the corrected JSON object. No prose, no code fences.`;
|
|
594
603
|
}
|
|
@@ -670,6 +679,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
670
679
|
acceptance: meta.acceptance,
|
|
671
680
|
max_agents: maxAgents,
|
|
672
681
|
concurrency,
|
|
682
|
+
specialists: Object.keys(specialists),
|
|
673
683
|
policy: policy ? { name: policy.name, sha256: policyDigest(policy) } : null,
|
|
674
684
|
isolation,
|
|
675
685
|
network_access: networkAccess,
|
|
@@ -786,15 +796,18 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState) {
|
|
|
786
796
|
/** Stable identity for one agent() call, for the resume cache. The ORIGINAL
|
|
787
797
|
* prompt (never a retry-mutated one) plus everything that changes behavior. */
|
|
788
798
|
function agentCallKey(stage, harness, agentOpts, prompt) {
|
|
789
|
-
const
|
|
799
|
+
const parts = [
|
|
790
800
|
stage,
|
|
791
801
|
harness,
|
|
792
802
|
agentOpts.model ?? null,
|
|
793
803
|
agentOpts.effort ?? null,
|
|
794
804
|
agentOpts.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
795
805
|
agentOpts.schema ?? null,
|
|
796
|
-
|
|
797
|
-
|
|
806
|
+
];
|
|
807
|
+
if (agentOpts.specialist)
|
|
808
|
+
parts.push(agentOpts.specialist);
|
|
809
|
+
parts.push(prompt);
|
|
810
|
+
const basis = JSON.stringify(parts);
|
|
798
811
|
return createHash("sha256").update(basis).digest("hex").slice(0, 16);
|
|
799
812
|
}
|
|
800
813
|
/** Per-child fixed context overhead: the repo-instructions file at the child
|
|
@@ -4,6 +4,6 @@ export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js"
|
|
|
4
4
|
export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
|
|
5
5
|
export type { WorkflowRunManifest } from "./run-state.js";
|
|
6
6
|
export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
|
|
7
|
-
export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, } from "./types.js";
|
|
7
|
+
export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, } from "./types.js";
|
|
8
8
|
export { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,oCAAoC,EACpC,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,oCAAoC,EACpC,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EAEZ,aAAa,EAId,MAAM,YAAY,CAAC;AAepB,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC,CAAC;IAClF,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;QACrC,SAAS,EAAE,eAAe,CAAC;QAC3B,aAAa,EAAE,mBAAmB,CAAC;KACpC,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GACnB,sBAAsB,CAqCxB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,KAAK,EAAE,qBAAqB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,sBAAsB,CA6BzB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,EAAE,sBAAsB,EAAE,GACjC;IAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAuB9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAQjF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EAEZ,aAAa,EAId,MAAM,YAAY,CAAC;AAepB,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC,CAAC;IAClF,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;QACrC,SAAS,EAAE,eAAe,CAAC;QAC3B,aAAa,EAAE,mBAAmB,CAAC;KACpC,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GACnB,sBAAsB,CAqCxB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,KAAK,EAAE,qBAAqB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,sBAAsB,CA6BzB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,EAAE,sBAAsB,EAAE,GACjC;IAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAuB9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAQjF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,aAAa,CA2ChF;AAgCD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAe3E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAkBjF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAoChE"}
|
|
@@ -109,6 +109,7 @@ export function buildWorkflowProof(input) {
|
|
|
109
109
|
const agents = input.agents.map((agent) => ({
|
|
110
110
|
...agent,
|
|
111
111
|
label: clipped(agent.label, MAX_LABEL_CHARS),
|
|
112
|
+
specialist: clippedOptional(agent.specialist, MAX_LABEL_CHARS),
|
|
112
113
|
model: clippedOptional(agent.model, MAX_LABEL_CHARS),
|
|
113
114
|
session_id: clippedOptional(agent.session_id, MAX_REF_CHARS),
|
|
114
115
|
error: clippedOptional(agent.error, MAX_SUMMARY_CHARS),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RepoSnapshot } from "../context/index.js";
|
|
2
2
|
import { type NormalizedPolicy, type PolicyIsolation, type PolicyNetworkAccess } from "../policy/index.js";
|
|
3
|
+
import type { WorkflowSpecialistProfile } from "./types.js";
|
|
3
4
|
export declare const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION: 1;
|
|
4
5
|
export interface WorkflowRunManifest {
|
|
5
6
|
schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
|
|
@@ -24,6 +25,7 @@ export interface WorkflowRunManifest {
|
|
|
24
25
|
isolation: PolicyIsolation;
|
|
25
26
|
network_access: PolicyNetworkAccess;
|
|
26
27
|
policy?: NormalizedPolicy;
|
|
28
|
+
specialists?: Record<string, WorkflowSpecialistProfile>;
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
export interface WriteWorkflowRunManifestInput {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/run-state.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAG5B,eAAO,MAAM,oCAAoC,EAAG,CAAU,CAAC;AAK/D,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,oCAAoC,CAAC;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iBAAiB,EAAE,YAAY,CAAC;IAChC,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;QAC/B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,SAAS,EAAE,eAAe,CAAC;QAC3B,cAAc,EAAE,mBAAmB,CAAC;QACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/run-state.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,eAAO,MAAM,oCAAoC,EAAG,CAAU,CAAC;AAK/D,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,oCAAoC,CAAC;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,iBAAiB,EAAE,YAAY,CAAC;IAChC,SAAS,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,iBAAiB,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;QAC/B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,SAAS,EAAE,eAAe,CAAC;QAC3B,cAAc,EAAE,mBAAmB,CAAC;QACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;KACzD,CAAC;CACH;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,GAAG,MAAM,CAmBrF;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAgC7F;AAED,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ;IAAE,QAAQ,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA4BvD;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CASjF;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,CA0CvF"}
|
|
@@ -4,6 +4,7 @@ import { hostname } from "node:os";
|
|
|
4
4
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { normalizePolicy, policyDigest, } from "../policy/index.js";
|
|
6
6
|
import { assertWorkflowRunId, readWorkflowApproval } from "./approvals.js";
|
|
7
|
+
import { normalizeWorkflowSpecialists } from "./specialists.js";
|
|
7
8
|
export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 1;
|
|
8
9
|
const MANIFEST_LIMIT_BYTES = 256 * 1024;
|
|
9
10
|
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
@@ -201,8 +202,19 @@ function validExecution(value) {
|
|
|
201
202
|
value.approval_addressee.length <= 200 &&
|
|
202
203
|
["shared", "worktree", "sandbox", "remote"].includes(value.isolation) &&
|
|
203
204
|
["enabled", "disabled", "unknown"].includes(value.network_access) &&
|
|
205
|
+
validSpecialists(value.specialists) &&
|
|
204
206
|
validFrozenPolicy(value.policy, value.cwd));
|
|
205
207
|
}
|
|
208
|
+
function validSpecialists(specialists) {
|
|
209
|
+
if (specialists === undefined)
|
|
210
|
+
return true;
|
|
211
|
+
try {
|
|
212
|
+
return (JSON.stringify(normalizeWorkflowSpecialists(specialists)) === JSON.stringify(specialists));
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
206
218
|
function validFrozenPolicy(policy, cwd) {
|
|
207
219
|
if (policy === undefined)
|
|
208
220
|
return true;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentOpts, WorkflowSpecialistProfile } from "./types.js";
|
|
2
|
+
export declare function normalizeWorkflowSpecialists(input: Readonly<Record<string, WorkflowSpecialistProfile>> | undefined): Record<string, WorkflowSpecialistProfile>;
|
|
3
|
+
export declare function resolveSpecialistAssignment(profiles: Readonly<Record<string, WorkflowSpecialistProfile>>, prompt: string, opts: AgentOpts): {
|
|
4
|
+
prompt: string;
|
|
5
|
+
opts: AgentOpts;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=specialists.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specialists.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/specialists.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAOvE,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,GAAG,SAAS,GACrE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAuC3C;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,EAC7D,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAsBrC"}
|