@tangle-network/agent-runtime 0.116.0 → 0.117.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 +7 -1
- package/dist/{activation-DuqQhee6.js → activation-BZ2FyX0O.js} +3 -3
- package/dist/{activation-DuqQhee6.js.map → activation-BZ2FyX0O.js.map} +1 -1
- package/dist/agent.d.ts +3 -64
- package/dist/agent.js +5 -207
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-BoNIG2hA.js → analyst-loop-DvSciOfB.js} +2 -2
- package/dist/{analyst-loop-BoNIG2hA.js.map → analyst-loop-DvSciOfB.js.map} +1 -1
- package/dist/analyst-loop.js +1 -1
- package/dist/candidate-execution/index.d.ts +3 -3
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{candidate-execution-CfpJrd3o.js → candidate-execution-PX61zXd2.js} +4 -4
- package/dist/{candidate-execution-CfpJrd3o.js.map → candidate-execution-PX61zXd2.js.map} +1 -1
- package/dist/{environment-provider-CWsRh6Uz.d.ts → environment-provider-CTZjdIUg.d.ts} +298 -32
- package/dist/{environment-provider-CCaEhA-l.js → environment-provider-CzcEyRL7.js} +140 -31
- package/dist/environment-provider-CzcEyRL7.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{improvement-cycle-C1cmjvPD.js → improvement-cycle-Bcqz8rZI.js} +160 -19
- package/dist/improvement-cycle-Bcqz8rZI.js.map +1 -0
- package/dist/{index-CYkDeM5L.d.ts → index-BHSYaWrC.d.ts} +3 -3
- package/dist/{index-DcLMNnG5.d.ts → index-BTUG4Xln.d.ts} +6 -6
- package/dist/{index-COumPQka.d.ts → index-DPWJxSRa.d.ts} +847 -275
- package/dist/{index-C-FYUuFG.d.ts → index-X2YwyWkc.d.ts} +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +12 -12
- package/dist/intelligence.d.ts +8 -7
- package/dist/intelligence.js +15 -9
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +3 -3
- package/dist/kernel.js +8 -8
- package/dist/{knowledge-DOzbywZT.js → knowledge-CDlrayRc.js} +7 -7
- package/dist/knowledge-CDlrayRc.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-BuQjc5DR.js → loop-runner-bin-BxhhDtmG.js} +4 -4
- package/dist/{loop-runner-bin-BuQjc5DR.js.map → loop-runner-bin-BxhhDtmG.js.map} +1 -1
- package/dist/{loop-runner-bin-BFrhPLKt.d.ts → loop-runner-bin-oyL7it5O.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +43 -10
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +3 -43
- package/dist/mcp/index.js +5 -204
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-_Wyp4udO.js → openai-tools-CO9X36zy.js} +2 -2
- package/dist/{openai-tools-_Wyp4udO.js.map → openai-tools-CO9X36zy.js.map} +1 -1
- package/dist/{prepare-BHQBb02e.js → prepare-BbI6qGOW.js} +5 -2
- package/dist/prepare-BbI6qGOW.js.map +1 -0
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{protected-model-port-BP6Z4eau.d.ts → protected-model-port-B0yXMpLL.d.ts} +10 -2
- package/dist/{protected-model-port-DqAH1Z2M.js → protected-model-port-COnyZfML.js} +2 -2
- package/dist/{protected-model-port-DqAH1Z2M.js.map → protected-model-port-COnyZfML.js.map} +1 -1
- package/dist/{runtime-Ut1pkd2n.js → runtime-Ce39zfqn.js} +145 -53
- package/dist/runtime-Ce39zfqn.js.map +1 -0
- package/dist/{sandbox-events-DeI5xX8P.js → sandbox-events-Yhd1GYWl.js} +4 -2
- package/dist/sandbox-events-Yhd1GYWl.js.map +1 -0
- package/dist/spawn-journal-B_mu_oH2.js +857 -0
- package/dist/spawn-journal-B_mu_oH2.js.map +1 -0
- package/dist/{structural-rollout-CVY_0hJp.js → structural-rollout-DLwW69R4.js} +4 -4
- package/dist/structural-rollout-DLwW69R4.js.map +1 -0
- package/dist/{supervise-BUR9ByF7.js → supervise-U7_K5yuV.js} +2598 -821
- package/dist/supervise-U7_K5yuV.js.map +1 -0
- package/dist/{supervisor-BBbPBXpe.js → supervisor-CAXiCKnX.js} +2808 -973
- package/dist/supervisor-CAXiCKnX.js.map +1 -0
- package/dist/testing.js +98 -76
- package/dist/testing.js.map +1 -1
- package/dist/{workspace-archive-DXzJq7WP.js → workspace-archive-CqPIijFA.js} +2 -2
- package/dist/{workspace-archive-DXzJq7WP.js.map → workspace-archive-CqPIijFA.js.map} +1 -1
- package/package.json +1 -1
- package/dist/environment-provider-CCaEhA-l.js.map +0 -1
- package/dist/improvement-cycle-C1cmjvPD.js.map +0 -1
- package/dist/knowledge-DOzbywZT.js.map +0 -1
- package/dist/prepare-BHQBb02e.js.map +0 -1
- package/dist/runtime-Ut1pkd2n.js.map +0 -1
- package/dist/sandbox-events-DeI5xX8P.js.map +0 -1
- package/dist/spawn-journal-DCPbicXB.js +0 -457
- package/dist/spawn-journal-DCPbicXB.js.map +0 -1
- package/dist/structural-rollout-CVY_0hJp.js.map +0 -1
- package/dist/supervise-BUR9ByF7.js.map +0 -1
- package/dist/supervisor-BBbPBXpe.js.map +0 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { c as RuntimeRunStateError, i as ConfigError, o as NotFoundError, t as AgentEvalError$1, u as ValidationError } from "./errors-DEAvWQPy.js";
|
|
2
|
+
import { S as detachedSnapshot, b as workerTraceAnalysisStore, d as writeAllBytes, i as InMemorySpawnJournal, l as parseCommittedJsonLines, n as FileSpawnJournal, r as InMemoryResultBlobStore, t as FileResultBlobStore, u as prepareJsonlAppend, x as contentAddress } from "./spawn-journal-B_mu_oH2.js";
|
|
2
3
|
import { a as mapWithConcurrency, c as sleep, d as throwIfAborted, f as withTimeout, l as stringifySafe, n as addTokenUsage, o as randomSuffix, p as zeroTokenUsage, r as deleteBoxSafe, s as randomUuid, t as abortError, u as throwAbort } from "./util-Cc9g9Y-o.js";
|
|
3
|
-
import { a as
|
|
4
|
-
import { $ as removeWorktree, B as createActivityLog, D as toOtelAttributes, H as routerBrain, Q as createWorktree, S as createOtelExporter, T as generateSpanId, W as routerChatWithUsage, X as runWorktreeHarness, Y as runWorktreeChecks, Z as captureWorktreeDiff, a as runFinalizer, b as buildLoopSpanNodes, i as pickBestDelivered, l as withDriverExecutor, m as workerTraceSeamKey, n as bestDelivered, o as runTree, p as workerTraceEnv, q as runBrainLoop, t as createSupervisor } from "./supervisor-BBbPBXpe.js";
|
|
4
|
+
import { C as buildLoopSpanNodes, Ct as controlProfileMaterialization, Dt as promptControlProfileMaterialization, E as createOtelExporter, Et as profileMaterializationAxes, Ot as promptModelProfileMaterialization, Pt as worktreeCliProfileMaterialization, Q as assertValidBudget, St as assertProfileMaterialization, Tt as fullProfileMaterialization, W as createActivityLog, X as DEFAULT_SUCCESSFUL_SHUTDOWN_MS, Z as teardownExecutor, _t as worktreeProfileExecutionPlan, a as pickBestDelivered, at as runtimeOwnedExecutorExecutionBinding, bt as removeWorktree, c as driverChild, ct as routerBrain, d as deriveNodeExecutionIdentity, et as spendFromUsageEvents, f as recordScopeOwnerMaterialization, g as workerTraceEnv, gt as runWorktreeHarness, ht as runWorktreeChecks, it as newExecutionAttemptId, j as toOtelAttributes, k as generateSpanId, l as withDriverExecutor, n as createSupervisor, nt as attestRuntimeOwnedScopeOwner, o as runFinalizer, ot as runtimeOwnedExecutorMaterialization, p as scopeOwnerExecutorNodeContext, pt as runBrainLoop, q as freeSlots, r as bestDelivered, rt as inheritRuntimeOwnedExecutorAttestation, s as runTree, st as runtimeOwnedScopeOwnerRuntime, tt as attestRuntimeOwnedExecutor, ut as routerChatWithUsage, vt as captureWorktreeDiff, wt as defineProfileMaterializationContract, yt as createWorktree } from "./supervisor-CAXiCKnX.js";
|
|
5
5
|
import { i as notifyRuntimeHookEvent, t as composeRuntimeHooks } from "./runtime-hooks-C7iJOWm3.js";
|
|
6
|
-
import { a as notifySandboxEventObserver, n as extractLlmCallEvent } from "./sandbox-events-
|
|
7
|
-
import { i as resolveAgentEnvironmentProvider, n as providerAsExecutor, s as buildBackendOptions } from "./environment-provider-
|
|
6
|
+
import { a as notifySandboxEventObserver, n as extractLlmCallEvent } from "./sandbox-events-Yhd1GYWl.js";
|
|
7
|
+
import { i as resolveAgentEnvironmentProvider, n as providerAsExecutor, r as providerAsSandboxClient, s as buildBackendOptions } from "./environment-provider-CzcEyRL7.js";
|
|
8
8
|
import { t as createStdioToolServer } from "./tool-server-RcWgLIsL.js";
|
|
9
9
|
import { n as UI_LENSES } from "./substrate-B0TYNrXn.js";
|
|
10
10
|
import { argHash, computeFindingId, errorStreakDetector, estimateCost, isModelPriced, makeFinding, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
|
|
11
|
-
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
11
|
+
import { agentProfileSchema, canonicalCandidateDigest, mergeAgentProfiles, validateAgentProfileSecurity } from "@tangle-network/agent-interface";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
14
14
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
-
import path, { dirname, isAbsolute, join, sep } from "node:path";
|
|
15
|
+
import path, { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
16
16
|
import { spawn } from "node:child_process";
|
|
17
17
|
import { homedir, tmpdir } from "node:os";
|
|
18
18
|
import { createServer, request } from "node:http";
|
|
@@ -20,6 +20,28 @@ import { request as request$1 } from "node:https";
|
|
|
20
20
|
import { Readable, Writable } from "node:stream";
|
|
21
21
|
//#region src/runtime/supervise/completion-gate.ts
|
|
22
22
|
/**
|
|
23
|
+
*
|
|
24
|
+
* The completion-oracle: **settled ⟺ DELIVERED.**
|
|
25
|
+
*
|
|
26
|
+
* Foreman's one hard lesson (0/18 self-improvement deliverables) — "done" must mean a check
|
|
27
|
+
* PASSED, not the agent's say-so. `gateOnDeliverable` wraps an `Executor` so its settlement
|
|
28
|
+
* is `valid` ONLY when the deliverable check passes. The child still RUNS and settles (its
|
|
29
|
+
* spend is conserved into the pool either way), but a child that ran WITHOUT delivering
|
|
30
|
+
* settles `valid:false` — so a keep-best driver never counts it as done, and a gate never
|
|
31
|
+
* inflates with self-judged wins.
|
|
32
|
+
*
|
|
33
|
+
* Dual-purpose by construction:
|
|
34
|
+
* - product: the agent fleet only advances on real, checked deliverables.
|
|
35
|
+
* - proof: the gate's `valid` is the honest settle — equal-k comparisons can't be gamed by an
|
|
36
|
+
* arm that "ran" without producing the artifact.
|
|
37
|
+
*
|
|
38
|
+
* The check is a DEPLOYABLE oracle — a test command, a state verifier, the commit0 judge —
|
|
39
|
+
* read off the child's output, never the model judging itself. A throwing check is
|
|
40
|
+
* fail-closed (not delivered), never a crash.
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
23
45
|
* Wrap an `Executor` so its settlement `valid` reflects the deliverable check, not the
|
|
24
46
|
* inner verdict. Handles both `execute` shapes (one-shot `Promise<ExecutorResult>` and
|
|
25
47
|
* streaming `AsyncIterable<UsageEvent>` + `resultArtifact()`); the check runs once the inner
|
|
@@ -39,7 +61,22 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
39
61
|
score: baseScore ?? (delivered ? 1 : 0)
|
|
40
62
|
};
|
|
41
63
|
};
|
|
42
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Ask the delivery question once, from whatever the inner executor managed to produce.
|
|
66
|
+
*
|
|
67
|
+
* Fail-closed on the artifact being unavailable: an executor that never produced one delivered
|
|
68
|
+
* nothing, and leaving `gated` unset keeps the existing invalid-by-default reading.
|
|
69
|
+
*/
|
|
70
|
+
const settleVerdict = async () => {
|
|
71
|
+
let art;
|
|
72
|
+
try {
|
|
73
|
+
art = inner.resultArtifact();
|
|
74
|
+
} catch {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
gated = await check(art.out, art.verdict?.score);
|
|
78
|
+
};
|
|
79
|
+
return inheritRuntimeOwnedExecutorAttestation(inner, {
|
|
43
80
|
runtime: inner.runtime,
|
|
44
81
|
...inner.budgetExempt !== void 0 ? { budgetExempt: inner.budgetExempt } : {},
|
|
45
82
|
...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
|
|
@@ -48,13 +85,21 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
48
85
|
...inner.metered ? { metered: () => inner.metered?.() } : {},
|
|
49
86
|
execute(task, signal) {
|
|
50
87
|
const r = inner.execute(task, signal);
|
|
51
|
-
if (isAsyncIterable$
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
if (isAsyncIterable$2(r)) return (async function* () {
|
|
89
|
+
try {
|
|
90
|
+
for await (const ev of r) yield ev;
|
|
91
|
+
} finally {
|
|
92
|
+
await settleVerdict();
|
|
93
|
+
}
|
|
55
94
|
})();
|
|
56
95
|
return (async () => {
|
|
57
|
-
|
|
96
|
+
let res;
|
|
97
|
+
try {
|
|
98
|
+
res = await r;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await settleVerdict();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
58
103
|
gated = await check(res.out, res.verdict?.score);
|
|
59
104
|
return {
|
|
60
105
|
...res,
|
|
@@ -70,19 +115,281 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
70
115
|
verdict: gated ?? art.verdict
|
|
71
116
|
};
|
|
72
117
|
}
|
|
73
|
-
};
|
|
118
|
+
});
|
|
74
119
|
}
|
|
75
|
-
function isAsyncIterable$
|
|
120
|
+
function isAsyncIterable$2(v) {
|
|
76
121
|
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
77
122
|
}
|
|
78
123
|
//#endregion
|
|
79
|
-
//#region src/runtime/supervise/
|
|
124
|
+
//#region src/runtime/supervise/authoring.ts
|
|
125
|
+
/**
|
|
126
|
+
*
|
|
127
|
+
* The supervisor's intelligence is AUTHORING the agents it spawns — not pressing buttons.
|
|
128
|
+
*
|
|
129
|
+
* Every agent here is three things: instructions (system prompt), tools, and a model — its
|
|
130
|
+
* `AgentProfile`. The supervisor's job is to WRITE those profiles: read the task, decompose it,
|
|
131
|
+
* and for each sub-task author a tailored worker recipe. `supervisorInstructions` is the how-to the
|
|
132
|
+
* supervisor reads (its system prompt); `authoredWorker` builds a worker AGENT from a profile the
|
|
133
|
+
* supervisor authored — the authored systemPrompt + model shape the worker's call.
|
|
134
|
+
*
|
|
135
|
+
* The skill is the single OPTIMIZABLE surface: edit it → the supervisor designs better agents.
|
|
136
|
+
* That is the self-improvement lever (the prompt/skill lever), not the execution plumbing.
|
|
137
|
+
*
|
|
138
|
+
* @experimental
|
|
139
|
+
*/
|
|
140
|
+
/** Narrow an untyped `spawn_agent` profile argument to an `AuthoredProfile`, or null if the
|
|
141
|
+
* supervisor failed to author one (empty/placeholder profile — a skill violation worth catching). */
|
|
142
|
+
function asAuthoredProfile(raw) {
|
|
143
|
+
const parsed = agentProfileSchema.safeParse(raw);
|
|
144
|
+
if (!parsed.success) return null;
|
|
145
|
+
const systemPrompt = parsed.data.prompt?.systemPrompt;
|
|
146
|
+
if (typeof systemPrompt !== "string" || systemPrompt.trim().length === 0) return null;
|
|
147
|
+
return {
|
|
148
|
+
...parsed.data,
|
|
149
|
+
name: typeof parsed.data.name === "string" && parsed.data.name.length > 0 ? parsed.data.name : "worker",
|
|
150
|
+
prompt: {
|
|
151
|
+
...parsed.data.prompt,
|
|
152
|
+
systemPrompt
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
80
156
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
157
|
+
* Lift a profile the supervisor AUTHORED into the canonical shape every executor reads.
|
|
158
|
+
*
|
|
159
|
+
* The skill asks for `systemPrompt` and `model` as flat fields — the vocabulary a model writes
|
|
160
|
+
* well — while `AgentProfile` carries them as `prompt.systemPrompt` and `model.default`. Nothing
|
|
161
|
+
* downstream reads the flat form: the router and cli-bridge leaves read `profile.prompt
|
|
162
|
+
* .systemPrompt`, and the sandbox leaf hands the profile to a strict schema that REJECTS the flat
|
|
163
|
+
* key outright (`Unrecognized key: "systemPrompt"`), which fails the worker's every round. Lift
|
|
164
|
+
* both here, once, so what the supervisor writes is what the worker runs.
|
|
165
|
+
*
|
|
166
|
+
* Purely additive: a profile already canonical is returned untouched, and a flat field is dropped
|
|
167
|
+
* only after its canonical slot is filled. Both spellings of the same standing instruction, set to
|
|
168
|
+
* DIFFERENT text, is a contradiction with no safe reading — it fails loud, matching
|
|
169
|
+
* `resolveSupervisorProfile`'s rule for the supervisor's own profile.
|
|
85
170
|
*/
|
|
171
|
+
function canonicalizeAuthoredProfile(raw) {
|
|
172
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw ?? {};
|
|
173
|
+
const authored = { ...raw };
|
|
174
|
+
const flatPrompt = authored.systemPrompt;
|
|
175
|
+
if (typeof flatPrompt === "string" && flatPrompt.trim().length > 0) {
|
|
176
|
+
const prompt = authored.prompt && typeof authored.prompt === "object" && !Array.isArray(authored.prompt) ? { ...authored.prompt } : {};
|
|
177
|
+
const canonicalPrompt = prompt.systemPrompt;
|
|
178
|
+
if (typeof canonicalPrompt === "string" && canonicalPrompt !== flatPrompt) throw new ValidationError(`canonicalizeAuthoredProfile: prompt.systemPrompt and systemPrompt are both set and differ — they are the same standing instruction, so author exactly one (prompt.systemPrompt: ${JSON.stringify(canonicalPrompt.slice(0, 80))}; systemPrompt: ${JSON.stringify(flatPrompt.slice(0, 80))})`);
|
|
179
|
+
if (typeof canonicalPrompt !== "string") prompt.systemPrompt = flatPrompt;
|
|
180
|
+
authored.prompt = prompt;
|
|
181
|
+
delete authored.systemPrompt;
|
|
182
|
+
}
|
|
183
|
+
if (typeof authored.model === "string" && authored.model.trim().length > 0) authored.model = { default: authored.model };
|
|
184
|
+
return authored;
|
|
185
|
+
}
|
|
186
|
+
/** The supervisor SKILL — the how-to the supervisor reads (its system prompt). THE optimizable
|
|
187
|
+
* surface: editing this changes how the supervisor designs every agent it spawns. */
|
|
188
|
+
function supervisorInstructions(opts) {
|
|
189
|
+
return [
|
|
190
|
+
"You are a SUPERVISOR. You do NOT do the work yourself — your job is to DESIGN and DRIVE specialist worker agents.",
|
|
191
|
+
"",
|
|
192
|
+
"For the task you are given:",
|
|
193
|
+
"1. DECOMPOSE it into the smallest set of sub-tasks a single focused worker can each deliver.",
|
|
194
|
+
"2. For EACH sub-task, AUTHOR a worker by calling spawn_agent with a COMPLETE `profile`:",
|
|
195
|
+
" • name and description: who this specialist is and why it exists.",
|
|
196
|
+
" • prompt.systemPrompt: rich instructions for THIS sub-task — exact output, process, evidence, and what \"done\" means.",
|
|
197
|
+
" • model.default, model.reasoningEffort, and harness: choose the execution system deliberately when the task benefits from it.",
|
|
198
|
+
" • tools, mcp, resources.skills/files/instructions, hooks, subagents, permissions, and modes: grant or attach every capability the worker needs; omit an axis only when it is intentionally unnecessary.",
|
|
199
|
+
" • metadata.role=\"driver\" when this child should be a sub-supervisor that may author and drive its own children.",
|
|
200
|
+
" NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
|
|
201
|
+
"3. await_event (kinds:['settled']) to collect each worker. Its result says valid:true only if the deployable check passed.",
|
|
202
|
+
"4. If a worker did NOT deliver, AUTHOR A NEW profile whose prompt.systemPrompt names the SPECIFIC failure and how to fix it — never just retry the same profile.",
|
|
203
|
+
"5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself — only a delivered (valid:true) worker counts.",
|
|
204
|
+
...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
|
|
205
|
+
].join("\n");
|
|
206
|
+
}
|
|
207
|
+
/** Build a router-only worker from an authored profile. This helper executes the prompt/model axes;
|
|
208
|
+
* use `workerFromBackend` for full materialization of tools, MCP, resources, hooks, and subagents. */
|
|
209
|
+
function authoredWorker(profile, opts) {
|
|
210
|
+
const model = profile.model?.default ?? opts.cfg.model;
|
|
211
|
+
const executorFactory = (spec, ctx) => {
|
|
212
|
+
let artifact;
|
|
213
|
+
const executionId = ctx.node?.nodeId ?? `authored-router-${profile.name}`;
|
|
214
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
215
|
+
return gateOnDeliverable(attestRuntimeOwnedExecutor({
|
|
216
|
+
runtime: "router",
|
|
217
|
+
async execute(_t, signal) {
|
|
218
|
+
const res = await routerChatWithUsage({
|
|
219
|
+
...opts.cfg,
|
|
220
|
+
model
|
|
221
|
+
}, [{
|
|
222
|
+
role: "system",
|
|
223
|
+
content: profile.prompt.systemPrompt
|
|
224
|
+
}, {
|
|
225
|
+
role: "user",
|
|
226
|
+
content: opts.taskPrompt
|
|
227
|
+
}], {
|
|
228
|
+
temperature: opts.temperature ?? .4,
|
|
229
|
+
...signal ? { signal } : {}
|
|
230
|
+
});
|
|
231
|
+
artifact = {
|
|
232
|
+
outRef: contentAddress(res.content),
|
|
233
|
+
out: res.content,
|
|
234
|
+
spent: {
|
|
235
|
+
iterations: 1,
|
|
236
|
+
tokens: res.usage ?? {
|
|
237
|
+
input: 0,
|
|
238
|
+
output: 0
|
|
239
|
+
},
|
|
240
|
+
usd: res.costUsd ?? 0,
|
|
241
|
+
ms: 0
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
return artifact;
|
|
245
|
+
},
|
|
246
|
+
teardown: () => Promise.resolve({ destroyed: true }),
|
|
247
|
+
resultArtifact: () => {
|
|
248
|
+
if (!artifact) throw new Error("authoredWorker: resultArtifact read before execute");
|
|
249
|
+
return artifact;
|
|
250
|
+
}
|
|
251
|
+
}, {
|
|
252
|
+
effectiveProfile: spec.profile,
|
|
253
|
+
backend: "router",
|
|
254
|
+
model: {
|
|
255
|
+
status: "known",
|
|
256
|
+
id: model
|
|
257
|
+
},
|
|
258
|
+
execution: {
|
|
259
|
+
kind: "request",
|
|
260
|
+
id: executionId
|
|
261
|
+
},
|
|
262
|
+
materializer: "authored-router-prompt",
|
|
263
|
+
plan: {
|
|
264
|
+
kind: "authored-router-completion",
|
|
265
|
+
model,
|
|
266
|
+
temperature: opts.temperature ?? .4,
|
|
267
|
+
taskPrompt: opts.taskPrompt
|
|
268
|
+
}
|
|
269
|
+
}, {
|
|
270
|
+
attemptId,
|
|
271
|
+
binding: {
|
|
272
|
+
endpoint: opts.cfg.routerBaseUrl,
|
|
273
|
+
executionId,
|
|
274
|
+
model
|
|
275
|
+
},
|
|
276
|
+
descriptor: {
|
|
277
|
+
kind: "router-request",
|
|
278
|
+
transport: "http",
|
|
279
|
+
backend: "router"
|
|
280
|
+
}
|
|
281
|
+
}), opts.deliverable);
|
|
282
|
+
};
|
|
283
|
+
const spec = {
|
|
284
|
+
profile,
|
|
285
|
+
harness: null,
|
|
286
|
+
executorFactory
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
name: profile.name,
|
|
290
|
+
act: async () => "",
|
|
291
|
+
executorSpec: spec
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/** Default thresholds for `ProfileRichnessThresholds` — 600 chars / 6 lines minimum system prompt. */
|
|
295
|
+
const defaultProfileRichnessThresholds = {
|
|
296
|
+
minSystemPromptChars: 600,
|
|
297
|
+
minSystemPromptLines: 6
|
|
298
|
+
};
|
|
299
|
+
/** Read the system prompt from any authored shape: canonical `prompt.systemPrompt`, the sandbox
|
|
300
|
+
* `prompt.system` convention, or a bare-string `prompt`. */
|
|
301
|
+
function resolveSystemPrompt(profile) {
|
|
302
|
+
const pr = profile.prompt;
|
|
303
|
+
if (typeof pr === "string") return pr;
|
|
304
|
+
if (pr && typeof pr === "object") {
|
|
305
|
+
const o = pr;
|
|
306
|
+
if (typeof o.systemPrompt === "string") return o.systemPrompt;
|
|
307
|
+
if (typeof o.system === "string") return o.system;
|
|
308
|
+
}
|
|
309
|
+
return "";
|
|
310
|
+
}
|
|
311
|
+
/** OBSERVE one authored `AgentProfile` and score its richness (no judge verdict is read). The task
|
|
312
|
+
* context (`needsMcp`) lets a domain say "this work needs a data/tool MCP" so a missing MCP counts. */
|
|
313
|
+
function assessAuthoredProfile(profile, opts) {
|
|
314
|
+
const th = {
|
|
315
|
+
...defaultProfileRichnessThresholds,
|
|
316
|
+
...opts?.thresholds ?? {}
|
|
317
|
+
};
|
|
318
|
+
const systemPrompt = resolveSystemPrompt(profile);
|
|
319
|
+
const trimmed = systemPrompt.trim();
|
|
320
|
+
const systemPromptChars = trimmed.length;
|
|
321
|
+
const systemPromptLines = trimmed ? trimmed.split("\n").filter((l) => l.trim().length > 0).length : 0;
|
|
322
|
+
const sentenceCount = trimmed ? (trimmed.match(/[.!?](\s|$)/g) ?? []).length || (trimmed ? 1 : 0) : 0;
|
|
323
|
+
const hasDescription = typeof profile.description === "string" && profile.description.trim().length > 0;
|
|
324
|
+
const tools = profile.tools;
|
|
325
|
+
const hasTools = !!tools && Object.keys(tools).length > 0;
|
|
326
|
+
const skills = profile.resources?.skills;
|
|
327
|
+
const hasSkills = Array.isArray(skills) && skills.length > 0;
|
|
328
|
+
const mcp = profile.mcp;
|
|
329
|
+
const hasMcp = !!mcp && Object.keys(mcp).length > 0;
|
|
330
|
+
const subagents = profile.subagents;
|
|
331
|
+
const hasSubagents = !!subagents && Object.keys(subagents).length > 0;
|
|
332
|
+
const reasons = [];
|
|
333
|
+
const promptThin = systemPromptChars < th.minSystemPromptChars || systemPromptLines < th.minSystemPromptLines;
|
|
334
|
+
if (promptThin) reasons.push(`system prompt is thin (${systemPromptChars} chars, ${systemPromptLines} lines; need ≥${th.minSystemPromptChars} chars and ≥${th.minSystemPromptLines} lines)`);
|
|
335
|
+
if (!hasTools) reasons.push("no tools granted (a worker can only act through the tools you grant it)");
|
|
336
|
+
if (!hasSkills) reasons.push("no skills attached (no reusable how-to notes injected)");
|
|
337
|
+
if (opts?.needsMcp && !hasMcp) reasons.push("no MCP server, but the task needs data/tool access");
|
|
338
|
+
const signals = [
|
|
339
|
+
!promptThin,
|
|
340
|
+
hasTools,
|
|
341
|
+
hasSkills,
|
|
342
|
+
hasDescription,
|
|
343
|
+
opts?.needsMcp ? hasMcp : true
|
|
344
|
+
];
|
|
345
|
+
const richness = signals.filter(Boolean).length / signals.length;
|
|
346
|
+
const thin = promptThin || !hasTools && !hasSkills && !hasMcp;
|
|
347
|
+
return {
|
|
348
|
+
name: profile.name ?? "worker",
|
|
349
|
+
systemPrompt,
|
|
350
|
+
systemPromptChars,
|
|
351
|
+
systemPromptLines,
|
|
352
|
+
sentenceCount,
|
|
353
|
+
hasDescription,
|
|
354
|
+
hasTools,
|
|
355
|
+
hasSkills,
|
|
356
|
+
hasMcp,
|
|
357
|
+
hasSubagents,
|
|
358
|
+
richness,
|
|
359
|
+
thin,
|
|
360
|
+
reasons
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** Turn a {@link ProfileRichness} verdict into a bus-routable `AnalystFinding` (area `profile-quality`).
|
|
364
|
+
* Severity scales with thinness; the recommended action names the MISSING lever so the supervisor can
|
|
365
|
+
* re-author. `subject` = the worker name so per-worker findings diff cleanly across re-authors. */
|
|
366
|
+
function profileRichnessFinding(richness, opts) {
|
|
367
|
+
const analyst_id = opts?.analystId ?? "profile-richness";
|
|
368
|
+
const subject = richness.name;
|
|
369
|
+
const claim = richness.thin ? `Worker "${richness.name}" was authored as a THIN profile: ${richness.reasons.join("; ")}.` : `Worker "${richness.name}" was authored as a rich profile (richness ${(richness.richness * 100).toFixed(0)}%).`;
|
|
370
|
+
return makeFinding({
|
|
371
|
+
analyst_id,
|
|
372
|
+
severity: richness.thin ? richness.richness < .25 ? "high" : "medium" : "info",
|
|
373
|
+
area: "profile-quality",
|
|
374
|
+
claim,
|
|
375
|
+
subject,
|
|
376
|
+
confidence: .9,
|
|
377
|
+
evidence_refs: [{
|
|
378
|
+
kind: "metric",
|
|
379
|
+
uri: `profile:${subject}`,
|
|
380
|
+
excerpt: `chars=${richness.systemPromptChars} lines=${richness.systemPromptLines} tools=${richness.hasTools} skills=${richness.hasSkills} mcp=${richness.hasMcp} richness=${richness.richness.toFixed(2)}`
|
|
381
|
+
}],
|
|
382
|
+
...richness.thin ? { recommended_action: `Re-author "${richness.name}" with: ${richness.reasons.join("; ")}.` } : {},
|
|
383
|
+
id_basis: computeFindingId({
|
|
384
|
+
analyst_id,
|
|
385
|
+
area: "profile-quality",
|
|
386
|
+
subject,
|
|
387
|
+
claim: `richness:${richness.thin ? "thin" : "rich"}`
|
|
388
|
+
})
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/runtime/supervise/model-policy.ts
|
|
86
393
|
/**
|
|
87
394
|
* Throw a `ConfigError` when `allowed` is set, `model` is defined, and `model` is not a
|
|
88
395
|
* member of `allowed`. No-op when `allowed` is unset (the unrestricted default) or when
|
|
@@ -92,6 +399,14 @@ function assertModelAllowed(model, allowed) {
|
|
|
92
399
|
if (!allowed || model === void 0) return;
|
|
93
400
|
if (!allowed.includes(model)) throw new ConfigError(`model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`);
|
|
94
401
|
}
|
|
402
|
+
/** Check every canonical model-bearing field in a complete profile, including the models a
|
|
403
|
+
* backend may select for cheap work, named subagents, or modes. */
|
|
404
|
+
function assertProfileModelsAllowed(profile, allowed) {
|
|
405
|
+
assertModelAllowed(profile.model?.default, allowed);
|
|
406
|
+
assertModelAllowed(profile.model?.small, allowed);
|
|
407
|
+
for (const subagent of Object.values(profile.subagents ?? {})) assertModelAllowed(subagent.model, allowed);
|
|
408
|
+
for (const mode of Object.values(profile.modes ?? {})) assertModelAllowed(mode.model, allowed);
|
|
409
|
+
}
|
|
95
410
|
//#endregion
|
|
96
411
|
//#region src/runtime/supervise/otel-spans.ts
|
|
97
412
|
/**
|
|
@@ -402,59 +717,96 @@ function truncate(value) {
|
|
|
402
717
|
}
|
|
403
718
|
//#endregion
|
|
404
719
|
//#region src/runtime/supervise/coordination-log.ts
|
|
405
|
-
/**
|
|
406
|
-
*
|
|
720
|
+
/**
|
|
721
|
+
* Durable side-log for coordination evidence the spawn journal does not own: questions, analyst
|
|
722
|
+
* findings, answer decisions, authorized continuation receipts, delivery-attempt markers, and
|
|
723
|
+
* delivery outcomes. A durable run
|
|
724
|
+
* (`supervise({ runDir })`) appends them as they publish and loads them on resume, so a restarted
|
|
725
|
+
* coordinator retains the exact evidence produced by prior processes.
|
|
726
|
+
*
|
|
727
|
+
* Answer down-events also fold status on load: a question answered before the crash reloads as
|
|
728
|
+
* `answered`, not as a re-blocking `open`. Settled events are skipped (the spawn journal is their
|
|
729
|
+
* ledger). A receipt followed by an attempt but no outcome proves the process died in the delivery
|
|
730
|
+
* window; that outcome remains unknown and no prior instruction is auto-delivered.
|
|
731
|
+
*
|
|
732
|
+
* JSONL, one fsynced record per event, keyed by `runId` — several runs may share one log file
|
|
733
|
+
* exactly as they share one spawn-journal file.
|
|
734
|
+
*
|
|
735
|
+
* @experimental
|
|
736
|
+
*/
|
|
737
|
+
/** Persist prior context plus exact continuation authorization, attempt, and result evidence.
|
|
738
|
+
* Settlements have their own journal. */
|
|
407
739
|
function persisted(event) {
|
|
408
|
-
return event.type
|
|
740
|
+
return event.type !== "settled";
|
|
409
741
|
}
|
|
410
742
|
/** FS-backed `CoordinationLog`: append-only JSONL, fsynced per record. */
|
|
411
743
|
var FileCoordinationLog = class {
|
|
412
744
|
path;
|
|
745
|
+
appendTail = Promise.resolve();
|
|
413
746
|
constructor(path) {
|
|
414
747
|
this.path = path;
|
|
415
748
|
}
|
|
416
|
-
async append(runId,
|
|
417
|
-
if (!persisted(event)) return;
|
|
749
|
+
async append(runId, record, ownerId) {
|
|
750
|
+
if (!persisted(record.event)) return;
|
|
751
|
+
const append = this.appendTail.then(() => this.appendRecord(runId, record, ownerId));
|
|
752
|
+
this.appendTail = append.catch(() => void 0);
|
|
753
|
+
return append;
|
|
754
|
+
}
|
|
755
|
+
async appendRecord(runId, busRecord, ownerId) {
|
|
418
756
|
const fs = await import("node:fs/promises");
|
|
419
757
|
const path = await import("node:path");
|
|
420
758
|
await fs.mkdir(path.dirname(this.path), { recursive: true });
|
|
421
759
|
const record = {
|
|
422
760
|
runId,
|
|
423
|
-
|
|
424
|
-
|
|
761
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
762
|
+
...busRecord
|
|
425
763
|
};
|
|
764
|
+
const needsSeparator = await prepareJsonlAppend(this.path);
|
|
426
765
|
const fh = await fs.open(this.path, "a");
|
|
427
766
|
try {
|
|
428
|
-
await fh
|
|
767
|
+
await writeAllBytes(fh, `${needsSeparator ? "\n" : ""}${JSON.stringify(record)}\n`);
|
|
429
768
|
await fh.sync();
|
|
430
769
|
} finally {
|
|
431
770
|
await fh.close();
|
|
432
771
|
}
|
|
433
772
|
}
|
|
434
|
-
async load(runId) {
|
|
773
|
+
async load(runId, ownerId) {
|
|
435
774
|
const fs = await import("node:fs/promises");
|
|
436
775
|
let text;
|
|
437
776
|
try {
|
|
438
777
|
text = await fs.readFile(this.path, "utf8");
|
|
439
778
|
} catch (err) {
|
|
440
|
-
if (isNoEntError(err)) return
|
|
441
|
-
questions: [],
|
|
442
|
-
findings: []
|
|
443
|
-
};
|
|
779
|
+
if (isNoEntError(err)) return emptyPriorCoordination(ownerId);
|
|
444
780
|
throw err;
|
|
445
781
|
}
|
|
446
782
|
const byId = /* @__PURE__ */ new Map();
|
|
447
783
|
const findings = [];
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
784
|
+
const continuations = [];
|
|
785
|
+
const deliveryEvidence = [];
|
|
786
|
+
const records = [];
|
|
787
|
+
let legacySeq = 0;
|
|
788
|
+
for (const stored of parseCommittedJsonLines(text, this.path)) {
|
|
789
|
+
if (stored.runId !== runId) continue;
|
|
790
|
+
if (ownerId !== void 0 && stored.ownerId !== ownerId) continue;
|
|
791
|
+
const record = "seq" in stored ? {
|
|
792
|
+
seq: stored.seq,
|
|
793
|
+
at: stored.at,
|
|
794
|
+
priority: stored.priority,
|
|
795
|
+
event: stored.event
|
|
796
|
+
} : {
|
|
797
|
+
seq: legacySeq++,
|
|
798
|
+
at: Date.parse(stored.at),
|
|
799
|
+
priority: 0,
|
|
800
|
+
event: stored.event
|
|
801
|
+
};
|
|
802
|
+
records.push(record);
|
|
452
803
|
const ev = record.event;
|
|
804
|
+
if (ev.type === "delivery-attempt" || ev.type === "steer" || ev.type === "answer") deliveryEvidence.push(ev);
|
|
453
805
|
if (ev.type === "question") byId.set(ev.question.id, ev.question);
|
|
454
806
|
else if (ev.type === "finding") findings.push(ev.finding);
|
|
455
807
|
else if (ev.type === "answer") {
|
|
456
808
|
const prior = byId.get(ev.questionId);
|
|
457
|
-
if (prior) byId.set(ev.questionId, {
|
|
809
|
+
if (prior && ev.down.delivered) byId.set(ev.questionId, {
|
|
458
810
|
...prior,
|
|
459
811
|
status: "answered",
|
|
460
812
|
decision: {
|
|
@@ -463,14 +815,28 @@ var FileCoordinationLog = class {
|
|
|
463
815
|
by: "prior-run"
|
|
464
816
|
}
|
|
465
817
|
});
|
|
466
|
-
}
|
|
818
|
+
} else if (ev.type === "instruction") continuations.push(ev.instruction);
|
|
467
819
|
}
|
|
468
820
|
return {
|
|
821
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
469
822
|
questions: [...byId.values()],
|
|
470
|
-
findings
|
|
823
|
+
findings,
|
|
824
|
+
continuations,
|
|
825
|
+
deliveryEvidence,
|
|
826
|
+
records
|
|
471
827
|
};
|
|
472
828
|
}
|
|
473
829
|
};
|
|
830
|
+
function emptyPriorCoordination(ownerId) {
|
|
831
|
+
return {
|
|
832
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
833
|
+
questions: [],
|
|
834
|
+
findings: [],
|
|
835
|
+
continuations: [],
|
|
836
|
+
deliveryEvidence: [],
|
|
837
|
+
records: []
|
|
838
|
+
};
|
|
839
|
+
}
|
|
474
840
|
function isNoEntError(err) {
|
|
475
841
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
476
842
|
}
|
|
@@ -1561,9 +1927,10 @@ function createInbox() {
|
|
|
1561
1927
|
return {
|
|
1562
1928
|
deliver(msg) {
|
|
1563
1929
|
const m = parseDown(msg);
|
|
1564
|
-
if (!m) return;
|
|
1930
|
+
if (!m) return false;
|
|
1565
1931
|
pending.push(m);
|
|
1566
1932
|
if (m.interrupt && live && !live.signal.aborted) live.abort();
|
|
1933
|
+
return true;
|
|
1567
1934
|
},
|
|
1568
1935
|
drain() {
|
|
1569
1936
|
return pending.splice(0, pending.length);
|
|
@@ -2220,8 +2587,8 @@ function sandboxSessionTraceSource(box, sessionId, opts = {}) {
|
|
|
2220
2587
|
* | `profile.prompt.systemPrompt` | honored — prepended to the task text (pi RPC takes no separate system-prompt channel) |
|
|
2221
2588
|
* | `profile.mcp` | honored — written to this execution's own file and passed as `--mcp-config` for `pi-mcp-adapter`; see `pi-mcp.ts` |
|
|
2222
2589
|
* | `profile.extensions.pi.load` | honored — lowered to `--no-extensions` + `--extension <abs>` |
|
|
2223
|
-
* | `profile.prompt.instructions` |
|
|
2224
|
-
* | `profile.model` |
|
|
2590
|
+
* | `profile.prompt.instructions` | honored — appended to the system prompt, one per line |
|
|
2591
|
+
* | `profile.model.default` | honored — overrides the seam's `model`; the seam is the fallback for profiles that select none |
|
|
2225
2592
|
* | `profile.model.reasoningEffort` | DROPPED — no `--thinking` flag is emitted, so pi's configured `defaultThinkingLevel` applies |
|
|
2226
2593
|
* | `profile.tools` | DROPPED — no `--no-tools` / allow-deny mapping; pi runs its full builtin tool set |
|
|
2227
2594
|
* | `profile.permissions` | DROPPED |
|
|
@@ -2242,12 +2609,17 @@ const PI_RUNTIME = "pi";
|
|
|
2242
2609
|
const piSeamKey = "pi";
|
|
2243
2610
|
/** Build the `Executor` for one pi worker. Registered as runtime `'pi'`. */
|
|
2244
2611
|
const piExecutor = (spec, ctx) => {
|
|
2245
|
-
const seam =
|
|
2612
|
+
const seam = {
|
|
2613
|
+
...readPiSeam(ctx),
|
|
2614
|
+
...spec.profile.model?.default ? { model: spec.profile.model.default } : {}
|
|
2615
|
+
};
|
|
2246
2616
|
const traceEnv = workerTraceEnv(ctx);
|
|
2247
2617
|
const inbox = createInbox();
|
|
2248
2618
|
const activity = createActivityLog(seam.activityWindow ?? 12);
|
|
2249
2619
|
const runId = `pi-${spec.profile.name ?? "worker"}-${Date.now()}`;
|
|
2250
2620
|
const trace = createPushTraceSource({ runId });
|
|
2621
|
+
const executionId = ctx.node?.nodeId ?? `pi-run-${randomUUID()}`;
|
|
2622
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
2251
2623
|
const derived = [];
|
|
2252
2624
|
const controller = new AbortController();
|
|
2253
2625
|
const cascade = () => controller.abort();
|
|
@@ -2260,7 +2632,7 @@ const piExecutor = (spec, ctx) => {
|
|
|
2260
2632
|
proc: void 0,
|
|
2261
2633
|
artifact: void 0
|
|
2262
2634
|
};
|
|
2263
|
-
|
|
2635
|
+
const executor = {
|
|
2264
2636
|
runtime: "pi",
|
|
2265
2637
|
deliver: (m) => inbox.deliver(m),
|
|
2266
2638
|
progress: () => ({
|
|
@@ -2301,6 +2673,44 @@ const piExecutor = (spec, ctx) => {
|
|
|
2301
2673
|
return state.artifact;
|
|
2302
2674
|
}
|
|
2303
2675
|
};
|
|
2676
|
+
if (ctx.node === void 0) return executor;
|
|
2677
|
+
return attestRuntimeOwnedExecutor(executor, {
|
|
2678
|
+
effectiveProfile: spec.profile,
|
|
2679
|
+
backend: "pi",
|
|
2680
|
+
model: seam.model ? {
|
|
2681
|
+
status: "known",
|
|
2682
|
+
id: seam.model
|
|
2683
|
+
} : {
|
|
2684
|
+
status: "unknown",
|
|
2685
|
+
reason: "pi selected its configured default model"
|
|
2686
|
+
},
|
|
2687
|
+
execution: {
|
|
2688
|
+
kind: "run",
|
|
2689
|
+
id: executionId
|
|
2690
|
+
},
|
|
2691
|
+
materializer: "pi-rpc-agent-profile",
|
|
2692
|
+
plan: {
|
|
2693
|
+
kind: "pi-rpc-session",
|
|
2694
|
+
bin: seam.bin ?? "pi",
|
|
2695
|
+
args: seam.args ?? [],
|
|
2696
|
+
cwd: seam.cwd ?? null,
|
|
2697
|
+
model: seam.model ?? null,
|
|
2698
|
+
turnTimeoutMs: seam.turnTimeoutMs ?? null
|
|
2699
|
+
}
|
|
2700
|
+
}, {
|
|
2701
|
+
attemptId,
|
|
2702
|
+
binding: {
|
|
2703
|
+
executionId,
|
|
2704
|
+
bin: seam.bin ?? "pi",
|
|
2705
|
+
cwd: seam.cwd ?? null,
|
|
2706
|
+
model: seam.model ?? null
|
|
2707
|
+
},
|
|
2708
|
+
descriptor: {
|
|
2709
|
+
kind: "pi-rpc-run",
|
|
2710
|
+
transport: "process",
|
|
2711
|
+
backend: "pi"
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2304
2714
|
};
|
|
2305
2715
|
/**
|
|
2306
2716
|
* One pi RPC session, run to `agent_settled`. Every steer delivered while the turn is in flight is
|
|
@@ -2317,6 +2727,7 @@ async function* streamPiSession(args) {
|
|
|
2317
2727
|
};
|
|
2318
2728
|
let usd = 0;
|
|
2319
2729
|
let usdKnown = true;
|
|
2730
|
+
let tokensKnown = true;
|
|
2320
2731
|
throwIfAborted(args.signal);
|
|
2321
2732
|
throwIfAborted(args.controller.signal);
|
|
2322
2733
|
const piMcp = preparePiMcp(args.spec.profile, {
|
|
@@ -2383,7 +2794,7 @@ async function* streamPiSession(args) {
|
|
|
2383
2794
|
};
|
|
2384
2795
|
args.signal.addEventListener("abort", abortAll, { once: true });
|
|
2385
2796
|
args.controller.signal.addEventListener("abort", abortAll, { once: true });
|
|
2386
|
-
const system = args.spec.profile.prompt?.systemPrompt;
|
|
2797
|
+
const system = [args.spec.profile.prompt?.systemPrompt, ...args.spec.profile.prompt?.instructions ?? []].filter((line) => typeof line === "string" && line.trim().length > 0).join("\n");
|
|
2387
2798
|
const opening = system ? `${system}\n\n${taskText(args.task)}` : taskText(args.task);
|
|
2388
2799
|
const deadline = seam.turnTimeoutMs ? Date.now() + seam.turnTimeoutMs : void 0;
|
|
2389
2800
|
const sendPrompt = (message, streamingBehavior) => {
|
|
@@ -2413,6 +2824,7 @@ async function* streamPiSession(args) {
|
|
|
2413
2824
|
}
|
|
2414
2825
|
const projected = projectPiEvent(ev, args, tokens, pendingTools);
|
|
2415
2826
|
if (projected.assistant) lastAssistant = projected.assistant;
|
|
2827
|
+
if (projected.tokensUnknown) tokensKnown = false;
|
|
2416
2828
|
for (const usage of projected.events) {
|
|
2417
2829
|
if (usage.kind === "cost") {
|
|
2418
2830
|
usd += usage.usd;
|
|
@@ -2466,6 +2878,7 @@ async function* streamPiSession(args) {
|
|
|
2466
2878
|
const spent = {
|
|
2467
2879
|
iterations: state.turns,
|
|
2468
2880
|
tokens,
|
|
2881
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
2469
2882
|
usd,
|
|
2470
2883
|
...usdKnown ? {} : { usdKnown: false },
|
|
2471
2884
|
ms: Date.now() - started
|
|
@@ -2569,9 +2982,11 @@ function projectPiEvent(ev, args, tokens, pendingTools) {
|
|
|
2569
2982
|
});
|
|
2570
2983
|
out.push({ kind: "iteration" });
|
|
2571
2984
|
const assistant = readAssistantOutcome(ev.message);
|
|
2985
|
+
const tokensUnknown = !usage || usage.tokensKnown === false;
|
|
2572
2986
|
return {
|
|
2573
2987
|
events: out,
|
|
2574
|
-
...assistant ? { assistant } : {}
|
|
2988
|
+
...assistant ? { assistant } : {},
|
|
2989
|
+
...tokensUnknown ? { tokensUnknown: true } : {}
|
|
2575
2990
|
};
|
|
2576
2991
|
}
|
|
2577
2992
|
return { events: out };
|
|
@@ -2584,14 +2999,18 @@ function readUsage(message) {
|
|
|
2584
2999
|
const usage = message.usage;
|
|
2585
3000
|
if (!usage || typeof usage !== "object") return void 0;
|
|
2586
3001
|
const u = usage;
|
|
2587
|
-
const
|
|
2588
|
-
const
|
|
3002
|
+
const promptTokens = num(u.prompt_tokens);
|
|
3003
|
+
const freshInput = num(u.input) ?? num(u.inputTokens);
|
|
3004
|
+
const outputRaw = num(u.output) ?? num(u.outputTokens) ?? num(u.completion_tokens);
|
|
3005
|
+
const input = promptTokens ?? (freshInput ?? 0) + (num(u.cacheRead) ?? num(u.cache_read_input_tokens) ?? num(u.cacheReadInputTokens) ?? 0) + (num(u.cacheWrite) ?? num(u.cache_creation_input_tokens) ?? num(u.cacheCreationInputTokens) ?? 0);
|
|
3006
|
+
const output = outputRaw ?? 0;
|
|
2589
3007
|
const costRaw = u.cost;
|
|
2590
3008
|
const reportedUsd = num(costRaw) ?? (costRaw && typeof costRaw === "object" ? num(costRaw.total) ?? num(costRaw.totalCost) : void 0);
|
|
2591
3009
|
return {
|
|
2592
3010
|
input,
|
|
2593
3011
|
output,
|
|
2594
|
-
...reportedUsd !== void 0 && reportedUsd > 0 ? { usd: reportedUsd } : {}
|
|
3012
|
+
...reportedUsd !== void 0 && reportedUsd > 0 ? { usd: reportedUsd } : {},
|
|
3013
|
+
tokensKnown: promptTokens !== void 0 || freshInput !== void 0 || outputRaw !== void 0
|
|
2595
3014
|
};
|
|
2596
3015
|
}
|
|
2597
3016
|
function readAssistantOutcome(message) {
|
|
@@ -3026,34 +3445,38 @@ function readPromptOptions(loopCtx) {
|
|
|
3026
3445
|
* Build a worktree-CLI leaf `Executor`. Per-spawn (a fresh worktree + abort + teardown each), so a
|
|
3027
3446
|
* fanout of N profiles = N parallel worktrees that never clobber each other.
|
|
3028
3447
|
*
|
|
3029
|
-
* Fail-loud: an empty `repoRoot`/`harness
|
|
3030
|
-
*
|
|
3448
|
+
* Fail-loud: an empty `repoRoot`/`harness` or an explicitly empty `taskPrompt` throws at
|
|
3449
|
+
* construction. Calling `execute(undefined, signal)` without a configured prompt throws before a
|
|
3450
|
+
* worktree is created. `resultArtifact()` before `execute()` resolves throws.
|
|
3031
3451
|
*
|
|
3032
3452
|
* @experimental
|
|
3033
3453
|
*/
|
|
3034
3454
|
function createWorktreeCliExecutor(options) {
|
|
3035
3455
|
if (!options.repoRoot) throw new ValidationError("createWorktreeCliExecutor: repoRoot required");
|
|
3036
3456
|
if (!options.harness) throw new ValidationError("createWorktreeCliExecutor: harness required");
|
|
3037
|
-
if (typeof options.taskPrompt !== "string" || options.taskPrompt.length === 0) throw new ValidationError("createWorktreeCliExecutor: taskPrompt required");
|
|
3457
|
+
if (options.taskPrompt !== void 0 && (typeof options.taskPrompt !== "string" || options.taskPrompt.length === 0)) throw new ValidationError("createWorktreeCliExecutor: taskPrompt required");
|
|
3038
3458
|
if (options.codexReproducible && options.harness !== "codex") throw new ValidationError("createWorktreeCliExecutor: codexReproducible requires harness \"codex\"");
|
|
3039
3459
|
if (options.codexReproducible && options.budgetExempt === true) throw new ValidationError("createWorktreeCliExecutor: codexReproducible cannot be budgetExempt");
|
|
3040
3460
|
if (options.codexReadDeniedPaths !== void 0 && !options.codexReproducible) throw new ValidationError("createWorktreeCliExecutor: codexReadDeniedPaths requires codexReproducible");
|
|
3041
3461
|
const runId = options.runId ?? randomUUID();
|
|
3462
|
+
const attemptId = options.executionAttemptId ?? newExecutionAttemptId(runId);
|
|
3042
3463
|
const controller = new AbortController();
|
|
3043
3464
|
const budgetExempt = options.budgetExempt ?? !options.codexReproducible;
|
|
3044
3465
|
let run;
|
|
3045
3466
|
let artifact;
|
|
3046
|
-
|
|
3467
|
+
const profilePlan = worktreeProfileExecutionPlan(options.profile, options.harness);
|
|
3468
|
+
return attestRuntimeOwnedExecutor({
|
|
3047
3469
|
runtime: "cli",
|
|
3048
3470
|
budgetExempt,
|
|
3049
|
-
async execute(
|
|
3471
|
+
async execute(task, signal) {
|
|
3050
3472
|
const linked = linkSignals$1(signal, controller.signal);
|
|
3051
3473
|
const started = Date.now();
|
|
3474
|
+
const taskPrompt = executionTaskPrompt(task, options.taskPrompt);
|
|
3052
3475
|
run = await runWorktreeHarness({
|
|
3053
3476
|
repoRoot: options.repoRoot,
|
|
3054
3477
|
profile: options.profile,
|
|
3055
3478
|
harness: options.harness,
|
|
3056
|
-
taskPrompt
|
|
3479
|
+
taskPrompt,
|
|
3057
3480
|
runId,
|
|
3058
3481
|
...options.baseRef ? { baseRef: options.baseRef } : {},
|
|
3059
3482
|
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
@@ -3108,7 +3531,65 @@ function createWorktreeCliExecutor(options) {
|
|
|
3108
3531
|
if (!artifact) throw new ValidationError("createWorktreeCliExecutor: resultArtifact() read before execute() resolved");
|
|
3109
3532
|
return artifact;
|
|
3110
3533
|
}
|
|
3111
|
-
}
|
|
3534
|
+
}, {
|
|
3535
|
+
effectiveProfile: options.profile,
|
|
3536
|
+
backend: `cli-worktree:${options.harness}`,
|
|
3537
|
+
model: options.profile.model?.default ? {
|
|
3538
|
+
status: "known",
|
|
3539
|
+
id: options.profile.model.default
|
|
3540
|
+
} : {
|
|
3541
|
+
status: "unknown",
|
|
3542
|
+
reason: `${options.harness} selected its configured default model`
|
|
3543
|
+
},
|
|
3544
|
+
execution: {
|
|
3545
|
+
kind: "worktree-run",
|
|
3546
|
+
id: runId
|
|
3547
|
+
},
|
|
3548
|
+
materializer: "agent-profile-worktree-plan",
|
|
3549
|
+
plan: {
|
|
3550
|
+
kind: "worktree-cli",
|
|
3551
|
+
profilePlan,
|
|
3552
|
+
harness: options.harness,
|
|
3553
|
+
baseRef: options.baseRef ?? "HEAD",
|
|
3554
|
+
harnessTimeoutMs: options.harnessTimeoutMs ?? null,
|
|
3555
|
+
codexReproducible: options.codexReproducible === true,
|
|
3556
|
+
codexReadDeniedPaths: options.codexReadDeniedPaths ?? [],
|
|
3557
|
+
testCmd: options.testCmd ?? null,
|
|
3558
|
+
typecheckCmd: options.typecheckCmd ?? null,
|
|
3559
|
+
checkTimeoutMs: options.checkTimeoutMs ?? null,
|
|
3560
|
+
checkOutputCap: options.checkOutputCap ?? 16e3
|
|
3561
|
+
}
|
|
3562
|
+
}, {
|
|
3563
|
+
attemptId,
|
|
3564
|
+
binding: {
|
|
3565
|
+
repoRoot: options.repoRoot,
|
|
3566
|
+
runId,
|
|
3567
|
+
harness: options.harness,
|
|
3568
|
+
model: options.profile.model?.default ?? null,
|
|
3569
|
+
baseRef: options.baseRef ?? "HEAD"
|
|
3570
|
+
},
|
|
3571
|
+
descriptor: {
|
|
3572
|
+
kind: "worktree-cli-run",
|
|
3573
|
+
transport: "process",
|
|
3574
|
+
backend: options.harness
|
|
3575
|
+
}
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
/** A scoped execution task is authoritative. The configured prompt remains only as the
|
|
3579
|
+
* unambiguous direct-call default for existing `execute(undefined, signal)` consumers. */
|
|
3580
|
+
function executionTaskPrompt(task, configuredPrompt) {
|
|
3581
|
+
if (task === void 0) {
|
|
3582
|
+
if (configuredPrompt !== void 0) return configuredPrompt;
|
|
3583
|
+
throw new ValidationError("createWorktreeCliExecutor: execute task required when taskPrompt is not configured");
|
|
3584
|
+
}
|
|
3585
|
+
if (typeof task === "string") return task;
|
|
3586
|
+
try {
|
|
3587
|
+
const encoded = JSON.stringify(task);
|
|
3588
|
+
if (encoded !== void 0) return encoded;
|
|
3589
|
+
} catch (error) {
|
|
3590
|
+
throw new ValidationError("createWorktreeCliExecutor: execute task must be JSON-serializable", { cause: error });
|
|
3591
|
+
}
|
|
3592
|
+
throw new ValidationError("createWorktreeCliExecutor: execute task must be JSON-serializable");
|
|
3112
3593
|
}
|
|
3113
3594
|
/** Link two abort signals into one that fires when either does. Returns `undefined` when neither
|
|
3114
3595
|
* is present so the harness runner gets no signal at all. */
|
|
@@ -3195,7 +3676,7 @@ function zeroSpend() {
|
|
|
3195
3676
|
*/
|
|
3196
3677
|
const routerInlineExecutor = (spec, ctx) => {
|
|
3197
3678
|
const seam = readSeam(ctx, routerSeamKey, "router/inline");
|
|
3198
|
-
const model =
|
|
3679
|
+
const model = spec.profile.model?.default ?? seam.model;
|
|
3199
3680
|
if (!model) throw new ValidationError("routerInlineExecutor: no model — set RouterSeam.model or AgentProfile.model.default");
|
|
3200
3681
|
if (!seam.routerBaseUrl || !seam.routerKey) throw new ValidationError("routerInlineExecutor: RouterSeam.routerBaseUrl + routerKey required");
|
|
3201
3682
|
const controller = new AbortController();
|
|
@@ -3205,7 +3686,9 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3205
3686
|
abortIfSignalled();
|
|
3206
3687
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3207
3688
|
let artifact;
|
|
3208
|
-
|
|
3689
|
+
const executionId = ctx.node?.nodeId ?? `router-request-${randomUUID()}`;
|
|
3690
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
3691
|
+
return attestRuntimeOwnedExecutor({
|
|
3209
3692
|
runtime: "router",
|
|
3210
3693
|
async execute(task, signal) {
|
|
3211
3694
|
const messages = taskToMessages(task, spec);
|
|
@@ -3223,6 +3706,8 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3223
3706
|
output: r.usage.output
|
|
3224
3707
|
} : zeroTokenUsage(),
|
|
3225
3708
|
usd: r.costUsd ?? 0,
|
|
3709
|
+
...r.usage ? {} : { tokensKnown: false },
|
|
3710
|
+
...r.costUsd === void 0 ? { usdKnown: false } : {},
|
|
3226
3711
|
ms: Date.now() - started
|
|
3227
3712
|
};
|
|
3228
3713
|
const out = { content: r.content };
|
|
@@ -3247,7 +3732,35 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3247
3732
|
spent: artifact.spent
|
|
3248
3733
|
};
|
|
3249
3734
|
}
|
|
3250
|
-
}
|
|
3735
|
+
}, {
|
|
3736
|
+
effectiveProfile: spec.profile,
|
|
3737
|
+
backend: "router",
|
|
3738
|
+
model: {
|
|
3739
|
+
status: "known",
|
|
3740
|
+
id: model
|
|
3741
|
+
},
|
|
3742
|
+
execution: {
|
|
3743
|
+
kind: "request",
|
|
3744
|
+
id: executionId
|
|
3745
|
+
},
|
|
3746
|
+
materializer: "router-prompt-model",
|
|
3747
|
+
plan: {
|
|
3748
|
+
kind: "openai-chat-completion",
|
|
3749
|
+
model
|
|
3750
|
+
}
|
|
3751
|
+
}, {
|
|
3752
|
+
attemptId,
|
|
3753
|
+
binding: {
|
|
3754
|
+
endpoint: seam.routerBaseUrl,
|
|
3755
|
+
executionId,
|
|
3756
|
+
model
|
|
3757
|
+
},
|
|
3758
|
+
descriptor: {
|
|
3759
|
+
kind: "router-request",
|
|
3760
|
+
transport: "http",
|
|
3761
|
+
backend: "router"
|
|
3762
|
+
}
|
|
3763
|
+
});
|
|
3251
3764
|
};
|
|
3252
3765
|
const routerToolsSeamKey = "router-tools";
|
|
3253
3766
|
/**
|
|
@@ -3256,7 +3769,7 @@ const routerToolsSeamKey = "router-tools";
|
|
|
3256
3769
|
*/
|
|
3257
3770
|
const routerToolsInlineExecutor = (spec, ctx) => {
|
|
3258
3771
|
const seam = readSeam(ctx, routerToolsSeamKey, "router-tools");
|
|
3259
|
-
const model =
|
|
3772
|
+
const model = spec.profile.model?.default ?? seam.model;
|
|
3260
3773
|
if (!model) throw new ValidationError("routerToolsInlineExecutor: no model — set RouterToolsSeam.model or AgentProfile.model.default");
|
|
3261
3774
|
if (!seam.routerBaseUrl || !seam.routerKey) throw new ValidationError("routerToolsInlineExecutor: RouterToolsSeam.routerBaseUrl + routerKey required");
|
|
3262
3775
|
const maxTurns = seam.maxTurns ?? 200;
|
|
@@ -3268,13 +3781,16 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3268
3781
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3269
3782
|
const inbox = createInbox();
|
|
3270
3783
|
let artifact;
|
|
3271
|
-
|
|
3784
|
+
const executionId = ctx.node?.nodeId ?? `router-tools-run-${randomUUID()}`;
|
|
3785
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
3786
|
+
return attestRuntimeOwnedExecutor({
|
|
3272
3787
|
runtime: "router",
|
|
3273
3788
|
deliver: (m) => inbox.deliver(m),
|
|
3274
3789
|
async execute(task, signal) {
|
|
3275
3790
|
const started = Date.now();
|
|
3276
3791
|
const messages = [...taskToMessages(task, spec)];
|
|
3277
3792
|
const tokens = zeroTokenUsage();
|
|
3793
|
+
let tokensKnown = true;
|
|
3278
3794
|
let turns = 0;
|
|
3279
3795
|
let lastText = "";
|
|
3280
3796
|
const flush = () => {
|
|
@@ -3325,7 +3841,7 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3325
3841
|
if (u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number") {
|
|
3326
3842
|
tokens.input += u.prompt_tokens;
|
|
3327
3843
|
tokens.output += u.completion_tokens;
|
|
3328
|
-
}
|
|
3844
|
+
} else tokensKnown = false;
|
|
3329
3845
|
const msg = data.choices?.[0]?.message;
|
|
3330
3846
|
if (msg?.content) lastText = msg.content;
|
|
3331
3847
|
const toolCalls = msg?.tool_calls ?? [];
|
|
@@ -3387,11 +3903,14 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3387
3903
|
} catch {}
|
|
3388
3904
|
}
|
|
3389
3905
|
}
|
|
3390
|
-
const
|
|
3906
|
+
const priced = isModelPriced(model);
|
|
3907
|
+
const usd = priced ? estimateCost(tokens.input, tokens.output, model) : 0;
|
|
3391
3908
|
const spent = {
|
|
3392
3909
|
iterations: turns,
|
|
3393
3910
|
tokens,
|
|
3911
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
3394
3912
|
usd,
|
|
3913
|
+
...!priced || !tokensKnown ? { usdKnown: false } : {},
|
|
3395
3914
|
ms: Date.now() - started
|
|
3396
3915
|
};
|
|
3397
3916
|
const out = { content: lastText };
|
|
@@ -3416,7 +3935,37 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3416
3935
|
spent: artifact.spent
|
|
3417
3936
|
};
|
|
3418
3937
|
}
|
|
3419
|
-
}
|
|
3938
|
+
}, {
|
|
3939
|
+
effectiveProfile: spec.profile,
|
|
3940
|
+
backend: "router-tools",
|
|
3941
|
+
model: {
|
|
3942
|
+
status: "known",
|
|
3943
|
+
id: model
|
|
3944
|
+
},
|
|
3945
|
+
execution: {
|
|
3946
|
+
kind: "run",
|
|
3947
|
+
id: executionId
|
|
3948
|
+
},
|
|
3949
|
+
materializer: "router-tools-prompt-model",
|
|
3950
|
+
plan: {
|
|
3951
|
+
kind: "openai-tool-loop",
|
|
3952
|
+
model,
|
|
3953
|
+
maxTurns,
|
|
3954
|
+
tools: seam.tools
|
|
3955
|
+
}
|
|
3956
|
+
}, {
|
|
3957
|
+
attemptId,
|
|
3958
|
+
binding: {
|
|
3959
|
+
endpoint: seam.routerBaseUrl,
|
|
3960
|
+
executionId,
|
|
3961
|
+
model
|
|
3962
|
+
},
|
|
3963
|
+
descriptor: {
|
|
3964
|
+
kind: "router-tool-loop",
|
|
3965
|
+
transport: "http",
|
|
3966
|
+
backend: "router-tools"
|
|
3967
|
+
}
|
|
3968
|
+
});
|
|
3420
3969
|
};
|
|
3421
3970
|
/**
|
|
3422
3971
|
* COMPOSES `runAgentRounds` as a single-task leaf: one box, a refine driver bounded to
|
|
@@ -3445,11 +3994,48 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3445
3994
|
abortIfSignalled();
|
|
3446
3995
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3447
3996
|
let artifact;
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3997
|
+
const executionId = ctx.node?.nodeId ?? `sandbox-run-${randomUUID()}`;
|
|
3998
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
3999
|
+
const sandboxMaterialization = {
|
|
4000
|
+
effectiveProfile: spec.profile,
|
|
4001
|
+
backend: harness,
|
|
4002
|
+
model: spec.profile.model?.default ? {
|
|
4003
|
+
status: "known",
|
|
4004
|
+
id: spec.profile.model.default
|
|
4005
|
+
} : {
|
|
4006
|
+
status: "unknown",
|
|
4007
|
+
reason: "sandbox harness selected its default model"
|
|
4008
|
+
},
|
|
4009
|
+
execution: {
|
|
4010
|
+
kind: "run",
|
|
4011
|
+
id: executionId
|
|
4012
|
+
},
|
|
4013
|
+
materializer: "sandbox-agent-profile",
|
|
4014
|
+
plan: {
|
|
4015
|
+
kind: "sandbox-agent-rounds",
|
|
4016
|
+
harness,
|
|
4017
|
+
maxIterations,
|
|
4018
|
+
steering: seam.steering !== void 0
|
|
4019
|
+
}
|
|
4020
|
+
};
|
|
4021
|
+
const sandboxBinding = {
|
|
4022
|
+
attemptId,
|
|
4023
|
+
binding: {
|
|
4024
|
+
executionId,
|
|
4025
|
+
harness,
|
|
4026
|
+
model: spec.profile.model?.default ?? null
|
|
4027
|
+
},
|
|
4028
|
+
descriptor: {
|
|
4029
|
+
kind: "sandbox-run",
|
|
4030
|
+
transport: "sandbox",
|
|
4031
|
+
backend: harness
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
if (seam.steering) {
|
|
4035
|
+
const inbox = createInbox();
|
|
4036
|
+
const session = createSteerableSandboxSession({
|
|
4037
|
+
controller,
|
|
4038
|
+
profile: spec.profile,
|
|
3453
4039
|
harness,
|
|
3454
4040
|
sandboxClient: seam.sandboxClient,
|
|
3455
4041
|
inbox,
|
|
@@ -3459,7 +4045,7 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3459
4045
|
...Object.keys(traceEnv).length > 0 ? { traceEnv } : {},
|
|
3460
4046
|
contentRef
|
|
3461
4047
|
});
|
|
3462
|
-
return {
|
|
4048
|
+
return attestRuntimeOwnedExecutor({
|
|
3463
4049
|
runtime: "sandbox",
|
|
3464
4050
|
deliver: (m) => inbox.deliver(m),
|
|
3465
4051
|
progress: () => session.progress(),
|
|
@@ -3477,13 +4063,13 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3477
4063
|
if (!a) throw new ValidationError("sandboxExecutor(steering): resultArtifact() read before stream drained");
|
|
3478
4064
|
return a;
|
|
3479
4065
|
}
|
|
3480
|
-
};
|
|
4066
|
+
}, sandboxMaterialization, sandboxBinding);
|
|
3481
4067
|
}
|
|
3482
4068
|
const output = { parse(events) {
|
|
3483
4069
|
return { events };
|
|
3484
4070
|
} };
|
|
3485
4071
|
const driver = singleShotDriver(maxIterations);
|
|
3486
|
-
return {
|
|
4072
|
+
return attestRuntimeOwnedExecutor({
|
|
3487
4073
|
runtime: "sandbox",
|
|
3488
4074
|
execute(task, signal) {
|
|
3489
4075
|
return streamSandboxLeaf({
|
|
@@ -3511,7 +4097,7 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3511
4097
|
if (!artifact) throw new ValidationError("sandboxExecutor: resultArtifact() read before stream drained");
|
|
3512
4098
|
return artifact;
|
|
3513
4099
|
}
|
|
3514
|
-
};
|
|
4100
|
+
}, sandboxMaterialization, sandboxBinding);
|
|
3515
4101
|
};
|
|
3516
4102
|
async function* streamSandboxLeaf(args) {
|
|
3517
4103
|
const linked = new AbortController();
|
|
@@ -3547,8 +4133,10 @@ async function* streamSandboxLeaf(args) {
|
|
|
3547
4133
|
};
|
|
3548
4134
|
try {
|
|
3549
4135
|
const result = await runAgentRounds(loopOptions);
|
|
4136
|
+
const failure = failedRound(result);
|
|
4137
|
+
if (failure) throw failure;
|
|
3550
4138
|
const out = result.winner?.output ?? { events: [] };
|
|
3551
|
-
const verdict = result.winner?.verdict;
|
|
4139
|
+
const verdict = result.winner?.verdict ?? leafVerdict(result);
|
|
3552
4140
|
const spent = {
|
|
3553
4141
|
iterations: result.iterations.length,
|
|
3554
4142
|
tokens: {
|
|
@@ -3582,11 +4170,39 @@ async function* streamSandboxLeaf(args) {
|
|
|
3582
4170
|
args.controller.signal.removeEventListener("abort", cascade);
|
|
3583
4171
|
}
|
|
3584
4172
|
}
|
|
4173
|
+
/** The loop's own failure, when NO iteration produced an output: the first error it
|
|
4174
|
+
* recorded, renamed so the settled worker names the leaf it died in. `undefined`
|
|
4175
|
+
* when any iteration produced an output — a partly-failed round still has material
|
|
4176
|
+
* to settle on. */
|
|
4177
|
+
function failedRound(result) {
|
|
4178
|
+
if (result.iterations.length === 0) return void 0;
|
|
4179
|
+
if (result.iterations.some((iteration) => iteration.output !== void 0)) return void 0;
|
|
4180
|
+
const first = result.iterations.find((iteration) => iteration.error)?.error;
|
|
4181
|
+
if (!first) return void 0;
|
|
4182
|
+
return new Error(`sandboxExecutor: agent round failed — ${first.message}`, { cause: first });
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* The leaf's OWN verdict, for a round the loop scored no validator against.
|
|
4186
|
+
*
|
|
4187
|
+
* `settled ⟺ delivered` is written by the completion oracle, and a caller that
|
|
4188
|
+
* passes one keeps it: `gateOnDeliverable` wraps this executor and overrides
|
|
4189
|
+
* `valid` from its check. This is the sandbox backend's structural answer for a
|
|
4190
|
+
* run with no oracle at all — without it nothing ever writes `valid`, no settled
|
|
4191
|
+
* child is ever DELIVERED, and the finalizer has nothing to select no matter how
|
|
4192
|
+
* well the worker ran. Structural, never self-reported: the harness completed a
|
|
4193
|
+
* round and returned an output artifact, or it did not.
|
|
4194
|
+
*/
|
|
4195
|
+
function leafVerdict(result) {
|
|
4196
|
+
if (result.winner?.output === void 0) return void 0;
|
|
4197
|
+
return {
|
|
4198
|
+
valid: true,
|
|
4199
|
+
score: 1
|
|
4200
|
+
};
|
|
4201
|
+
}
|
|
3585
4202
|
/**
|
|
3586
4203
|
* Spawns a subprocess (`bin` + `args`). It cannot account tokens, so it is
|
|
3587
|
-
* `budgetExempt: true`:
|
|
3588
|
-
*
|
|
3589
|
-
* resolver/equal-k path checks `budgetExempt`). teardown is SIGTERM → SIGKILL
|
|
4204
|
+
* `budgetExempt: true`: it remains usable as a direct executor, while budgeted supervision
|
|
4205
|
+
* refuses it before process execution because the CLI exposes no usage receipt. teardown is SIGTERM → SIGKILL
|
|
3590
4206
|
* with a grace window. Streaming: yields one `iteration` event on clean exit.
|
|
3591
4207
|
*/
|
|
3592
4208
|
const cliExecutor = (_spec, ctx) => {
|
|
@@ -3601,7 +4217,9 @@ const cliExecutor = (_spec, ctx) => {
|
|
|
3601
4217
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3602
4218
|
let proc;
|
|
3603
4219
|
let artifact;
|
|
3604
|
-
|
|
4220
|
+
const executionId = ctx.node?.nodeId ?? `cli-process-${randomUUID()}`;
|
|
4221
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
4222
|
+
return attestRuntimeOwnedExecutor({
|
|
3605
4223
|
runtime: "cli",
|
|
3606
4224
|
budgetExempt: true,
|
|
3607
4225
|
execute(task, signal) {
|
|
@@ -3628,7 +4246,39 @@ const cliExecutor = (_spec, ctx) => {
|
|
|
3628
4246
|
if (!artifact) throw new ValidationError("cliExecutor: resultArtifact() read before stream drained");
|
|
3629
4247
|
return artifact;
|
|
3630
4248
|
}
|
|
3631
|
-
}
|
|
4249
|
+
}, {
|
|
4250
|
+
effectiveProfile: _spec.profile,
|
|
4251
|
+
backend: "cli",
|
|
4252
|
+
model: {
|
|
4253
|
+
status: "unknown",
|
|
4254
|
+
reason: "raw subprocess has no model identity contract"
|
|
4255
|
+
},
|
|
4256
|
+
execution: {
|
|
4257
|
+
kind: "process-attempt",
|
|
4258
|
+
id: executionId
|
|
4259
|
+
},
|
|
4260
|
+
materializer: "raw-cli-stdin",
|
|
4261
|
+
plan: {
|
|
4262
|
+
kind: "raw-cli-process",
|
|
4263
|
+
bin: seam.bin,
|
|
4264
|
+
args: seam.args ?? [],
|
|
4265
|
+
cwd: seam.cwd ?? null,
|
|
4266
|
+
envOverrides: seam.env ?? {},
|
|
4267
|
+
ambientEnvironment: "inherited"
|
|
4268
|
+
}
|
|
4269
|
+
}, {
|
|
4270
|
+
attemptId,
|
|
4271
|
+
binding: {
|
|
4272
|
+
executionId,
|
|
4273
|
+
bin: seam.bin,
|
|
4274
|
+
cwd: seam.cwd ?? null
|
|
4275
|
+
},
|
|
4276
|
+
descriptor: {
|
|
4277
|
+
kind: "cli-process",
|
|
4278
|
+
transport: "process",
|
|
4279
|
+
backend: "cli"
|
|
4280
|
+
}
|
|
4281
|
+
});
|
|
3632
4282
|
};
|
|
3633
4283
|
async function* streamCliLeaf(args) {
|
|
3634
4284
|
const prompt = taskToPrompt(args.task);
|
|
@@ -3717,33 +4367,34 @@ function killWithGrace(proc, grace) {
|
|
|
3717
4367
|
* - STEERABLE: the down-leg `inbox` is drained at each turn boundary; a queued
|
|
3718
4368
|
* steer becomes the next turn's prompt on the same session, and the worker can't
|
|
3719
4369
|
* settle while a steer it never read is pending (the sandbox/router contract).
|
|
3720
|
-
* - ABORT:
|
|
3721
|
-
*
|
|
4370
|
+
* - ABORT: reader abort only detaches HTTP. Interrupt/teardown then call the
|
|
4371
|
+
* bridge's explicit cancel operation and wait for the owned run to terminate.
|
|
3722
4372
|
*
|
|
3723
4373
|
* Reports REAL usage when the bridge surfaces it, never a fabricated cost.
|
|
3724
4374
|
*/
|
|
3725
|
-
/** Resolve the bridge wire model for this spawn
|
|
3726
|
-
*
|
|
3727
|
-
|
|
3728
|
-
function bridgeCellModel(seamModel, ctx) {
|
|
4375
|
+
/** Resolve the bridge wire model for this spawn. Per-create matrix settings win, then the
|
|
4376
|
+
* canonical profile's harness/model preferences, then the bridge's configured fallback. */
|
|
4377
|
+
function bridgeCellModel(seamModel, ctx, profile) {
|
|
3729
4378
|
const backend = ctx.seams.createOptions?.backend;
|
|
3730
|
-
const
|
|
3731
|
-
const
|
|
4379
|
+
const profileHarness = profile.harness === "cli-base" ? void 0 : profile.harness;
|
|
4380
|
+
const harness = backend?.type ?? profileHarness;
|
|
4381
|
+
const model = backend?.model?.model ?? profile.model?.default;
|
|
3732
4382
|
if (!harness && !model) return seamModel;
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
return m.startsWith(`${h}/`) ? m : `${h}/${m}`;
|
|
4383
|
+
if (!harness) return model;
|
|
4384
|
+
if (model) return model.startsWith(`${harness}/`) ? model : `${harness}/${model}`;
|
|
4385
|
+
return seamModel?.startsWith(`${harness}/`) ? seamModel : void 0;
|
|
3737
4386
|
}
|
|
3738
4387
|
const bridgeExecutor = (spec, ctx) => {
|
|
3739
4388
|
const base = readSeam(ctx, bridgeSeamKey, "bridge");
|
|
4389
|
+
const effectiveProfile = agentProfileSchema.parse(mergeAgentProfiles(spec.profile, base.agentProfile) ?? spec.profile);
|
|
3740
4390
|
const seam = {
|
|
3741
4391
|
...base,
|
|
3742
|
-
model: bridgeCellModel(base.model, ctx)
|
|
4392
|
+
model: bridgeCellModel(base.model, ctx, effectiveProfile)
|
|
3743
4393
|
};
|
|
3744
|
-
if (!seam.bridgeUrl || !seam.bridgeBearer || !seam.model) throw new ValidationError("bridgeExecutor:
|
|
4394
|
+
if (!seam.bridgeUrl || !seam.bridgeBearer || !seam.model) throw new ValidationError("bridgeExecutor: bridgeUrl + bridgeBearer and a profile or bridge model are required");
|
|
3745
4395
|
const maxTurns = seam.maxTurns ?? 200;
|
|
3746
4396
|
const sessionId = seam.sessionId ?? `bridge-${spec.profile.name ?? "worker"}-${randomUUID()}`;
|
|
4397
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(sessionId);
|
|
3747
4398
|
const controller = new AbortController();
|
|
3748
4399
|
const abortIfSignalled = () => {
|
|
3749
4400
|
if (ctx.signal.aborted) controller.abort();
|
|
@@ -3752,27 +4403,31 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
3752
4403
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3753
4404
|
const inbox = createInbox();
|
|
3754
4405
|
let artifact;
|
|
3755
|
-
|
|
4406
|
+
const activeRuns = /* @__PURE__ */ new Map();
|
|
4407
|
+
return attestRuntimeOwnedExecutor({
|
|
3756
4408
|
runtime: "cli",
|
|
3757
4409
|
deliver: (m) => inbox.deliver(m),
|
|
3758
4410
|
execute(task, signal) {
|
|
3759
4411
|
return streamBridgeSession({
|
|
3760
4412
|
task,
|
|
3761
4413
|
signal,
|
|
3762
|
-
|
|
4414
|
+
profile: effectiveProfile,
|
|
3763
4415
|
seam,
|
|
3764
4416
|
sessionId,
|
|
3765
4417
|
maxTurns,
|
|
3766
4418
|
inbox,
|
|
3767
4419
|
controller,
|
|
4420
|
+
activeRuns,
|
|
3768
4421
|
onArtifact: (a) => {
|
|
3769
4422
|
artifact = a;
|
|
3770
4423
|
}
|
|
3771
4424
|
});
|
|
3772
4425
|
},
|
|
3773
|
-
teardown(
|
|
4426
|
+
async teardown(grace) {
|
|
3774
4427
|
controller.abort();
|
|
3775
|
-
|
|
4428
|
+
const remaining = [...activeRuns.values()].filter((run) => !run.terminal);
|
|
4429
|
+
if (remaining.length === 0) return { destroyed: true };
|
|
4430
|
+
return { destroyed: (await Promise.all(remaining.map((run) => cancelBridgeRunToTerminal(seam, run, grace)))).every(Boolean) };
|
|
3776
4431
|
},
|
|
3777
4432
|
resultArtifact() {
|
|
3778
4433
|
if (!artifact) throw new ValidationError("bridgeExecutor: resultArtifact() read before stream drained");
|
|
@@ -3781,7 +4436,40 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
3781
4436
|
spent: artifact.spent
|
|
3782
4437
|
};
|
|
3783
4438
|
}
|
|
3784
|
-
}
|
|
4439
|
+
}, {
|
|
4440
|
+
effectiveProfile,
|
|
4441
|
+
backend: "bridge",
|
|
4442
|
+
model: {
|
|
4443
|
+
status: "known",
|
|
4444
|
+
id: seam.model
|
|
4445
|
+
},
|
|
4446
|
+
execution: {
|
|
4447
|
+
kind: "session",
|
|
4448
|
+
id: sessionId
|
|
4449
|
+
},
|
|
4450
|
+
materializer: "cli-bridge-agent-profile",
|
|
4451
|
+
plan: {
|
|
4452
|
+
kind: "cli-bridge-session",
|
|
4453
|
+
cwd: seam.cwd ?? null,
|
|
4454
|
+
maxTurns,
|
|
4455
|
+
timeoutMs: seam.timeoutMs ?? null,
|
|
4456
|
+
streaming: true
|
|
4457
|
+
}
|
|
4458
|
+
}, {
|
|
4459
|
+
attemptId,
|
|
4460
|
+
binding: {
|
|
4461
|
+
bridgeUrl: seam.bridgeUrl,
|
|
4462
|
+
cwd: seam.cwd ?? null,
|
|
4463
|
+
effectiveProfile,
|
|
4464
|
+
model: seam.model,
|
|
4465
|
+
sessionId
|
|
4466
|
+
},
|
|
4467
|
+
descriptor: {
|
|
4468
|
+
kind: "bridge-session",
|
|
4469
|
+
transport: "http",
|
|
4470
|
+
backend: "bridge"
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
3785
4473
|
};
|
|
3786
4474
|
/**
|
|
3787
4475
|
* One resumable cli-bridge session, run as a streamed turn loop. Turn 0 sends the
|
|
@@ -3795,12 +4483,13 @@ async function* streamBridgeSession(args) {
|
|
|
3795
4483
|
const started = Date.now();
|
|
3796
4484
|
const external = mergeAbortSignals(args.signal, args.controller.signal);
|
|
3797
4485
|
const tokens = zeroTokenUsage();
|
|
4486
|
+
let tokensKnown = true;
|
|
3798
4487
|
let usd = 0;
|
|
4488
|
+
let usdKnown = true;
|
|
3799
4489
|
let turns = 0;
|
|
3800
4490
|
let lastText = "";
|
|
3801
4491
|
const toolCalls = [];
|
|
3802
4492
|
let nextPrompt = taskToPrompt(args.task);
|
|
3803
|
-
const system = args.spec.profile.prompt?.systemPrompt;
|
|
3804
4493
|
for (let t = 0; t < args.maxTurns; t += 1) {
|
|
3805
4494
|
const pending = inbox.drain();
|
|
3806
4495
|
if (pending.length) {
|
|
@@ -3809,10 +4498,6 @@ async function* streamBridgeSession(args) {
|
|
|
3809
4498
|
}
|
|
3810
4499
|
if (nextPrompt === void 0) break;
|
|
3811
4500
|
const messages = [];
|
|
3812
|
-
if (t === 0 && typeof system === "string" && system.length > 0) messages.push({
|
|
3813
|
-
role: "system",
|
|
3814
|
-
content: system
|
|
3815
|
-
});
|
|
3816
4501
|
messages.push({
|
|
3817
4502
|
role: "user",
|
|
3818
4503
|
content: nextPrompt
|
|
@@ -3824,45 +4509,46 @@ async function* streamBridgeSession(args) {
|
|
|
3824
4509
|
if (external.aborted) turnController.abort();
|
|
3825
4510
|
else external.addEventListener("abort", abortTurn);
|
|
3826
4511
|
interruptSig.addEventListener("abort", abortTurn, { once: true });
|
|
3827
|
-
|
|
4512
|
+
let timedOut = false;
|
|
4513
|
+
const timer = seam.timeoutMs ? setTimeout(() => {
|
|
4514
|
+
timedOut = true;
|
|
4515
|
+
abortTurn();
|
|
4516
|
+
}, seam.timeoutMs) : void 0;
|
|
3828
4517
|
const cleanup = () => {
|
|
3829
4518
|
external.removeEventListener("abort", abortTurn);
|
|
3830
4519
|
if (timer) clearTimeout(timer);
|
|
3831
4520
|
};
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
} catch (e) {
|
|
3848
|
-
cleanup();
|
|
3849
|
-
if (e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) continue;
|
|
3850
|
-
throw e;
|
|
3851
|
-
}
|
|
3852
|
-
if (!res.ok) {
|
|
3853
|
-
cleanup();
|
|
3854
|
-
throw new ValidationError(`bridgeExecutor: bridge ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
3855
|
-
}
|
|
3856
|
-
if (!res.body) {
|
|
3857
|
-
cleanup();
|
|
3858
|
-
throw new ValidationError("bridgeExecutor: bridge response had no body to stream");
|
|
3859
|
-
}
|
|
4521
|
+
const activeRun = {
|
|
4522
|
+
id: `bridge-run-${randomUUID()}`,
|
|
4523
|
+
lastEventId: 0,
|
|
4524
|
+
terminal: false
|
|
4525
|
+
};
|
|
4526
|
+
args.activeRuns.set(activeRun.id, activeRun);
|
|
4527
|
+
const requestBody = {
|
|
4528
|
+
model: seam.model,
|
|
4529
|
+
stream: true,
|
|
4530
|
+
run_id: activeRun.id,
|
|
4531
|
+
session_id: args.sessionId,
|
|
4532
|
+
...seam.cwd ? { cwd: seam.cwd } : {},
|
|
4533
|
+
agent_profile: args.profile,
|
|
4534
|
+
messages
|
|
4535
|
+
};
|
|
3860
4536
|
let turnText = "";
|
|
4537
|
+
let turnTokensKnown = false;
|
|
4538
|
+
let turnUsdKnown = false;
|
|
4539
|
+
let interrupted = false;
|
|
3861
4540
|
try {
|
|
3862
|
-
for await (const chunk of
|
|
4541
|
+
for await (const chunk of streamDurableBridgeRun({
|
|
4542
|
+
seam,
|
|
4543
|
+
sessionId: args.sessionId,
|
|
4544
|
+
body: requestBody,
|
|
4545
|
+
signal: turnController.signal,
|
|
4546
|
+
run: activeRun
|
|
4547
|
+
})) {
|
|
3863
4548
|
if (chunk.content) turnText += chunk.content;
|
|
3864
4549
|
if (chunk.toolCall) toolCalls.push(chunk.toolCall);
|
|
3865
4550
|
if (chunk.usage) {
|
|
4551
|
+
turnTokensKnown = true;
|
|
3866
4552
|
tokens.input += chunk.usage.input;
|
|
3867
4553
|
tokens.output += chunk.usage.output;
|
|
3868
4554
|
yield {
|
|
@@ -3871,26 +4557,43 @@ async function* streamBridgeSession(args) {
|
|
|
3871
4557
|
output: chunk.usage.output
|
|
3872
4558
|
};
|
|
3873
4559
|
}
|
|
3874
|
-
if (typeof chunk.cost === "number"
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
4560
|
+
if (typeof chunk.cost === "number") {
|
|
4561
|
+
turnUsdKnown = true;
|
|
4562
|
+
if (chunk.cost > 0) {
|
|
4563
|
+
usd += chunk.cost;
|
|
4564
|
+
yield {
|
|
4565
|
+
kind: "cost",
|
|
4566
|
+
usd: chunk.cost
|
|
4567
|
+
};
|
|
4568
|
+
}
|
|
3880
4569
|
}
|
|
3881
4570
|
}
|
|
4571
|
+
} catch (error) {
|
|
4572
|
+
if (interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) {
|
|
4573
|
+
if (!await cancelBridgeRunToTerminal(seam, activeRun, "infinity", external)) throw new ValidationError(`bridgeExecutor: interrupted run ${activeRun.id} did not reach terminal state`);
|
|
4574
|
+
interrupted = true;
|
|
4575
|
+
} else {
|
|
4576
|
+
if (timedOut && !activeRun.terminal) await requestBridgeRunCancellation(seam, activeRun, 0);
|
|
4577
|
+
throw error;
|
|
4578
|
+
}
|
|
3882
4579
|
} finally {
|
|
3883
4580
|
cleanup();
|
|
3884
4581
|
}
|
|
4582
|
+
if (interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) interrupted = true;
|
|
3885
4583
|
turns += 1;
|
|
4584
|
+
if (!turnTokensKnown) tokensKnown = false;
|
|
4585
|
+
if (!turnUsdKnown) usdKnown = false;
|
|
3886
4586
|
yield { kind: "iteration" };
|
|
3887
|
-
if (turnText) lastText = turnText;
|
|
4587
|
+
if (!interrupted && turnText) lastText = turnText;
|
|
4588
|
+
if (interrupted) continue;
|
|
3888
4589
|
if (inbox.pending() === 0) break;
|
|
3889
4590
|
}
|
|
3890
4591
|
const spent = {
|
|
3891
4592
|
iterations: turns,
|
|
3892
4593
|
tokens,
|
|
4594
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
3893
4595
|
usd,
|
|
4596
|
+
...usdKnown ? {} : { usdKnown: false },
|
|
3894
4597
|
ms: Date.now() - started
|
|
3895
4598
|
};
|
|
3896
4599
|
const out = {
|
|
@@ -3907,6 +4610,69 @@ async function* streamBridgeSession(args) {
|
|
|
3907
4610
|
spent
|
|
3908
4611
|
});
|
|
3909
4612
|
}
|
|
4613
|
+
const BRIDGE_MAX_RECONNECTS = 3;
|
|
4614
|
+
const BRIDGE_CANCEL_LONG_POLL_MS = 3e4;
|
|
4615
|
+
const BRIDGE_BRUTAL_KILL_WAIT_MS = 150;
|
|
4616
|
+
/**
|
|
4617
|
+
* Drain one server-owned bridge run. A transport loss replays from the last
|
|
4618
|
+
* contiguous event id under the SAME run id and request bytes. No unnumbered,
|
|
4619
|
+
* duplicate, or skipped event is accepted: an exact replay contract that
|
|
4620
|
+
* cannot prove continuity fails instead of returning a plausible partial answer.
|
|
4621
|
+
*/
|
|
4622
|
+
async function* streamDurableBridgeRun(args) {
|
|
4623
|
+
let reconnects = 0;
|
|
4624
|
+
let pendingUpstreamError;
|
|
4625
|
+
for (;;) {
|
|
4626
|
+
let res;
|
|
4627
|
+
try {
|
|
4628
|
+
res = await bridgeStreamPost(args.seam.bridgeUrl, {
|
|
4629
|
+
bearer: args.seam.bridgeBearer,
|
|
4630
|
+
sessionId: args.sessionId,
|
|
4631
|
+
runId: args.run.id,
|
|
4632
|
+
afterEventId: args.run.lastEventId,
|
|
4633
|
+
body: args.body,
|
|
4634
|
+
signal: args.signal
|
|
4635
|
+
});
|
|
4636
|
+
} catch (error) {
|
|
4637
|
+
if (args.signal.aborted) throw error;
|
|
4638
|
+
if (reconnects >= BRIDGE_MAX_RECONNECTS) throw new ValidationError(`bridgeExecutor: run ${args.run.id} disconnected before terminal acknowledgement after ${reconnects + 1} attempts: ${errorMessage$1(error)}`);
|
|
4639
|
+
reconnects += 1;
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
if (!res.ok) throw new ValidationError(`bridgeExecutor: bridge ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
4643
|
+
if (!res.body) throw new ValidationError("bridgeExecutor: bridge response had no body to stream");
|
|
4644
|
+
assertBridgeResponseIdentity(res, args.run);
|
|
4645
|
+
let sawDone = false;
|
|
4646
|
+
try {
|
|
4647
|
+
for await (const event of parseSseChatStream(res.body)) {
|
|
4648
|
+
if (event.kind === "done") {
|
|
4649
|
+
sawDone = true;
|
|
4650
|
+
break;
|
|
4651
|
+
}
|
|
4652
|
+
const expected = args.run.lastEventId + 1;
|
|
4653
|
+
if (event.id !== expected) throw new ValidationError(`bridgeExecutor: run ${args.run.id} replay gap: expected event ${expected}, received ${event.id}`);
|
|
4654
|
+
args.run.lastEventId = event.id;
|
|
4655
|
+
if (event.error) pendingUpstreamError = event.error;
|
|
4656
|
+
if (event.chunk) yield event.chunk;
|
|
4657
|
+
}
|
|
4658
|
+
} catch (error) {
|
|
4659
|
+
if (args.signal.aborted) throw error;
|
|
4660
|
+
if (error instanceof ValidationError) throw error;
|
|
4661
|
+
if (reconnects >= BRIDGE_MAX_RECONNECTS) throw new ValidationError(`bridgeExecutor: run ${args.run.id} stream disconnected before terminal acknowledgement after ${reconnects + 1} attempts: ${errorMessage$1(error)}`);
|
|
4662
|
+
reconnects += 1;
|
|
4663
|
+
continue;
|
|
4664
|
+
}
|
|
4665
|
+
if (sawDone) {
|
|
4666
|
+
args.run.terminal = true;
|
|
4667
|
+
if (pendingUpstreamError) throw pendingUpstreamError;
|
|
4668
|
+
return;
|
|
4669
|
+
}
|
|
4670
|
+
if (pendingUpstreamError) throw pendingUpstreamError;
|
|
4671
|
+
if (args.signal.aborted) throw new DOMException("bridgeExecutor: turn aborted", "AbortError");
|
|
4672
|
+
if (reconnects >= BRIDGE_MAX_RECONNECTS) throw new ValidationError(`bridgeExecutor: run ${args.run.id} ended without terminal acknowledgement after ${reconnects + 1} attempts`);
|
|
4673
|
+
reconnects += 1;
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
3910
4676
|
/**
|
|
3911
4677
|
* POST one streamed turn to the cli-bridge over the `node:http(s)` core client
|
|
3912
4678
|
* instead of global `fetch`. The bridge runs a harness CLI and streams SSE only
|
|
@@ -3934,15 +4700,22 @@ function bridgeStreamPost(url, args) {
|
|
|
3934
4700
|
"content-type": "application/json",
|
|
3935
4701
|
authorization: `Bearer ${args.bearer}`,
|
|
3936
4702
|
"x-session-id": args.sessionId,
|
|
4703
|
+
"x-run-id": args.runId,
|
|
4704
|
+
...args.afterEventId > 0 ? { "last-event-id": String(args.afterEventId) } : {},
|
|
3937
4705
|
"content-length": Buffer.byteLength(payload)
|
|
3938
4706
|
},
|
|
3939
4707
|
timeout: 0
|
|
3940
4708
|
}, (res) => {
|
|
4709
|
+
response = res;
|
|
4710
|
+
res.once("close", () => args.signal.removeEventListener("abort", onAbort));
|
|
3941
4711
|
const status = res.statusCode ?? 0;
|
|
4712
|
+
const ok = status >= 200 && status < 300;
|
|
4713
|
+
const body = Readable.toWeb(res);
|
|
3942
4714
|
resolve({
|
|
3943
|
-
ok
|
|
4715
|
+
ok,
|
|
3944
4716
|
status,
|
|
3945
|
-
|
|
4717
|
+
headers: res.headers,
|
|
4718
|
+
body,
|
|
3946
4719
|
text: async () => {
|
|
3947
4720
|
const chunks = [];
|
|
3948
4721
|
for await (const c of res) chunks.push(c);
|
|
@@ -3950,8 +4723,10 @@ function bridgeStreamPost(url, args) {
|
|
|
3950
4723
|
}
|
|
3951
4724
|
});
|
|
3952
4725
|
});
|
|
4726
|
+
let response;
|
|
3953
4727
|
const onAbort = () => {
|
|
3954
4728
|
req.destroy(new DOMException("bridgeExecutor: turn aborted", "AbortError"));
|
|
4729
|
+
if (response && "destroy" in response && typeof response.destroy === "function") response.destroy(new DOMException("bridgeExecutor: turn aborted", "AbortError"));
|
|
3955
4730
|
};
|
|
3956
4731
|
if (args.signal.aborted) onAbort();
|
|
3957
4732
|
else args.signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -3959,16 +4734,109 @@ function bridgeStreamPost(url, args) {
|
|
|
3959
4734
|
args.signal.removeEventListener("abort", onAbort);
|
|
3960
4735
|
reject(e);
|
|
3961
4736
|
});
|
|
3962
|
-
req.on("close", () =>
|
|
4737
|
+
req.on("close", () => {
|
|
4738
|
+
if (!response) args.signal.removeEventListener("abort", onAbort);
|
|
4739
|
+
});
|
|
3963
4740
|
req.write(payload);
|
|
3964
4741
|
req.end();
|
|
3965
4742
|
});
|
|
3966
4743
|
}
|
|
4744
|
+
function bridgeHeader(headers, name) {
|
|
4745
|
+
const raw = headers[name.toLowerCase()];
|
|
4746
|
+
if (Array.isArray(raw)) return raw.length === 1 ? raw[0] : void 0;
|
|
4747
|
+
return raw;
|
|
4748
|
+
}
|
|
4749
|
+
function assertBridgeResponseIdentity(response, run) {
|
|
4750
|
+
assertBridgeIdentityHeaders(response.headers, run);
|
|
4751
|
+
}
|
|
4752
|
+
function assertBridgeIdentityHeaders(headers, run) {
|
|
4753
|
+
const responseRunId = bridgeHeader(headers, "x-run-id");
|
|
4754
|
+
if (responseRunId !== run.id) throw new ValidationError(`bridgeExecutor: bridge run identity mismatch: expected ${run.id}, received ${responseRunId ?? "missing"}`);
|
|
4755
|
+
const digest = bridgeHeader(headers, "x-run-request-digest");
|
|
4756
|
+
if (!digest || !/^sha256:[a-f0-9]{64}$/u.test(digest)) throw new ValidationError("bridgeExecutor: bridge response omitted a valid request digest");
|
|
4757
|
+
if (run.requestDigest !== void 0 && run.requestDigest !== digest) throw new ValidationError(`bridgeExecutor: bridge request digest changed for run ${run.id}: expected ${run.requestDigest}, received ${digest}`);
|
|
4758
|
+
run.requestDigest = digest;
|
|
4759
|
+
}
|
|
4760
|
+
/** Explicitly cancel one server-owned run and long-poll for its terminal snapshot. */
|
|
4761
|
+
function bridgeCancelPost(seam, run, waitMs) {
|
|
4762
|
+
const target = new URL(`${seam.bridgeUrl.replace(/\/$/, "")}/v1/runs/${encodeURIComponent(run.id)}/cancel`);
|
|
4763
|
+
target.searchParams.set("wait_ms", String(waitMs));
|
|
4764
|
+
const requestFn = target.protocol === "https:" ? request$1 : request;
|
|
4765
|
+
return new Promise((resolve, reject) => {
|
|
4766
|
+
const req = requestFn(target, {
|
|
4767
|
+
method: "POST",
|
|
4768
|
+
headers: {
|
|
4769
|
+
authorization: `Bearer ${seam.bridgeBearer}`,
|
|
4770
|
+
"x-run-id": run.id,
|
|
4771
|
+
"content-length": "0"
|
|
4772
|
+
},
|
|
4773
|
+
timeout: 0
|
|
4774
|
+
}, (res) => {
|
|
4775
|
+
(async () => {
|
|
4776
|
+
const chunks = [];
|
|
4777
|
+
for await (const chunk of res) chunks.push(Buffer.from(chunk));
|
|
4778
|
+
resolve({
|
|
4779
|
+
status: res.statusCode ?? 0,
|
|
4780
|
+
headers: res.headers,
|
|
4781
|
+
text: Buffer.concat(chunks).toString("utf8")
|
|
4782
|
+
});
|
|
4783
|
+
})().catch(reject);
|
|
4784
|
+
});
|
|
4785
|
+
req.on("error", reject);
|
|
4786
|
+
req.end();
|
|
4787
|
+
});
|
|
4788
|
+
}
|
|
4789
|
+
async function requestBridgeRunCancellation(seam, run, waitMs) {
|
|
4790
|
+
if (run.terminal) return true;
|
|
4791
|
+
if (run.cancelInFlight) return run.cancelInFlight;
|
|
4792
|
+
const work = (async () => {
|
|
4793
|
+
const response = await bridgeCancelPost(seam, run, waitMs);
|
|
4794
|
+
if (response.status === 404) throw new ValidationError(`bridgeExecutor: bridge no longer knows run ${run.id}; terminal state is unproven`);
|
|
4795
|
+
if (response.status !== 200 && response.status !== 202) throw new ValidationError(`bridgeExecutor: cancel ${run.id} returned ${response.status}: ${response.text.slice(0, 300)}`);
|
|
4796
|
+
assertBridgeIdentityHeaders(response.headers, run);
|
|
4797
|
+
let parsed;
|
|
4798
|
+
try {
|
|
4799
|
+
parsed = JSON.parse(response.text);
|
|
4800
|
+
} catch {
|
|
4801
|
+
throw new ValidationError(`bridgeExecutor: cancel ${run.id} returned invalid JSON`);
|
|
4802
|
+
}
|
|
4803
|
+
if (parsed.run?.id !== run.id || parsed.run.requestDigest !== run.requestDigest || typeof parsed.terminal !== "boolean" || typeof parsed.run.terminal !== "boolean" || parsed.terminal !== parsed.run.terminal) throw new ValidationError(`bridgeExecutor: cancel ${run.id} returned an inconsistent terminal snapshot`);
|
|
4804
|
+
if (response.status === 200 && parsed.terminal === true) {
|
|
4805
|
+
run.terminal = true;
|
|
4806
|
+
return true;
|
|
4807
|
+
}
|
|
4808
|
+
if (response.status === 202 && parsed.terminal === false) return false;
|
|
4809
|
+
throw new ValidationError(`bridgeExecutor: cancel ${run.id} status ${response.status} disagreed with terminal=${String(parsed.terminal)}`);
|
|
4810
|
+
})();
|
|
4811
|
+
run.cancelInFlight = work;
|
|
4812
|
+
try {
|
|
4813
|
+
return await work;
|
|
4814
|
+
} finally {
|
|
4815
|
+
if (run.cancelInFlight === work) run.cancelInFlight = void 0;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
async function cancelBridgeRunToTerminal(seam, run, grace, stopSignal) {
|
|
4819
|
+
if (run.terminal) return true;
|
|
4820
|
+
const deadline = grace === "infinity" ? void 0 : Date.now() + (grace === "brutalKill" ? BRIDGE_BRUTAL_KILL_WAIT_MS : Math.max(0, grace));
|
|
4821
|
+
let first = true;
|
|
4822
|
+
for (;;) {
|
|
4823
|
+
const remaining = deadline === void 0 ? BRIDGE_CANCEL_LONG_POLL_MS : deadline - Date.now();
|
|
4824
|
+
if (!first && remaining <= 0) return false;
|
|
4825
|
+
if (!first && stopSignal?.aborted) return false;
|
|
4826
|
+
if (await requestBridgeRunCancellation(seam, run, Math.max(0, Math.min(stopSignal ? 1e3 : BRIDGE_CANCEL_LONG_POLL_MS, deadline === void 0 ? remaining : Math.max(0, remaining))))) return true;
|
|
4827
|
+
first = false;
|
|
4828
|
+
if (deadline !== void 0 && Date.now() >= deadline) return false;
|
|
4829
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
function errorMessage$1(error) {
|
|
4833
|
+
return error instanceof Error ? error.message : String(error);
|
|
4834
|
+
}
|
|
3967
4835
|
/**
|
|
3968
4836
|
* Parse cli-bridge's OpenAI-compatible SSE stream into normalized chunks. Each
|
|
3969
|
-
* `data:` line is an OpenAI chat-completion chunk (`choices[].delta`)
|
|
3970
|
-
*
|
|
3971
|
-
*
|
|
4837
|
+
* `data:` line is an OpenAI chat-completion chunk (`choices[].delta`). Every
|
|
4838
|
+
* run-owned frame, including an id-only comment, is returned so the caller can
|
|
4839
|
+
* prove a contiguous replay sequence. Transport keepalives have no id and are ignored.
|
|
3972
4840
|
*/
|
|
3973
4841
|
async function* parseSseChatStream(body) {
|
|
3974
4842
|
const reader = body.getReader();
|
|
@@ -3979,18 +4847,18 @@ async function* parseSseChatStream(body) {
|
|
|
3979
4847
|
const { done, value } = await reader.read();
|
|
3980
4848
|
if (done) break;
|
|
3981
4849
|
buf += decoder.decode(value, { stream: true });
|
|
3982
|
-
let
|
|
3983
|
-
while (
|
|
3984
|
-
const frame = buf.slice(0,
|
|
3985
|
-
buf = buf.slice(
|
|
3986
|
-
const
|
|
3987
|
-
if (
|
|
3988
|
-
|
|
3989
|
-
sep = buf.indexOf("\n\n");
|
|
4850
|
+
let separator = /\r?\n\r?\n/u.exec(buf);
|
|
4851
|
+
while (separator) {
|
|
4852
|
+
const frame = buf.slice(0, separator.index);
|
|
4853
|
+
buf = buf.slice(separator.index + separator[0].length);
|
|
4854
|
+
const event = parseSseFrame(frame);
|
|
4855
|
+
if (event) yield event;
|
|
4856
|
+
separator = /\r?\n\r?\n/u.exec(buf);
|
|
3990
4857
|
}
|
|
3991
4858
|
}
|
|
4859
|
+
buf += decoder.decode();
|
|
3992
4860
|
const tail = parseSseStreamTail(buf);
|
|
3993
|
-
if (tail !== void 0
|
|
4861
|
+
if (tail !== void 0) yield tail;
|
|
3994
4862
|
} finally {
|
|
3995
4863
|
reader.releaseLock();
|
|
3996
4864
|
}
|
|
@@ -4012,25 +4880,41 @@ function parseSseStreamTail(buf) {
|
|
|
4012
4880
|
}
|
|
4013
4881
|
if (parsed.error) throw new ValidationError(`bridgeExecutor: bridge upstream error: ${parsed.error.message ?? parsed.error.type ?? "unknown"}`);
|
|
4014
4882
|
}
|
|
4015
|
-
/** Parse one SSE frame
|
|
4016
|
-
* or undefined (comment/keepalive/empty). */
|
|
4883
|
+
/** Parse one SSE frame into a numbered run event, terminal marker, or unnumbered keepalive. */
|
|
4017
4884
|
function parseSseFrame(frame) {
|
|
4018
4885
|
const dataLines = [];
|
|
4886
|
+
let id;
|
|
4019
4887
|
for (const rawLine of frame.split("\n")) {
|
|
4020
4888
|
const line = rawLine.replace(/\r$/, "");
|
|
4021
4889
|
if (!line || line.startsWith(":")) continue;
|
|
4890
|
+
if (line.startsWith("id:")) {
|
|
4891
|
+
const rawId = line.slice(3).trim();
|
|
4892
|
+
if (!/^[1-9][0-9]*$/u.test(rawId)) throw new ValidationError(`bridgeExecutor: invalid SSE event id ${JSON.stringify(rawId)}`);
|
|
4893
|
+
const parsedId = Number(rawId);
|
|
4894
|
+
if (!Number.isSafeInteger(parsedId)) throw new ValidationError(`bridgeExecutor: SSE event id exceeds safe integer range`);
|
|
4895
|
+
id = parsedId;
|
|
4896
|
+
continue;
|
|
4897
|
+
}
|
|
4022
4898
|
if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
4023
4899
|
}
|
|
4024
|
-
if (dataLines.length === 0) return void 0
|
|
4900
|
+
if (dataLines.length === 0) return id === void 0 ? void 0 : {
|
|
4901
|
+
kind: "event",
|
|
4902
|
+
id
|
|
4903
|
+
};
|
|
4025
4904
|
const data = dataLines.join("\n");
|
|
4026
|
-
if (data === "[DONE]") return "done";
|
|
4905
|
+
if (data === "[DONE]") return { kind: "done" };
|
|
4027
4906
|
let parsed;
|
|
4028
4907
|
try {
|
|
4029
4908
|
parsed = JSON.parse(data);
|
|
4030
4909
|
} catch {
|
|
4031
|
-
|
|
4910
|
+
throw new ValidationError("bridgeExecutor: bridge emitted a non-JSON SSE data frame");
|
|
4032
4911
|
}
|
|
4033
|
-
if (
|
|
4912
|
+
if (id === void 0) throw new ValidationError("bridgeExecutor: bridge emitted an unnumbered run event");
|
|
4913
|
+
if (parsed.error) return {
|
|
4914
|
+
kind: "event",
|
|
4915
|
+
id,
|
|
4916
|
+
error: new ValidationError(`bridgeExecutor: bridge stream error: ${parsed.error.message ?? parsed.error.type ?? "unknown"}`)
|
|
4917
|
+
};
|
|
4034
4918
|
const out = {};
|
|
4035
4919
|
const choice = parsed.choices?.[0];
|
|
4036
4920
|
const content = choice?.delta?.content ?? choice?.message?.content;
|
|
@@ -4043,7 +4927,11 @@ function parseSseFrame(frame) {
|
|
|
4043
4927
|
output: u.completion_tokens ?? 0
|
|
4044
4928
|
};
|
|
4045
4929
|
if (typeof u?.cost === "number") out.cost = u.cost;
|
|
4046
|
-
return
|
|
4930
|
+
return {
|
|
4931
|
+
kind: "event",
|
|
4932
|
+
id,
|
|
4933
|
+
...Object.keys(out).length > 0 ? { chunk: out } : {}
|
|
4934
|
+
};
|
|
4047
4935
|
}
|
|
4048
4936
|
function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
4049
4937
|
const bridge = seam.bridge;
|
|
@@ -4051,6 +4939,9 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4051
4939
|
if (!bridge.bridgeUrl || !bridge.bridgeBearer) throw new ValidationError("cliWorktreeExecutor: bridge.bridgeUrl + bridge.bridgeBearer required");
|
|
4052
4940
|
const runId = seam.runId ?? randomUUID();
|
|
4053
4941
|
const sessionId = bridge.sessionId ?? `bridge-worktree-${runId}`;
|
|
4942
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(runId);
|
|
4943
|
+
const effectiveProfile = agentProfileSchema.parse(mergeAgentProfiles(spec.profile, bridge.agentProfile) ?? spec.profile);
|
|
4944
|
+
const model = bridgeCellModel(bridge.model, ctx, effectiveProfile);
|
|
4054
4945
|
const controller = new AbortController();
|
|
4055
4946
|
const pending = [];
|
|
4056
4947
|
let inner;
|
|
@@ -4075,11 +4966,11 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4075
4966
|
}
|
|
4076
4967
|
pending.push(msg);
|
|
4077
4968
|
};
|
|
4078
|
-
return {
|
|
4969
|
+
return attestRuntimeOwnedExecutor({
|
|
4079
4970
|
runtime: "cli",
|
|
4080
4971
|
budgetExempt: seam.budgetExempt ?? false,
|
|
4081
4972
|
deliver,
|
|
4082
|
-
execute(
|
|
4973
|
+
execute(task, signal) {
|
|
4083
4974
|
return (async function* bridgeWorktreeStream() {
|
|
4084
4975
|
const started = Date.now();
|
|
4085
4976
|
const linked = mergeAbortSignals(signal, controller.signal);
|
|
@@ -4095,10 +4986,10 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4095
4986
|
const bridgeSeam = {
|
|
4096
4987
|
bridgeUrl: bridge.bridgeUrl,
|
|
4097
4988
|
bridgeBearer: bridge.bridgeBearer,
|
|
4098
|
-
model: resolveBridgeWorktreeModel(spec, bridge),
|
|
4099
4989
|
cwd: worktree.path,
|
|
4100
4990
|
sessionId,
|
|
4101
|
-
...bridge.
|
|
4991
|
+
...bridge.model ? { model: bridge.model } : {},
|
|
4992
|
+
...bridge.agentProfile ? { agentProfile: bridge.agentProfile } : {},
|
|
4102
4993
|
...bridge.timeoutMs !== void 0 ? { timeoutMs: bridge.timeoutMs } : {},
|
|
4103
4994
|
...bridge.maxTurns !== void 0 ? { maxTurns: bridge.maxTurns } : {}
|
|
4104
4995
|
};
|
|
@@ -4112,8 +5003,8 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4112
5003
|
};
|
|
4113
5004
|
inner = bridgeExecutor(spec, bridgeCtx);
|
|
4114
5005
|
for (const msg of pending.splice(0)) inner.deliver?.(msg);
|
|
4115
|
-
const run = inner.execute(
|
|
4116
|
-
if (isAsyncIterable(run)) {
|
|
5006
|
+
const run = inner.execute(task, linked);
|
|
5007
|
+
if (isAsyncIterable$1(run)) {
|
|
4117
5008
|
for await (const event of run) yield event;
|
|
4118
5009
|
bridgeArtifact = inner.resultArtifact();
|
|
4119
5010
|
} else bridgeArtifact = await run;
|
|
@@ -4179,13 +5070,48 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4179
5070
|
if (!artifact) throw new ValidationError("cliWorktreeExecutor: bridge resultArtifact() read before stream drained");
|
|
4180
5071
|
return artifact;
|
|
4181
5072
|
}
|
|
4182
|
-
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
5073
|
+
}, {
|
|
5074
|
+
effectiveProfile,
|
|
5075
|
+
backend: "bridge-worktree",
|
|
5076
|
+
model: model ? {
|
|
5077
|
+
status: "known",
|
|
5078
|
+
id: model
|
|
5079
|
+
} : {
|
|
5080
|
+
status: "unknown",
|
|
5081
|
+
reason: "bridge worktree profile did not select a model"
|
|
5082
|
+
},
|
|
5083
|
+
execution: {
|
|
5084
|
+
kind: "worktree-session",
|
|
5085
|
+
id: `${runId}:${sessionId}`
|
|
5086
|
+
},
|
|
5087
|
+
materializer: "bridge-worktree-agent-profile",
|
|
5088
|
+
plan: {
|
|
5089
|
+
kind: "bridge-worktree-session",
|
|
5090
|
+
runId,
|
|
5091
|
+
sessionId,
|
|
5092
|
+
baseRef: seam.baseRef ?? "HEAD",
|
|
5093
|
+
model: model ?? null,
|
|
5094
|
+
testCmd: seam.testCmd ?? null,
|
|
5095
|
+
typecheckCmd: seam.typecheckCmd ?? null,
|
|
5096
|
+
checkTimeoutMs: seam.checkTimeoutMs ?? seam.harnessTimeoutMs ?? bridge.timeoutMs ?? 300 * 1e3,
|
|
5097
|
+
checkOutputCap: seam.checkOutputCap ?? 16e3
|
|
5098
|
+
}
|
|
5099
|
+
}, {
|
|
5100
|
+
attemptId,
|
|
5101
|
+
binding: {
|
|
5102
|
+
bridgeUrl: bridge.bridgeUrl,
|
|
5103
|
+
effectiveProfile,
|
|
5104
|
+
model: model ?? null,
|
|
5105
|
+
repoRoot: seam.repoRoot,
|
|
5106
|
+
runId,
|
|
5107
|
+
sessionId
|
|
5108
|
+
},
|
|
5109
|
+
descriptor: {
|
|
5110
|
+
kind: "bridge-worktree-session",
|
|
5111
|
+
transport: "http",
|
|
5112
|
+
backend: "bridge-worktree"
|
|
5113
|
+
}
|
|
5114
|
+
});
|
|
4189
5115
|
}
|
|
4190
5116
|
function bridgeOutputText(out) {
|
|
4191
5117
|
if (typeof out === "string") return out;
|
|
@@ -4199,7 +5125,7 @@ function bridgeOutputText(out) {
|
|
|
4199
5125
|
return String(out);
|
|
4200
5126
|
}
|
|
4201
5127
|
}
|
|
4202
|
-
function isAsyncIterable(value) {
|
|
5128
|
+
function isAsyncIterable$1(value) {
|
|
4203
5129
|
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
4204
5130
|
}
|
|
4205
5131
|
/**
|
|
@@ -4209,14 +5135,14 @@ function isAsyncIterable(value) {
|
|
|
4209
5135
|
*/
|
|
4210
5136
|
const cliWorktreeExecutor = (spec, ctx) => {
|
|
4211
5137
|
const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
|
|
4212
|
-
if (!seam.repoRoot
|
|
5138
|
+
if (!seam.repoRoot) throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot required");
|
|
4213
5139
|
if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
|
|
4214
5140
|
if (!seam.harness) throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set");
|
|
4215
5141
|
return createWorktreeCliExecutor({
|
|
4216
5142
|
repoRoot: seam.repoRoot,
|
|
4217
5143
|
profile: spec.profile,
|
|
4218
5144
|
harness: seam.harness,
|
|
4219
|
-
taskPrompt: seam.taskPrompt,
|
|
5145
|
+
...seam.taskPrompt !== void 0 ? { taskPrompt: seam.taskPrompt } : {},
|
|
4220
5146
|
...seam.runId ? { runId: seam.runId } : {},
|
|
4221
5147
|
...seam.baseRef ? { baseRef: seam.baseRef } : {},
|
|
4222
5148
|
...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
|
|
@@ -4228,9 +5154,110 @@ const cliWorktreeExecutor = (spec, ctx) => {
|
|
|
4228
5154
|
...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
|
|
4229
5155
|
...seam.runGit ? { runGit: seam.runGit } : {},
|
|
4230
5156
|
...seam.runCommand ? { runCommand: seam.runCommand } : {},
|
|
4231
|
-
...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {}
|
|
5157
|
+
...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {},
|
|
5158
|
+
...ctx.node?.attemptId !== void 0 ? { executionAttemptId: ctx.node.attemptId } : {}
|
|
4232
5159
|
});
|
|
4233
5160
|
};
|
|
5161
|
+
/** Capture one public executor configuration at its call boundary. All data that selects policy,
|
|
5162
|
+
* model, process, limits, profile overlays, or backend behavior is detached and deeply frozen.
|
|
5163
|
+
* Explicit service/function fields remain live by reference because they are executable ports,
|
|
5164
|
+
* not portable configuration. */
|
|
5165
|
+
function snapshotExecutorConfig(config) {
|
|
5166
|
+
switch (config.backend) {
|
|
5167
|
+
case "router-tools": {
|
|
5168
|
+
const { executeToolCall, onToolStep, ...decisionData } = config;
|
|
5169
|
+
const snapshot = detachedSnapshot(decisionData, "createExecutor router-tools config");
|
|
5170
|
+
return Object.freeze({
|
|
5171
|
+
...snapshot,
|
|
5172
|
+
executeToolCall,
|
|
5173
|
+
...onToolStep === void 0 ? {} : { onToolStep }
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
case "cli-worktree": {
|
|
5177
|
+
const { runGit, runCommand, ...decisionData } = config;
|
|
5178
|
+
const snapshot = detachedSnapshot(decisionData, "createExecutor cli-worktree config");
|
|
5179
|
+
return Object.freeze({
|
|
5180
|
+
...snapshot,
|
|
5181
|
+
...runGit === void 0 ? {} : { runGit },
|
|
5182
|
+
...runCommand === void 0 ? {} : { runCommand }
|
|
5183
|
+
});
|
|
5184
|
+
}
|
|
5185
|
+
case "provider": {
|
|
5186
|
+
const { provider, registry, taskToTurn, ...decisionData } = config;
|
|
5187
|
+
const snapshot = detachedSnapshot(decisionData, "createExecutor provider config");
|
|
5188
|
+
const resolvedProvider = resolveAgentEnvironmentProvider(provider, registry);
|
|
5189
|
+
return Object.freeze({
|
|
5190
|
+
...snapshot,
|
|
5191
|
+
provider: resolvedProvider,
|
|
5192
|
+
...taskToTurn === void 0 ? {} : { taskToTurn }
|
|
5193
|
+
});
|
|
5194
|
+
}
|
|
5195
|
+
case "sandbox": {
|
|
5196
|
+
const { sandboxClient, loopCtx, ...decisionData } = config;
|
|
5197
|
+
if (loopCtx === void 0) {
|
|
5198
|
+
const snapshot = detachedSnapshot(decisionData, "createExecutor sandbox config");
|
|
5199
|
+
return Object.freeze({
|
|
5200
|
+
...snapshot,
|
|
5201
|
+
sandboxClient
|
|
5202
|
+
});
|
|
5203
|
+
}
|
|
5204
|
+
const { hooks, traceEmitter, onSandboxEvent, runHandle, ...loopDecisionData } = loopCtx;
|
|
5205
|
+
const snapshot = detachedSnapshot({
|
|
5206
|
+
...decisionData,
|
|
5207
|
+
loopCtx: loopDecisionData
|
|
5208
|
+
}, "createExecutor sandbox config");
|
|
5209
|
+
const loopSnapshot = snapshot.loopCtx;
|
|
5210
|
+
return Object.freeze({
|
|
5211
|
+
...snapshot,
|
|
5212
|
+
sandboxClient,
|
|
5213
|
+
loopCtx: Object.freeze({
|
|
5214
|
+
...loopSnapshot,
|
|
5215
|
+
...hooks === void 0 ? {} : { hooks },
|
|
5216
|
+
...traceEmitter === void 0 ? {} : { traceEmitter },
|
|
5217
|
+
...onSandboxEvent === void 0 ? {} : { onSandboxEvent },
|
|
5218
|
+
...runHandle === void 0 ? {} : { runHandle }
|
|
5219
|
+
})
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
case "router":
|
|
5223
|
+
case "bridge":
|
|
5224
|
+
case "cli":
|
|
5225
|
+
case "pi": return detachedSnapshot(config, `createExecutor ${config.backend} config`);
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
/** A backend config reused for multiple workers/managers cannot pin execution identity or carry a
|
|
5229
|
+
* profile overlay applied after Scope hashed the authored profile. Direct single-execution
|
|
5230
|
+
* `createExecutor` calls may still use those fields. */
|
|
5231
|
+
function captureReusableExecutorConfig(config, context) {
|
|
5232
|
+
const captured = snapshotExecutorConfig(config);
|
|
5233
|
+
if ((captured.backend === "bridge" ? captured.agentProfile : captured.backend === "cli-worktree" ? captured.bridge?.agentProfile : void 0) !== void 0) throw new ValidationError(`${context}: backend agentProfile overlays are not allowed because they change the effective profile after spawn identity is fixed`);
|
|
5234
|
+
const fixedIdentity = captured.backend === "bridge" && captured.sessionId !== void 0 ? "sessionId" : captured.backend === "cli-worktree" && captured.runId !== void 0 ? "runId" : captured.backend === "cli-worktree" && captured.bridge?.sessionId !== void 0 ? "bridge.sessionId" : void 0;
|
|
5235
|
+
if (fixedIdentity !== void 0) throw new ValidationError(`${context}: fixed ${fixedIdentity} is not allowed on a reusable backend; let each execution derive an isolated id`);
|
|
5236
|
+
return captured;
|
|
5237
|
+
}
|
|
5238
|
+
/** Bind one already-captured reusable backend to the durable identity of the execution that will
|
|
5239
|
+
* use it. Stateful bridge backends need an explicit external id: a random default isolates two
|
|
5240
|
+
* siblings but cannot reconnect a replacement process to the same harness session. Non-stateful
|
|
5241
|
+
* backends carry no external execution id and are returned unchanged. */
|
|
5242
|
+
function bindReusableExecutorExecutionId(captured, executionId) {
|
|
5243
|
+
if (typeof executionId !== "string" || executionId.length === 0) throw new ValidationError("bindReusableExecutorExecutionId: executionId must be a non-empty string");
|
|
5244
|
+
switch (captured.backend) {
|
|
5245
|
+
case "bridge": return Object.freeze({
|
|
5246
|
+
...captured,
|
|
5247
|
+
sessionId: executionId
|
|
5248
|
+
});
|
|
5249
|
+
case "cli-worktree": return Object.freeze({
|
|
5250
|
+
...captured,
|
|
5251
|
+
runId: executionId
|
|
5252
|
+
});
|
|
5253
|
+
case "router":
|
|
5254
|
+
case "router-tools":
|
|
5255
|
+
case "cli":
|
|
5256
|
+
case "provider":
|
|
5257
|
+
case "pi":
|
|
5258
|
+
case "sandbox": return captured;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
4234
5261
|
/**
|
|
4235
5262
|
* The single built-in executor factory. Picks a leaf backend by data (`config.backend`),
|
|
4236
5263
|
* injects the matching seam, and delegates to that backend's built-in implementation.
|
|
@@ -4240,8 +5267,10 @@ const cliWorktreeExecutor = (spec, ctx) => {
|
|
|
4240
5267
|
* `UsageEvent` reporting channel.
|
|
4241
5268
|
*/
|
|
4242
5269
|
function createExecutor(config) {
|
|
5270
|
+
const captured = snapshotExecutorConfig(config);
|
|
4243
5271
|
return (spec, ctx) => {
|
|
4244
|
-
const { backend, ...
|
|
5272
|
+
const { backend, ...seamData } = captured;
|
|
5273
|
+
const seam = Object.freeze(seamData);
|
|
4245
5274
|
const seamed = {
|
|
4246
5275
|
...ctx,
|
|
4247
5276
|
seams: {
|
|
@@ -4249,7 +5278,7 @@ function createExecutor(config) {
|
|
|
4249
5278
|
[backend]: seam
|
|
4250
5279
|
}
|
|
4251
5280
|
};
|
|
4252
|
-
switch (
|
|
5281
|
+
switch (captured.backend) {
|
|
4253
5282
|
case "router": return routerInlineExecutor(spec, seamed);
|
|
4254
5283
|
case "router-tools": return routerToolsInlineExecutor(spec, seamed);
|
|
4255
5284
|
case "bridge": return bridgeExecutor(spec, seamed);
|
|
@@ -4258,10 +5287,41 @@ function createExecutor(config) {
|
|
|
4258
5287
|
case "pi": return piExecutor(spec, seamed);
|
|
4259
5288
|
case "provider": {
|
|
4260
5289
|
const providerSeam = readSeam(seamed, providerSeamKey, "provider");
|
|
4261
|
-
|
|
5290
|
+
const provider = resolveAgentEnvironmentProvider(providerSeam.provider, providerSeam.registry);
|
|
5291
|
+
if (providerSeam.steering) {
|
|
5292
|
+
if (providerSeam.taskToTurn) throw new ValidationError("createExecutor(provider, steering): taskToTurn is not representable by the text-only steerable session");
|
|
5293
|
+
if (providerSeam.destroyOnSettle === false) throw new ValidationError("createExecutor(provider, steering): destroyOnSettle=false conflicts with the session-owned environment lifecycle");
|
|
5294
|
+
const harness = requiredProviderProfileHarness(spec, providerSeam);
|
|
5295
|
+
const sandboxClient = providerAsSandboxClient(provider, {
|
|
5296
|
+
defaults: {
|
|
5297
|
+
...providerSeam.defaults ?? {},
|
|
5298
|
+
signal: seamed.signal
|
|
5299
|
+
},
|
|
5300
|
+
requireTerminalEvent: providerSeam.requireTerminalEvent,
|
|
5301
|
+
requireSession: true
|
|
5302
|
+
});
|
|
5303
|
+
const providerCtx = {
|
|
5304
|
+
...seamed,
|
|
5305
|
+
seams: {
|
|
5306
|
+
...seamed.seams,
|
|
5307
|
+
[sandboxSeamKey]: {
|
|
5308
|
+
sandboxClient,
|
|
5309
|
+
steering: providerSeam.steering
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
};
|
|
5313
|
+
return {
|
|
5314
|
+
...sandboxExecutor({
|
|
5315
|
+
...spec,
|
|
5316
|
+
harness
|
|
5317
|
+
}, providerCtx),
|
|
5318
|
+
runtime: providerSeam.runtime ?? provider.name
|
|
5319
|
+
};
|
|
5320
|
+
}
|
|
5321
|
+
return providerAsExecutor(provider, providerSeam)(spec, seamed);
|
|
4262
5322
|
}
|
|
4263
5323
|
case "sandbox": {
|
|
4264
|
-
const harness = spec.harness ??
|
|
5324
|
+
const harness = spec.harness ?? captured.harness ?? null;
|
|
4265
5325
|
return sandboxExecutor({
|
|
4266
5326
|
...spec,
|
|
4267
5327
|
harness
|
|
@@ -4270,6 +5330,13 @@ function createExecutor(config) {
|
|
|
4270
5330
|
}
|
|
4271
5331
|
};
|
|
4272
5332
|
}
|
|
5333
|
+
function requiredProviderProfileHarness(spec, seam) {
|
|
5334
|
+
const harness = spec.profile.harness;
|
|
5335
|
+
if (harness === void 0) throw new ValidationError("createExecutor(provider, steering): AgentProfile.harness is required");
|
|
5336
|
+
if (spec.harness != null && spec.harness !== harness) throw new ValidationError(`createExecutor(provider, steering): AgentSpec.harness "${spec.harness}" conflicts with AgentProfile.harness "${harness}"`);
|
|
5337
|
+
if (seam.defaults?.backend !== void 0 && seam.defaults.backend !== harness) throw new ValidationError(`createExecutor(provider, steering): provider default backend "${seam.defaults.backend}" conflicts with AgentProfile.harness "${harness}"`);
|
|
5338
|
+
return harness;
|
|
5339
|
+
}
|
|
4273
5340
|
/**
|
|
4274
5341
|
* The open resolver/registry. Pre-registers the three built-ins under their
|
|
4275
5342
|
* runtime tags (`'router'`, `'sandbox'`, `'cli'`) and accepts `register(name,
|
|
@@ -4277,8 +5344,8 @@ function createExecutor(config) {
|
|
|
4277
5344
|
* without touching the registry at all. NOT a closed switch; registration + BYO
|
|
4278
5345
|
* ARE the extension points.
|
|
4279
5346
|
*
|
|
4280
|
-
* `resolve` precedence (frozen in `ExecutorRegistry`): a BYO `spec.
|
|
4281
|
-
* `harness === null` → the `'router'` factory; else a registered factory for the
|
|
5347
|
+
* `resolve` precedence (frozen in `ExecutorRegistry`): a BYO `spec.executorFactory` →
|
|
5348
|
+
* `spec.executor` → `harness === null` → the `'router'` factory; else a registered factory for the
|
|
4282
5349
|
* harness-derived runtime (`'sandbox'` for any `BackendType`); else fail loud.
|
|
4283
5350
|
*/
|
|
4284
5351
|
function createExecutorRegistry() {
|
|
@@ -4294,6 +5361,10 @@ function createExecutorRegistry() {
|
|
|
4294
5361
|
factories.set(runtime, factory);
|
|
4295
5362
|
},
|
|
4296
5363
|
resolve(spec) {
|
|
5364
|
+
if (spec.executorFactory) return {
|
|
5365
|
+
succeeded: true,
|
|
5366
|
+
value: spec.executorFactory
|
|
5367
|
+
};
|
|
4297
5368
|
if (spec.executor) {
|
|
4298
5369
|
const byo = spec.executor;
|
|
4299
5370
|
return {
|
|
@@ -4347,11 +5418,11 @@ function taskToPrompt(task) {
|
|
|
4347
5418
|
}
|
|
4348
5419
|
return JSON.stringify(task);
|
|
4349
5420
|
}
|
|
4350
|
-
/** Router messages from the opaque task +
|
|
5421
|
+
/** Router messages from the opaque task + every portable profile prompt instruction. */
|
|
4351
5422
|
function taskToMessages(task, spec) {
|
|
4352
5423
|
const messages = [];
|
|
4353
|
-
const system = spec.profile.prompt?.systemPrompt;
|
|
4354
|
-
if (
|
|
5424
|
+
const system = [spec.profile.prompt?.systemPrompt, ...spec.profile.prompt?.instructions ?? []].filter((line) => typeof line === "string" && line.trim().length > 0).join("\n");
|
|
5425
|
+
if (system.length > 0) messages.push({
|
|
4355
5426
|
role: "system",
|
|
4356
5427
|
content: system
|
|
4357
5428
|
});
|
|
@@ -4449,8 +5520,9 @@ function createInMemoryRunContext(opts = {}) {
|
|
|
4449
5520
|
* back on `Scope.resume` (rehydrated by `replaySpawnTree`) instead of being re-executed.
|
|
4450
5521
|
*
|
|
4451
5522
|
* Layout: `${dir}/spawn-journal.jsonl` (one JSONL record per event), `${dir}/blobs/` (one
|
|
4452
|
-
* content-addressed JSON file per settled result), and `${dir}/coordination-log.jsonl`
|
|
4453
|
-
*
|
|
5523
|
+
* content-addressed JSON file per settled result), and `${dir}/coordination-log.jsonl`
|
|
5524
|
+
* (questions, findings, answer decisions, and authorized continuation receipts retained as
|
|
5525
|
+
* evidence). The directory is created on first write.
|
|
4454
5526
|
*
|
|
4455
5527
|
* Opt-in by construction — `createInMemoryRunContext()` is unchanged and stays the default, so no
|
|
4456
5528
|
* existing consumer writes to disk or resumes unless it asks for this.
|
|
@@ -4508,168 +5580,6 @@ function watchTrace(source, opts = {}) {
|
|
|
4508
5580
|
});
|
|
4509
5581
|
}
|
|
4510
5582
|
//#endregion
|
|
4511
|
-
//#region src/runtime/supervise/dispatch.ts
|
|
4512
|
-
/**
|
|
4513
|
-
*
|
|
4514
|
-
* `rollingDispatch` — the REFILLING dispatch policy over a `Scope`. It keeps `width` children in
|
|
4515
|
-
* flight and admits the next queued unit of work the instant one settles, instead of waiting for
|
|
4516
|
-
* a whole round to drain (`fanout`) or for a driver to decide again (`spawn → await → spawn`).
|
|
4517
|
-
*
|
|
4518
|
-
* The gap it closes: `fanout` opens every item at once and then drains — one round, no refill —
|
|
4519
|
-
* and a driver brain's manual loop opens one worker per model turn. Between those two there was
|
|
4520
|
-
* nothing that holds N slots full, which is why a 5-worker run can peak at 2 live workers and sit
|
|
4521
|
-
* ~50% idle. This is a policy over the EXISTING `Scope.spawn` / `Scope.next` primitives: it adds
|
|
4522
|
-
* no second admission path, so the conserved budget pool stays the only fence on total work and
|
|
4523
|
-
* `width` is only a fence on simultaneous work.
|
|
4524
|
-
*
|
|
4525
|
-
* Fail-closed by construction: an admission rejection (`budget-exhausted` / `depth-exceeded`) is
|
|
4526
|
-
* recorded and STOPS further admission — the loop then drains what is already live and returns.
|
|
4527
|
-
* It never retries a rejected spawn against the same pool, and it never spawns past `width`.
|
|
4528
|
-
*
|
|
4529
|
-
* ── The concurrency-cap ledger (read this before adding a fourth cap) ──────────────────────────
|
|
4530
|
-
*
|
|
4531
|
-
* Three unrelated caps bound "how much runs at once" in this stack, at three different layers.
|
|
4532
|
-
* They are NOT aware of each other, and the smallest one silently wins:
|
|
4533
|
-
*
|
|
4534
|
-
* 1. `CoordinationToolsOptions.maxLiveWorkers` (`src/mcp/tools/coordination.ts`) — supervisor
|
|
4535
|
-
* level. How many workers may be spawned-but-not-settled at once; `spawn_agent` fails closed
|
|
4536
|
-
* with `error: 'max-live-workers'` past it. Unset by default ⇒ NO cap at this layer.
|
|
4537
|
-
* 2. `SandboxLineage`'s `maxConcurrency` / `DEFAULT_FORK_CONCURRENCY = 4`
|
|
4538
|
-
* (`src/runtime/sandbox-lineage.ts`) — kernel level. How many BOXES one `runAgentRounds` fork wave
|
|
4539
|
-
* provisions at once. It bounds a single leaf's fanout, not the supervisor's worker count.
|
|
4540
|
-
* 3. A host's own live-box governor (e.g. the kernel's `ComputeGovernor`, `maxSandboxes = 4`) — fleet
|
|
4541
|
-
* level. How many sandboxes may exist across the whole host process.
|
|
4542
|
-
*
|
|
4543
|
-
* The honest effective limit on simultaneous WORKERS is the minimum of the caps that apply to the
|
|
4544
|
-
* worker layer — (1) and (3). (2) is a different unit (boxes per fork wave inside one leaf) and
|
|
4545
|
-
* must not be min'd into it, or a 4-way fork inside one worker reads as a 4-worker ceiling.
|
|
4546
|
-
* `effectiveConcurrency` computes that minimum in one place so a host derives ONE number and
|
|
4547
|
-
* passes it to BOTH `maxLiveWorkers` and this dispatcher's `width`, instead of leaving a fleet
|
|
4548
|
-
* governor of 4 and an unset worker fence as unrelated numbers.
|
|
4549
|
-
*
|
|
4550
|
-
* ── Why this is not a copy of the kernel's batch loop ──────────────────────────────────────────
|
|
4551
|
-
*
|
|
4552
|
-
* `runBatch` (`src/runtime/run-loop.ts`) runs the same hold-N-slots-full shape over the KERNEL's
|
|
4553
|
-
* substrate: bare promises in a `Set`, raced with `Promise.race`, bounded by `maxConcurrency`.
|
|
4554
|
-
* This one runs it over the SUPERVISOR's substrate — `Scope.spawn`'s atomic reservation against
|
|
4555
|
-
* the conserved pool, `Scope.next`'s journaled settlement cursor, and the blob store behind each
|
|
4556
|
-
* result. Neither can be expressed in the other's terms without dragging its whole substrate
|
|
4557
|
-
* along: the kernel loop has no budget to reserve and no cursor to order settlements by, and this
|
|
4558
|
-
* one cannot race raw promises because a settlement is only real once it is journaled. The shape
|
|
4559
|
-
* repeating across the two deliberate layers is resonance, not duplication — do not "unify" them
|
|
4560
|
-
* into a shared helper that would have to know about both.
|
|
4561
|
-
*
|
|
4562
|
-
* @experimental
|
|
4563
|
-
*/
|
|
4564
|
-
/**
|
|
4565
|
-
* Run the refilling dispatch loop over `scope` until the queue is dry (or a stop fires) and every
|
|
4566
|
-
* admitted child has settled. Returns the settlements in cursor order plus the admission ledger.
|
|
4567
|
-
*
|
|
4568
|
-
* The loop is: fill free slots from `nextUnit` → `await scope.next()` → deliver the settlement →
|
|
4569
|
-
* refill → repeat. Because the refill happens immediately after each settlement rather than after
|
|
4570
|
-
* a whole round, a slow child never idles the other slots.
|
|
4571
|
-
*/
|
|
4572
|
-
async function rollingDispatch(scope, opts) {
|
|
4573
|
-
if (!Number.isInteger(opts.width) || opts.width < 1) throw new ValidationError(`rollingDispatch: width must be a positive integer, got ${String(opts.width)}`);
|
|
4574
|
-
const settled = [];
|
|
4575
|
-
const rejected = [];
|
|
4576
|
-
const live = /* @__PURE__ */ new Set();
|
|
4577
|
-
let admitted = 0;
|
|
4578
|
-
let peakLive = 0;
|
|
4579
|
-
let queueDry = false;
|
|
4580
|
-
let stopReason = "drained";
|
|
4581
|
-
const fill = async () => {
|
|
4582
|
-
while (live.size < opts.width) {
|
|
4583
|
-
if (queueDry) return;
|
|
4584
|
-
if (scope.signal.aborted) {
|
|
4585
|
-
stopReason = "aborted";
|
|
4586
|
-
queueDry = true;
|
|
4587
|
-
return;
|
|
4588
|
-
}
|
|
4589
|
-
if (opts.shouldStop?.() === true) {
|
|
4590
|
-
stopReason = "stopped";
|
|
4591
|
-
queueDry = true;
|
|
4592
|
-
return;
|
|
4593
|
-
}
|
|
4594
|
-
const unit = await opts.nextUnit();
|
|
4595
|
-
if (unit === void 0) {
|
|
4596
|
-
queueDry = true;
|
|
4597
|
-
return;
|
|
4598
|
-
}
|
|
4599
|
-
const res = scope.spawn(unit.agent, unit.task, unit.opts);
|
|
4600
|
-
if (!res.ok) {
|
|
4601
|
-
rejected.push(`${unit.opts.label}: ${res.reason}`);
|
|
4602
|
-
stopReason = "not-admitted";
|
|
4603
|
-
queueDry = true;
|
|
4604
|
-
return;
|
|
4605
|
-
}
|
|
4606
|
-
live.add(res.handle.id);
|
|
4607
|
-
admitted += 1;
|
|
4608
|
-
if (live.size > peakLive) peakLive = live.size;
|
|
4609
|
-
}
|
|
4610
|
-
};
|
|
4611
|
-
await fill();
|
|
4612
|
-
while (live.size > 0) {
|
|
4613
|
-
const s = await scope.next();
|
|
4614
|
-
if (s === null) break;
|
|
4615
|
-
live.delete(s.handle.id);
|
|
4616
|
-
settled.push(s);
|
|
4617
|
-
await opts.onSettled?.(s);
|
|
4618
|
-
await fill();
|
|
4619
|
-
}
|
|
4620
|
-
return {
|
|
4621
|
-
settled,
|
|
4622
|
-
admitted,
|
|
4623
|
-
rejected,
|
|
4624
|
-
stopReason,
|
|
4625
|
-
peakLive
|
|
4626
|
-
};
|
|
4627
|
-
}
|
|
4628
|
-
/**
|
|
4629
|
-
* Free worker slots under a simultaneity cap: `cap - live`, floored at 0, or `null` when there is
|
|
4630
|
-
* no cap (the conserved pool is then the only fence and "free slots" is not a finite number).
|
|
4631
|
-
* The one place the answer is computed, so the driver-facing tool payload and a dispatcher agree.
|
|
4632
|
-
*/
|
|
4633
|
-
function freeSlots(liveCount, cap) {
|
|
4634
|
-
if (cap === void 0 || cap <= 0) return null;
|
|
4635
|
-
return Math.max(0, cap - liveCount);
|
|
4636
|
-
}
|
|
4637
|
-
/**
|
|
4638
|
-
* The ONE honest effective limit on simultaneous workers: the minimum of the caps that actually
|
|
4639
|
-
* bound the worker layer. Ignores unset/non-positive caps; returns `undefined` when no cap applies
|
|
4640
|
-
* (uncapped — the conserved pool remains the only fence).
|
|
4641
|
-
*
|
|
4642
|
-
* Deliberately does NOT fold in `SandboxLineage`'s fork concurrency: that bounds boxes inside ONE
|
|
4643
|
-
* leaf's fork wave, a different unit. Folding it in would report a 4-worker ceiling for what is
|
|
4644
|
-
* really a 4-box fanout inside a single worker.
|
|
4645
|
-
*
|
|
4646
|
-
* Use it once, at the top of a run, and pass the result to BOTH `maxLiveWorkers` and a
|
|
4647
|
-
* dispatcher's `width` — that is what turns three unrelated numbers into one.
|
|
4648
|
-
*/
|
|
4649
|
-
function effectiveConcurrency(caps) {
|
|
4650
|
-
const applicable = [caps.maxLiveWorkers, caps.maxSandboxes].filter((c) => typeof c === "number" && c > 0);
|
|
4651
|
-
if (applicable.length === 0) return void 0;
|
|
4652
|
-
return Math.min(...applicable);
|
|
4653
|
-
}
|
|
4654
|
-
/** Convenience: a `DispatchUnit` factory over a fixed array of tasks, for the common case where
|
|
4655
|
-
* the queue is known up front and only the refill behavior is wanted. */
|
|
4656
|
-
function queueOf(units, budget) {
|
|
4657
|
-
let i = 0;
|
|
4658
|
-
return () => {
|
|
4659
|
-
const u = units[i];
|
|
4660
|
-
if (u === void 0) return void 0;
|
|
4661
|
-
i += 1;
|
|
4662
|
-
return {
|
|
4663
|
-
agent: u.agent,
|
|
4664
|
-
task: u.task,
|
|
4665
|
-
opts: {
|
|
4666
|
-
budget,
|
|
4667
|
-
label: u.label
|
|
4668
|
-
}
|
|
4669
|
-
};
|
|
4670
|
-
};
|
|
4671
|
-
}
|
|
4672
|
-
//#endregion
|
|
4673
5583
|
//#region src/runtime/supervise/event-bus.ts
|
|
4674
5584
|
/** Create the child→parent coordination bus: one typed pipe for settled outputs, questions, and analyst findings, with a priority-ordered pull queue and a pass-through subscribe lane. */
|
|
4675
5585
|
function createEventBus(now = Date.now) {
|
|
@@ -4677,7 +5587,9 @@ function createEventBus(now = Date.now) {
|
|
|
4677
5587
|
const log = [];
|
|
4678
5588
|
const subscribers = [];
|
|
4679
5589
|
const byKind = {};
|
|
5590
|
+
const staged = /* @__PURE__ */ new WeakMap();
|
|
4680
5591
|
let seq = 0;
|
|
5592
|
+
let published = 0;
|
|
4681
5593
|
let pulled = 0;
|
|
4682
5594
|
const matches = (r, kinds) => !kinds || kinds.includes(r.event.type);
|
|
4683
5595
|
const bestIndex = (kinds) => {
|
|
@@ -4695,16 +5607,19 @@ function createEventBus(now = Date.now) {
|
|
|
4695
5607
|
};
|
|
4696
5608
|
return {
|
|
4697
5609
|
async publish(event, opts) {
|
|
4698
|
-
const record = {
|
|
5610
|
+
const record = staged.get(event) ?? {
|
|
4699
5611
|
seq: seq++,
|
|
4700
5612
|
at: now(),
|
|
4701
5613
|
priority: opts?.priority ?? 0,
|
|
4702
5614
|
event
|
|
4703
5615
|
};
|
|
5616
|
+
staged.set(event, record);
|
|
5617
|
+
for (const handler of subscribers) await handler(record);
|
|
5618
|
+
staged.delete(event);
|
|
4704
5619
|
if (opts?.queue !== false) queue.push(record);
|
|
4705
5620
|
log.push(record);
|
|
5621
|
+
published += 1;
|
|
4706
5622
|
byKind[event.type] = (byKind[event.type] ?? 0) + 1;
|
|
4707
|
-
for (const handler of subscribers) await handler(record);
|
|
4708
5623
|
return record;
|
|
4709
5624
|
},
|
|
4710
5625
|
pull(kinds) {
|
|
@@ -4728,7 +5643,7 @@ function createEventBus(now = Date.now) {
|
|
|
4728
5643
|
},
|
|
4729
5644
|
stats() {
|
|
4730
5645
|
return {
|
|
4731
|
-
published
|
|
5646
|
+
published,
|
|
4732
5647
|
pulled,
|
|
4733
5648
|
byKind: { ...byKind }
|
|
4734
5649
|
};
|
|
@@ -4936,14 +5851,6 @@ function spawnProfileArg() {
|
|
|
4936
5851
|
}).properties));
|
|
4937
5852
|
return spawnProfileArgCache;
|
|
4938
5853
|
}
|
|
4939
|
-
/** Freeze a JSON-Schema tree. The derived schema is plain JSON data with no cycles. */
|
|
4940
|
-
function deepFreeze(value) {
|
|
4941
|
-
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
4942
|
-
Object.freeze(value);
|
|
4943
|
-
for (const nested of Object.values(value)) deepFreeze(nested);
|
|
4944
|
-
}
|
|
4945
|
-
return value;
|
|
4946
|
-
}
|
|
4947
5854
|
/** Build the driver's MCP tools over a live scope. */
|
|
4948
5855
|
function createCoordinationTools(opts) {
|
|
4949
5856
|
const deliverable = opts.deliverable;
|
|
@@ -4956,23 +5863,76 @@ function createCoordinationTools(opts) {
|
|
|
4956
5863
|
const questionPolicy = opts.questionPolicy ?? "auto";
|
|
4957
5864
|
const completedKeys = /* @__PURE__ */ new Set();
|
|
4958
5865
|
const keyByWorker = /* @__PURE__ */ new Map();
|
|
5866
|
+
let unkeyedAssignmentOrdinal = nextUnkeyedAssignmentOrdinal(opts.scope);
|
|
4959
5867
|
for (const [key, prior] of opts.scope.resume?.keys ?? []) if (prior.state === "completed") completedKeys.add(key);
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
5868
|
+
const nodeForWorker = (id) => opts.scope.view.nodes.find((node) => node.id === id) ?? opts.scope.resume?.view.nodes.find((node) => node.id === id);
|
|
5869
|
+
const projectSettled = (settled, resumed = false) => {
|
|
5870
|
+
const node = nodeForWorker(settled.handle.id);
|
|
5871
|
+
const assignmentId = settled.handle.assignmentId ?? node?.assignmentId;
|
|
5872
|
+
const identity = settled.handle.identity ?? node?.identity;
|
|
5873
|
+
const materialization = settled.handle.materialization ?? node?.materialization;
|
|
5874
|
+
const executionBindings = settled.handle.executionBindings ?? node?.executionBindings;
|
|
5875
|
+
const settledAt = settled.settledAt ?? node?.settledAt;
|
|
5876
|
+
const trace = settled.trace ?? node?.trace ?? {
|
|
5877
|
+
status: "unavailable",
|
|
5878
|
+
reason: "legacy-settlement-without-trace-evidence"
|
|
5879
|
+
};
|
|
5880
|
+
const common = {
|
|
5881
|
+
id: settled.handle.id,
|
|
5882
|
+
...assignmentId === void 0 ? {} : { assignmentId },
|
|
5883
|
+
...identity === void 0 ? {} : { identity },
|
|
5884
|
+
...materialization === void 0 ? {} : { materialization },
|
|
5885
|
+
...executionBindings === void 0 ? {} : { executionBindings },
|
|
5886
|
+
...settledAt === void 0 ? {} : { settledAt },
|
|
5887
|
+
trace,
|
|
5888
|
+
...resumed ? { resumed: true } : {}
|
|
5889
|
+
};
|
|
5890
|
+
return deepFreezeDetached(settled.kind === "done" ? {
|
|
5891
|
+
...common,
|
|
5892
|
+
status: "done",
|
|
5893
|
+
spent: settled.spent,
|
|
5894
|
+
...settled.verdict?.score === void 0 ? {} : { score: settled.verdict.score },
|
|
5895
|
+
...settled.verdict?.valid === void 0 ? {} : { valid: settled.verdict.valid },
|
|
5896
|
+
outRef: settled.outRef
|
|
5897
|
+
} : {
|
|
5898
|
+
...common,
|
|
5899
|
+
status: "down",
|
|
5900
|
+
...node?.spent === void 0 ? {} : { spent: node.spent },
|
|
5901
|
+
reason: settled.reason
|
|
5902
|
+
});
|
|
5903
|
+
};
|
|
5904
|
+
const resumedWorkers = [];
|
|
5905
|
+
for (const s of opts.scope.resume?.settled ?? []) {
|
|
5906
|
+
const worker = projectSettled(s, true);
|
|
5907
|
+
resumedWorkers.push(worker);
|
|
5908
|
+
ledger.push(worker);
|
|
5909
|
+
}
|
|
4971
5910
|
const bus = createEventBus();
|
|
4972
5911
|
if (opts.onEvent) {
|
|
4973
5912
|
const cb = opts.onEvent;
|
|
4974
|
-
bus.subscribe((rec) => cb(rec.event));
|
|
4975
|
-
}
|
|
5913
|
+
bus.subscribe((rec) => cb(rec.event, rec));
|
|
5914
|
+
}
|
|
5915
|
+
const resumeEvents = opts.replaySettlements ? resumedWorkers.map((worker) => deepFreezeDetached({
|
|
5916
|
+
type: "settled",
|
|
5917
|
+
worker
|
|
5918
|
+
})) : [];
|
|
5919
|
+
let resumeEventIndex = 0;
|
|
5920
|
+
let readyInFlight;
|
|
5921
|
+
const ready = () => {
|
|
5922
|
+
if (resumeEventIndex >= resumeEvents.length) return Promise.resolve();
|
|
5923
|
+
if (readyInFlight) return readyInFlight;
|
|
5924
|
+
readyInFlight = (async () => {
|
|
5925
|
+
while (resumeEventIndex < resumeEvents.length) {
|
|
5926
|
+
const event = resumeEvents[resumeEventIndex];
|
|
5927
|
+
if (!event) break;
|
|
5928
|
+
await bus.publish(event);
|
|
5929
|
+
resumeEventIndex += 1;
|
|
5930
|
+
}
|
|
5931
|
+
})().finally(() => {
|
|
5932
|
+
readyInFlight = void 0;
|
|
5933
|
+
});
|
|
5934
|
+
return readyInFlight;
|
|
5935
|
+
};
|
|
4976
5936
|
const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
|
|
4977
5937
|
const str = (v, field) => {
|
|
4978
5938
|
if (typeof v !== "string" || v.length === 0) throw new Error(`coordination tools: "${field}" must be a non-empty string`);
|
|
@@ -4995,12 +5955,14 @@ function createCoordinationTools(opts) {
|
|
|
4995
5955
|
const maxTokens = field("maxTokens");
|
|
4996
5956
|
const maxUsd = field("maxUsd");
|
|
4997
5957
|
const deadlineMs = field("deadlineMs");
|
|
4998
|
-
|
|
5958
|
+
const merged = {
|
|
4999
5959
|
maxIterations: maxIterations ?? base.maxIterations,
|
|
5000
5960
|
maxTokens: maxTokens ?? base.maxTokens,
|
|
5001
5961
|
...(maxUsd ?? base.maxUsd) === void 0 ? {} : { maxUsd: maxUsd ?? base.maxUsd },
|
|
5002
5962
|
...(deadlineMs ?? base.deadlineMs) === void 0 ? {} : { deadlineMs: deadlineMs ?? base.deadlineMs }
|
|
5003
5963
|
};
|
|
5964
|
+
assertValidBudget(merged, "coordination tools: budget");
|
|
5965
|
+
return merged;
|
|
5004
5966
|
};
|
|
5005
5967
|
const level = (v) => {
|
|
5006
5968
|
if (v === "worker" || v === "driver" || v === "loop") return v;
|
|
@@ -5010,43 +5972,27 @@ function createCoordinationTools(opts) {
|
|
|
5010
5972
|
if (v === "continue-without" || v === "blocks-step" || v === "blocks-run") return v;
|
|
5011
5973
|
throw new Error("coordination tools: \"urgency\" must be continue-without, blocks-step, or blocks-run");
|
|
5012
5974
|
};
|
|
5013
|
-
const
|
|
5014
|
-
const settledAt = Date.now();
|
|
5975
|
+
const commitSettled = (s, w) => {
|
|
5015
5976
|
const settledKey = keyByWorker.get(s.handle.id);
|
|
5016
5977
|
if (settledKey !== void 0 && s.kind === "done") completedKeys.add(settledKey);
|
|
5017
|
-
const w = s.kind === "done" ? {
|
|
5018
|
-
id: s.handle.id,
|
|
5019
|
-
status: "done",
|
|
5020
|
-
score: s.verdict?.score ?? 0,
|
|
5021
|
-
valid: s.verdict?.valid ?? false,
|
|
5022
|
-
outRef: s.outRef,
|
|
5023
|
-
settledAt
|
|
5024
|
-
} : {
|
|
5025
|
-
id: s.handle.id,
|
|
5026
|
-
status: "down",
|
|
5027
|
-
reason: s.reason,
|
|
5028
|
-
settledAt
|
|
5029
|
-
};
|
|
5030
5978
|
ledger.push(w);
|
|
5031
5979
|
unwatchWorker(w.id);
|
|
5032
|
-
return w;
|
|
5033
5980
|
};
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
await bus.publish(
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
const trace = await opts.blobs.get(w.outRef);
|
|
5981
|
+
let pendingSettlement;
|
|
5982
|
+
const flushPendingSettlement = async () => {
|
|
5983
|
+
const pending = pendingSettlement;
|
|
5984
|
+
if (!pending) return false;
|
|
5985
|
+
await bus.publish(pending.event);
|
|
5986
|
+
commitSettled(pending.settled, pending.worker);
|
|
5987
|
+
pendingSettlement = void 0;
|
|
5988
|
+
if (pending.analyze && pending.worker.status === "done" && pending.worker.trace.status === "available" && opts.analysts && opts.analyzeOnSettle?.length) {
|
|
5989
|
+
const trace = await workerTraceAnalysisStore(pending.worker.trace, opts.blobs);
|
|
5044
5990
|
for (const analyst of opts.analyzeOnSettle) {
|
|
5045
5991
|
const findings = await opts.analysts.run(analyst, trace);
|
|
5046
5992
|
await bus.publish({
|
|
5047
5993
|
type: "finding",
|
|
5048
5994
|
finding: {
|
|
5049
|
-
fromWorker:
|
|
5995
|
+
fromWorker: pending.worker.id,
|
|
5050
5996
|
analyst,
|
|
5051
5997
|
findings
|
|
5052
5998
|
}
|
|
@@ -5055,16 +6001,41 @@ function createCoordinationTools(opts) {
|
|
|
5055
6001
|
}
|
|
5056
6002
|
return true;
|
|
5057
6003
|
};
|
|
6004
|
+
const drainSettlement = async () => {
|
|
6005
|
+
if (!pendingSettlement) {
|
|
6006
|
+
const settled = await opts.scope.next();
|
|
6007
|
+
if (!settled) return false;
|
|
6008
|
+
const worker = projectSettled(settled);
|
|
6009
|
+
pendingSettlement = {
|
|
6010
|
+
settled,
|
|
6011
|
+
worker,
|
|
6012
|
+
event: deepFreezeDetached({
|
|
6013
|
+
type: "settled",
|
|
6014
|
+
worker
|
|
6015
|
+
}),
|
|
6016
|
+
analyze: true
|
|
6017
|
+
};
|
|
6018
|
+
}
|
|
6019
|
+
return flushPendingSettlement();
|
|
6020
|
+
};
|
|
5058
6021
|
const drainResolved = async () => {
|
|
5059
6022
|
let drained = 0;
|
|
5060
6023
|
for (;;) {
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
6024
|
+
if (!pendingSettlement) {
|
|
6025
|
+
const settled = await opts.scope.nextResolved();
|
|
6026
|
+
if (!settled) return drained;
|
|
6027
|
+
const worker = projectSettled(settled);
|
|
6028
|
+
pendingSettlement = {
|
|
6029
|
+
settled,
|
|
6030
|
+
worker,
|
|
6031
|
+
event: deepFreezeDetached({
|
|
6032
|
+
type: "settled",
|
|
6033
|
+
worker
|
|
6034
|
+
}),
|
|
6035
|
+
analyze: false
|
|
6036
|
+
};
|
|
6037
|
+
}
|
|
6038
|
+
await flushPendingSettlement();
|
|
5068
6039
|
drained += 1;
|
|
5069
6040
|
}
|
|
5070
6041
|
};
|
|
@@ -5078,21 +6049,100 @@ function createCoordinationTools(opts) {
|
|
|
5078
6049
|
down
|
|
5079
6050
|
}, { queue: false });
|
|
5080
6051
|
}
|
|
6052
|
+
const authorizeInstruction = (kind, workerId, instruction, interrupt, questionId) => {
|
|
6053
|
+
const workerIdentity = opts.scope.view.nodes.find((node) => node.id === workerId)?.identity;
|
|
6054
|
+
let authorizedInstruction = instruction;
|
|
6055
|
+
if (opts.authorizeDownMessage) {
|
|
6056
|
+
if (workerIdentity === void 0) throw new Error(`coordination tools: cannot authorize ${kind} for worker ${JSON.stringify(workerId)} without durable identity`);
|
|
6057
|
+
const decision = deepFreezeDetached(opts.authorizeDownMessage(deepFreezeDetached({
|
|
6058
|
+
kind,
|
|
6059
|
+
workerId,
|
|
6060
|
+
workerIdentity,
|
|
6061
|
+
instruction,
|
|
6062
|
+
interrupt,
|
|
6063
|
+
...questionId !== void 0 ? { questionId } : {}
|
|
6064
|
+
})));
|
|
6065
|
+
if (typeof decision !== "object" || decision === null || Array.isArray(decision) || typeof decision.instruction !== "string" || decision.instruction.length === 0) throw new Error("coordination tools: authorizeDownMessage must return an instruction");
|
|
6066
|
+
authorizedInstruction = decision.instruction;
|
|
6067
|
+
}
|
|
6068
|
+
return deepFreezeDetached({
|
|
6069
|
+
receiptId: randomUUID(),
|
|
6070
|
+
kind,
|
|
6071
|
+
toWorker: workerId,
|
|
6072
|
+
instruction: authorizedInstruction,
|
|
6073
|
+
instructionDigest: canonicalCandidateDigest(authorizedInstruction),
|
|
6074
|
+
...workerIdentity !== void 0 ? { workerIdentity } : {},
|
|
6075
|
+
interrupt,
|
|
6076
|
+
...questionId !== void 0 ? { questionId } : {}
|
|
6077
|
+
});
|
|
6078
|
+
};
|
|
6079
|
+
/** Publish before `scope.send`: an awaited durable subscriber therefore commits the exact bytes
|
|
6080
|
+
* before the worker can observe them. */
|
|
6081
|
+
const recordInstruction = async (instruction) => {
|
|
6082
|
+
await bus.publish({
|
|
6083
|
+
type: "instruction",
|
|
6084
|
+
instruction
|
|
6085
|
+
}, { queue: false });
|
|
6086
|
+
};
|
|
6087
|
+
/** Commit delivery intent after the authorization receipt and before `Scope.send`. An attempt with
|
|
6088
|
+
* no matching outcome after a crash is explicitly unknown and must never be replayed. */
|
|
6089
|
+
const recordDeliveryAttempt = async (instruction) => {
|
|
6090
|
+
const attempt = deepFreezeDetached({
|
|
6091
|
+
receiptId: instruction.receiptId,
|
|
6092
|
+
kind: instruction.kind,
|
|
6093
|
+
toWorker: instruction.toWorker,
|
|
6094
|
+
instructionDigest: instruction.instructionDigest,
|
|
6095
|
+
interrupt: instruction.interrupt,
|
|
6096
|
+
...instruction.questionId !== void 0 ? { questionId: instruction.questionId } : {}
|
|
6097
|
+
});
|
|
6098
|
+
await bus.publish({
|
|
6099
|
+
type: "delivery-attempt",
|
|
6100
|
+
attempt
|
|
6101
|
+
}, { queue: false });
|
|
6102
|
+
return attempt;
|
|
6103
|
+
};
|
|
6104
|
+
const deliveryOutcome = (workerId, delivered) => {
|
|
6105
|
+
if (delivered) return "delivered";
|
|
6106
|
+
if (opts.scope.signal.aborted) return "scope-stopped";
|
|
6107
|
+
const node = opts.scope.view.nodes.find((candidate) => candidate.id === workerId);
|
|
6108
|
+
if (!node) return "unknown-worker";
|
|
6109
|
+
if (!isLive(node.status)) return "already-settled";
|
|
6110
|
+
return "runtime-has-no-inbox";
|
|
6111
|
+
};
|
|
6112
|
+
const attemptDelivery = async (instruction, message) => {
|
|
6113
|
+
await recordDeliveryAttempt(instruction);
|
|
6114
|
+
let delivered = false;
|
|
6115
|
+
let outcome;
|
|
6116
|
+
let error;
|
|
6117
|
+
try {
|
|
6118
|
+
delivered = opts.scope.send(instruction.toWorker, message);
|
|
6119
|
+
outcome = deliveryOutcome(instruction.toWorker, delivered);
|
|
6120
|
+
} catch (cause) {
|
|
6121
|
+
outcome = "runtime-error";
|
|
6122
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
6123
|
+
}
|
|
6124
|
+
const down = deepFreezeDetached({
|
|
6125
|
+
receiptId: instruction.receiptId,
|
|
6126
|
+
toWorker: instruction.toWorker,
|
|
6127
|
+
instruction: instruction.instruction,
|
|
6128
|
+
instructionDigest: instruction.instructionDigest,
|
|
6129
|
+
delivered,
|
|
6130
|
+
outcome,
|
|
6131
|
+
...error !== void 0 ? { error } : {}
|
|
6132
|
+
});
|
|
6133
|
+
if (instruction.kind === "answer") await sendDown("answer", down, str(instruction.questionId, "questionId"));
|
|
6134
|
+
else await sendDown("steer", down);
|
|
6135
|
+
if (error !== void 0) throw new Error(`coordination tools: delivery failed: ${error}`);
|
|
6136
|
+
return down;
|
|
6137
|
+
};
|
|
5081
6138
|
const projectEvent = (ev) => {
|
|
5082
6139
|
if (ev.type === "settled") {
|
|
5083
|
-
const
|
|
5084
|
-
return
|
|
5085
|
-
type: "settled",
|
|
5086
|
-
settled: w.id,
|
|
5087
|
-
status: "done",
|
|
5088
|
-
score: w.score,
|
|
5089
|
-
valid: w.valid,
|
|
5090
|
-
outRef: w.outRef
|
|
5091
|
-
} : {
|
|
6140
|
+
const { id, status, ...evidence } = ev.worker;
|
|
6141
|
+
return {
|
|
5092
6142
|
type: "settled",
|
|
5093
|
-
settled:
|
|
5094
|
-
status
|
|
5095
|
-
|
|
6143
|
+
settled: id,
|
|
6144
|
+
status,
|
|
6145
|
+
...evidence
|
|
5096
6146
|
};
|
|
5097
6147
|
}
|
|
5098
6148
|
if (ev.type === "question") return {
|
|
@@ -5108,12 +6158,25 @@ function createCoordinationTools(opts) {
|
|
|
5108
6158
|
...ev.down,
|
|
5109
6159
|
questionId: ev.questionId
|
|
5110
6160
|
};
|
|
5111
|
-
return {
|
|
6161
|
+
if (ev.type === "instruction") return {
|
|
6162
|
+
type: "instruction",
|
|
6163
|
+
...ev.instruction
|
|
6164
|
+
};
|
|
6165
|
+
if (ev.type === "delivery-attempt") return {
|
|
6166
|
+
type: "delivery-attempt",
|
|
6167
|
+
...ev.attempt
|
|
6168
|
+
};
|
|
6169
|
+
return {
|
|
5112
6170
|
type: ev.type,
|
|
5113
6171
|
...ev.down
|
|
5114
6172
|
};
|
|
5115
6173
|
};
|
|
5116
|
-
const nextQuestionId = (from) =>
|
|
6174
|
+
const nextQuestionId = (from) => {
|
|
6175
|
+
for (;;) {
|
|
6176
|
+
const id = `${from}:q${questionSeq++}`;
|
|
6177
|
+
if (!questions.some((question) => question.id === id)) return id;
|
|
6178
|
+
}
|
|
6179
|
+
};
|
|
5117
6180
|
const normalizeQuestion = (q, fallbackFrom) => {
|
|
5118
6181
|
const from = str(q.from ?? fallbackFrom, "from");
|
|
5119
6182
|
return {
|
|
@@ -5181,13 +6244,30 @@ function createCoordinationTools(opts) {
|
|
|
5181
6244
|
};
|
|
5182
6245
|
const maxLiveWorkers = opts.maxLiveWorkers;
|
|
5183
6246
|
const isLive = (status) => status !== "done" && status !== "failed" && status !== "cancelled";
|
|
5184
|
-
const
|
|
5185
|
-
const
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
6247
|
+
const localLiveWorkerCount = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).length;
|
|
6248
|
+
const sharedWorkerCapacity = () => {
|
|
6249
|
+
return opts.scope.workerCapacity;
|
|
6250
|
+
};
|
|
6251
|
+
const usesTreeWideLimit = () => {
|
|
6252
|
+
const capacity = sharedWorkerCapacity();
|
|
6253
|
+
return capacity !== void 0 && capacity.freeSlots !== null;
|
|
6254
|
+
};
|
|
6255
|
+
const liveWorkerCount = () => usesTreeWideLimit() ? sharedWorkerCapacity()?.live ?? localLiveWorkerCount() : localLiveWorkerCount();
|
|
6256
|
+
const projectNodeEvidence = (node, resumed = false) => ({
|
|
6257
|
+
id: node.id,
|
|
6258
|
+
status: node.status,
|
|
6259
|
+
...node.assignmentId === void 0 ? {} : { assignmentId: node.assignmentId },
|
|
6260
|
+
...node.identity === void 0 ? {} : { identity: node.identity },
|
|
6261
|
+
...node.materialization === void 0 ? {} : { materialization: node.materialization },
|
|
6262
|
+
...node.executionBindings === void 0 ? {} : { executionBindings: node.executionBindings },
|
|
6263
|
+
spent: node.spent,
|
|
6264
|
+
...node.settledAt === void 0 ? {} : { settledAt: node.settledAt },
|
|
6265
|
+
...node.outRef === void 0 ? {} : { outRef: node.outRef },
|
|
6266
|
+
...node.trace === void 0 ? {} : { trace: node.trace },
|
|
6267
|
+
...resumed ? { resumed: true } : {}
|
|
6268
|
+
});
|
|
6269
|
+
const liveSnapshot = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).map((n) => projectNodeEvidence(n));
|
|
6270
|
+
const freeWorkerSlots = () => usesTreeWideLimit() ? sharedWorkerCapacity()?.freeSlots ?? null : freeSlots(localLiveWorkerCount(), maxLiveWorkers);
|
|
5191
6271
|
const readProgress = (id) => {
|
|
5192
6272
|
const scope = opts.scope;
|
|
5193
6273
|
if (typeof scope.progress !== "function") return void 0;
|
|
@@ -5288,10 +6368,22 @@ function createCoordinationTools(opts) {
|
|
|
5288
6368
|
type: "object",
|
|
5289
6369
|
description: "Optional per-spawn budget that merges over the per-worker default (per field). Only set the ceilings this sub-task needs raised; the conserved pool still fences.",
|
|
5290
6370
|
properties: {
|
|
5291
|
-
maxIterations: {
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
6371
|
+
maxIterations: {
|
|
6372
|
+
type: "number",
|
|
6373
|
+
minimum: 0
|
|
6374
|
+
},
|
|
6375
|
+
maxTokens: {
|
|
6376
|
+
type: "number",
|
|
6377
|
+
minimum: 0
|
|
6378
|
+
},
|
|
6379
|
+
maxUsd: {
|
|
6380
|
+
type: "number",
|
|
6381
|
+
minimum: 0
|
|
6382
|
+
},
|
|
6383
|
+
deadlineMs: {
|
|
6384
|
+
type: "number",
|
|
6385
|
+
minimum: 0
|
|
6386
|
+
}
|
|
5295
6387
|
}
|
|
5296
6388
|
}
|
|
5297
6389
|
},
|
|
@@ -5300,28 +6392,47 @@ function createCoordinationTools(opts) {
|
|
|
5300
6392
|
handler: (raw) => {
|
|
5301
6393
|
const a = obj(raw);
|
|
5302
6394
|
const key = a.key === void 0 ? void 0 : str(a.key, "key");
|
|
5303
|
-
if (!(key !== void 0 && completedKeys.has(key)) && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve({
|
|
6395
|
+
if (!(key !== void 0 && completedKeys.has(key)) && !usesTreeWideLimit() && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve({
|
|
5304
6396
|
error: "max-live-workers",
|
|
5305
6397
|
live: liveWorkerCount(),
|
|
5306
6398
|
freeSlots: freeWorkerSlots()
|
|
5307
6399
|
});
|
|
5308
|
-
const
|
|
5309
|
-
|
|
5310
|
-
|
|
6400
|
+
const parsedProfile = agentProfileSchema.safeParse(a.profile);
|
|
6401
|
+
if (!parsedProfile.success) return Promise.resolve({
|
|
6402
|
+
error: "invalid-profile",
|
|
6403
|
+
issues: parsedProfile.error.issues.map((issue) => ({
|
|
6404
|
+
path: issue.path.join("."),
|
|
6405
|
+
message: issue.message
|
|
6406
|
+
}))
|
|
6407
|
+
});
|
|
6408
|
+
const profile = deepFreezeDetached(parsedProfile.data);
|
|
6409
|
+
const task = deepFreezeDetached(a.task);
|
|
6410
|
+
const label = typeof a.label === "string" ? a.label : "worker";
|
|
6411
|
+
const budget = Object.freeze(a.budget === void 0 ? opts.perWorker : mergeBudget(opts.perWorker, a.budget));
|
|
6412
|
+
const assignmentId = key !== void 0 ? `key:${key}` : `ordinal:${unkeyedAssignmentOrdinal++}`;
|
|
6413
|
+
const context = Object.freeze({
|
|
6414
|
+
assignmentId,
|
|
6415
|
+
parentNodeId: opts.scope.view.root,
|
|
6416
|
+
budget,
|
|
6417
|
+
task,
|
|
6418
|
+
label,
|
|
6419
|
+
...key !== void 0 ? { key } : {}
|
|
6420
|
+
});
|
|
6421
|
+
const res = opts.scope.spawn(() => opts.makeWorkerAgent(profile, context), task, {
|
|
5311
6422
|
budget,
|
|
5312
|
-
label
|
|
6423
|
+
label,
|
|
6424
|
+
assignmentId,
|
|
5313
6425
|
...key !== void 0 ? { key } : {}
|
|
5314
6426
|
});
|
|
5315
6427
|
if (res.ok && res.prior?.state === "completed") {
|
|
5316
6428
|
const s = res.prior.settled;
|
|
5317
6429
|
if (key !== void 0) completedKeys.add(key);
|
|
6430
|
+
const { id, status, resumed: _resumed, ...evidence } = projectSettled(s);
|
|
5318
6431
|
return Promise.resolve({
|
|
5319
|
-
workerId:
|
|
6432
|
+
workerId: id,
|
|
5320
6433
|
resumed: "completed",
|
|
5321
|
-
status
|
|
5322
|
-
|
|
5323
|
-
valid: s.verdict?.valid ?? false,
|
|
5324
|
-
outRef: s.outRef,
|
|
6434
|
+
status,
|
|
6435
|
+
...evidence,
|
|
5325
6436
|
live: liveWorkerCount(),
|
|
5326
6437
|
freeSlots: freeWorkerSlots()
|
|
5327
6438
|
});
|
|
@@ -5337,6 +6448,10 @@ function createCoordinationTools(opts) {
|
|
|
5337
6448
|
} : {};
|
|
5338
6449
|
return Promise.resolve(res.ok ? {
|
|
5339
6450
|
workerId: res.handle.id,
|
|
6451
|
+
assignmentId: res.handle.assignmentId ?? assignmentId,
|
|
6452
|
+
...res.handle.identity === void 0 ? {} : { identity: res.handle.identity },
|
|
6453
|
+
...res.handle.materialization === void 0 ? {} : { materialization: res.handle.materialization },
|
|
6454
|
+
...res.handle.executionBindings === void 0 ? {} : { executionBindings: res.handle.executionBindings },
|
|
5340
6455
|
live: liveWorkerCount(),
|
|
5341
6456
|
freeSlots: freeWorkerSlots(),
|
|
5342
6457
|
...priorHistory
|
|
@@ -5364,19 +6479,16 @@ function createCoordinationTools(opts) {
|
|
|
5364
6479
|
if (!resumed) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
5365
6480
|
const output = resumed.outRef ? await opts.blobs.get(resumed.outRef) : void 0;
|
|
5366
6481
|
return {
|
|
5367
|
-
|
|
5368
|
-
spent: resumed.spent,
|
|
6482
|
+
...projectNodeEvidence(resumed, true),
|
|
5369
6483
|
outRef: resumed.outRef ?? null,
|
|
5370
6484
|
output: output ?? null,
|
|
5371
|
-
progress: null
|
|
5372
|
-
resumed: true
|
|
6485
|
+
progress: null
|
|
5373
6486
|
};
|
|
5374
6487
|
}
|
|
5375
6488
|
const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
|
|
5376
6489
|
const progress = readProgress(id);
|
|
5377
6490
|
return {
|
|
5378
|
-
|
|
5379
|
-
spent: node.spent,
|
|
6491
|
+
...projectNodeEvidence(node),
|
|
5380
6492
|
outRef: node.outRef ?? null,
|
|
5381
6493
|
output: output ?? null,
|
|
5382
6494
|
progress: progress ?? null
|
|
@@ -5406,24 +6518,20 @@ function createCoordinationTools(opts) {
|
|
|
5406
6518
|
const workerId = str(a.workerId, "workerId");
|
|
5407
6519
|
const instruction = str(a.instruction, "instruction");
|
|
5408
6520
|
const interrupt = a.interrupt === true;
|
|
5409
|
-
const
|
|
5410
|
-
|
|
6521
|
+
const authorized = authorizeInstruction("steer", workerId, instruction, interrupt);
|
|
6522
|
+
await recordInstruction(authorized);
|
|
6523
|
+
const delivery = await attemptDelivery(authorized, {
|
|
6524
|
+
steer: authorized.instruction,
|
|
5411
6525
|
interrupt
|
|
5412
6526
|
});
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
instruction,
|
|
5416
|
-
delivered
|
|
5417
|
-
});
|
|
5418
|
-
if (delivered) return {
|
|
5419
|
-
delivered,
|
|
6527
|
+
if (delivery.delivered) return {
|
|
6528
|
+
delivered: true,
|
|
5420
6529
|
progress: readProgress(workerId) ?? null
|
|
5421
6530
|
};
|
|
5422
|
-
const progress = readProgress(workerId);
|
|
5423
6531
|
return {
|
|
5424
|
-
delivered,
|
|
5425
|
-
reason:
|
|
5426
|
-
progress:
|
|
6532
|
+
delivered: false,
|
|
6533
|
+
reason: delivery.outcome,
|
|
6534
|
+
progress: readProgress(workerId) ?? null
|
|
5427
6535
|
};
|
|
5428
6536
|
}
|
|
5429
6537
|
},
|
|
@@ -5505,25 +6613,24 @@ function createCoordinationTools(opts) {
|
|
|
5505
6613
|
const questionId = str(a.questionId, "questionId");
|
|
5506
6614
|
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
5507
6615
|
const answer = a.answer;
|
|
5508
|
-
const
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
const
|
|
5514
|
-
|
|
5515
|
-
answer,
|
|
6616
|
+
const pendingQuestion = questions.find((question) => question.id === questionId);
|
|
6617
|
+
if (pendingQuestion === void 0) throw new Error(`unknown questionId ${JSON.stringify(questionId)}`);
|
|
6618
|
+
const interrupt = pendingQuestion.urgency === "blocks-run" || pendingQuestion.urgency === "blocks-step";
|
|
6619
|
+
const authorized = authorizeInstruction("answer", pendingQuestion.from, answer, interrupt, questionId);
|
|
6620
|
+
await recordInstruction(authorized);
|
|
6621
|
+
const delivery = await attemptDelivery(authorized, {
|
|
6622
|
+
answer: authorized.instruction,
|
|
5516
6623
|
questionId,
|
|
5517
6624
|
interrupt
|
|
5518
6625
|
});
|
|
5519
|
-
await sendDown("answer", {
|
|
5520
|
-
toWorker: question.from,
|
|
5521
|
-
instruction: answer,
|
|
5522
|
-
delivered
|
|
5523
|
-
}, questionId);
|
|
5524
6626
|
return {
|
|
5525
|
-
question,
|
|
5526
|
-
|
|
6627
|
+
question: delivery.delivered ? decideQuestion(questionId, {
|
|
6628
|
+
kind: "answer",
|
|
6629
|
+
answer: authorized.instruction,
|
|
6630
|
+
by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
|
|
6631
|
+
}) : pendingQuestion,
|
|
6632
|
+
delivered: delivery.delivered,
|
|
6633
|
+
...delivery.delivered ? {} : { reason: delivery.outcome }
|
|
5527
6634
|
};
|
|
5528
6635
|
}
|
|
5529
6636
|
if (typeof a.deferReason === "string" && a.deferReason.length > 0) return Promise.resolve({ question: decideQuestion(questionId, {
|
|
@@ -5689,16 +6796,29 @@ function createCoordinationTools(opts) {
|
|
|
5689
6796
|
handler: async (raw) => {
|
|
5690
6797
|
const a = obj(raw);
|
|
5691
6798
|
const id = str(a.workerId, "workerId");
|
|
5692
|
-
const node =
|
|
6799
|
+
const node = nodeForWorker(id);
|
|
5693
6800
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
5694
|
-
if (
|
|
5695
|
-
const trace =
|
|
5696
|
-
|
|
6801
|
+
if (isLive(node.status)) return { error: `worker ${JSON.stringify(id)} has not settled — no trace to analyze yet` };
|
|
6802
|
+
const trace = ledger.find((worker) => worker.id === id)?.trace ?? node.trace ?? {
|
|
6803
|
+
status: "unavailable",
|
|
6804
|
+
reason: "legacy-settlement-without-trace-evidence"
|
|
6805
|
+
};
|
|
6806
|
+
let store;
|
|
6807
|
+
try {
|
|
6808
|
+
store = await workerTraceAnalysisStore(trace, opts.blobs);
|
|
6809
|
+
} catch (error) {
|
|
6810
|
+
return {
|
|
6811
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6812
|
+
trace
|
|
6813
|
+
};
|
|
6814
|
+
}
|
|
6815
|
+
return { findings: await opts.analysts?.run(str(a.kind, "kind"), store) };
|
|
5697
6816
|
}
|
|
5698
6817
|
});
|
|
5699
6818
|
}
|
|
5700
6819
|
return {
|
|
5701
6820
|
tools,
|
|
6821
|
+
ready,
|
|
5702
6822
|
history: () => bus.history(),
|
|
5703
6823
|
raiseFinding: (finding) => bus.publish({
|
|
5704
6824
|
type: "finding",
|
|
@@ -5713,6 +6833,31 @@ function createCoordinationTools(opts) {
|
|
|
5713
6833
|
drainResolved
|
|
5714
6834
|
};
|
|
5715
6835
|
}
|
|
6836
|
+
function nextUnkeyedAssignmentOrdinal(scope) {
|
|
6837
|
+
let next = 0;
|
|
6838
|
+
const views = [scope.resume?.view, scope.view];
|
|
6839
|
+
for (const view of views) {
|
|
6840
|
+
if (view === void 0) continue;
|
|
6841
|
+
for (const node of view.nodes) {
|
|
6842
|
+
const match = /^ordinal:(\d+)$/.exec(node.assignmentId ?? "");
|
|
6843
|
+
if (match === null) continue;
|
|
6844
|
+
const ordinal = Number(match[1]);
|
|
6845
|
+
if (!Number.isSafeInteger(ordinal)) throw new Error(`coordination: durable assignment id '${node.assignmentId}' exceeds the safe ordinal range`);
|
|
6846
|
+
next = Math.max(next, ordinal + 1);
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
if (!Number.isSafeInteger(next)) throw new Error("coordination: durable assignment ordinal space is exhausted");
|
|
6850
|
+
return next;
|
|
6851
|
+
}
|
|
6852
|
+
function deepFreezeDetached(value) {
|
|
6853
|
+
return deepFreeze(structuredClone(value));
|
|
6854
|
+
}
|
|
6855
|
+
function deepFreeze(value, seen = /* @__PURE__ */ new Set()) {
|
|
6856
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return value;
|
|
6857
|
+
seen.add(value);
|
|
6858
|
+
for (const child of Object.values(value)) deepFreeze(child, seen);
|
|
6859
|
+
return Object.freeze(value);
|
|
6860
|
+
}
|
|
5716
6861
|
//#endregion
|
|
5717
6862
|
//#region src/runtime/anytime.ts
|
|
5718
6863
|
/**
|
|
@@ -6132,10 +7277,11 @@ const runawayTripwireTurns = 2e3;
|
|
|
6132
7277
|
* overspend usd up to the turn tripwire). */
|
|
6133
7278
|
function poolStarved(scope, perWorker) {
|
|
6134
7279
|
const b = scope.budget;
|
|
6135
|
-
if (
|
|
7280
|
+
if (scope.view.inFlight > 0 || scope.view.waiting > 0) return false;
|
|
6136
7281
|
const tokenStarved = b.tokensLeft < perWorker.maxTokens;
|
|
6137
|
-
const
|
|
6138
|
-
|
|
7282
|
+
const iterationStarved = b.iterationsLeft <= 0;
|
|
7283
|
+
const usdStarved = b.usdCapped && (b.usdLeft <= 0 || perWorker.maxUsd !== void 0 && b.usdLeft < perWorker.maxUsd);
|
|
7284
|
+
return tokenStarved || iterationStarved || usdStarved;
|
|
6139
7285
|
}
|
|
6140
7286
|
/** The absolute wall-clock deadline (when the root set one) has passed. */
|
|
6141
7287
|
function deadlinePassed(scope, now) {
|
|
@@ -6168,17 +7314,29 @@ function driverAgent(opts) {
|
|
|
6168
7314
|
if ((opts.extraTools?.length ?? 0) > 0 && typeof opts.executeExtraTool !== "function") throw new ValidationError("driverAgent: extraTools requires executeExtraTool (how to run a work-tool call)");
|
|
6169
7315
|
if ((opts.analyzeOnSettle?.length ?? 0) > 0 && !opts.analysts) throw new ValidationError("driverAgent: analyzeOnSettle requires analysts (the lens registry the kinds resolve against)");
|
|
6170
7316
|
const reserved = new Set(coordinationVerbNames);
|
|
6171
|
-
for (const
|
|
7317
|
+
for (const tool of opts.nodeTools ?? []) {
|
|
7318
|
+
if (reserved.has(tool.name)) throw new ValidationError(`driverAgent: node tool "${tool.name}" collides with a coordination verb or another node tool`);
|
|
7319
|
+
reserved.add(tool.name);
|
|
7320
|
+
}
|
|
7321
|
+
for (const t of opts.extraTools ?? []) {
|
|
7322
|
+
if (reserved.has(t.name)) throw new ValidationError(`driverAgent: extra work tool "${t.name}" collides with a coordination verb or node tool`);
|
|
7323
|
+
reserved.add(t.name);
|
|
7324
|
+
}
|
|
6172
7325
|
if (opts.maxTurns !== void 0 && opts.maxTurns < 0) throw new ValidationError("driverAgent: maxTurns must be >= 0 (0 lifts the turn cap; bounds become the conserved pool + deadline + abort)");
|
|
6173
7326
|
const maxTurns = opts.maxTurns === 0 ? runawayTripwireTurns : opts.maxTurns ?? 16;
|
|
6174
7327
|
const now = opts.now ?? Date.now;
|
|
7328
|
+
const inbox = opts.inbox ?? createInbox();
|
|
6175
7329
|
return {
|
|
6176
7330
|
name: opts.name,
|
|
7331
|
+
deliver(message) {
|
|
7332
|
+
return inbox.deliver(message);
|
|
7333
|
+
},
|
|
6177
7334
|
async act(task, scope) {
|
|
6178
7335
|
const coord = createCoordinationTools({
|
|
6179
7336
|
scope,
|
|
6180
7337
|
blobs: opts.blobs,
|
|
6181
7338
|
makeWorkerAgent: opts.makeWorkerAgent,
|
|
7339
|
+
...opts.authorizeDownMessage ? { authorizeDownMessage: opts.authorizeDownMessage } : {},
|
|
6182
7340
|
perWorker: opts.perWorker,
|
|
6183
7341
|
...opts.deliverable ? { deliverable: opts.deliverable } : {},
|
|
6184
7342
|
...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
|
|
@@ -6187,28 +7345,41 @@ function driverAgent(opts) {
|
|
|
6187
7345
|
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
6188
7346
|
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
6189
7347
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
7348
|
+
...opts.replaySettlements ? { replaySettlements: true } : {},
|
|
6190
7349
|
...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {}
|
|
6191
7350
|
});
|
|
7351
|
+
await coord.ready();
|
|
6192
7352
|
for (const w of scope.resume?.waits ?? []) {
|
|
6193
7353
|
const rearmed = scope.wait(w.spec, { label: w.label });
|
|
6194
7354
|
if (!rearmed.ok) throw new RuntimeRunStateError(`driverAgent: cannot re-arm resumed wait '${w.label}' (${rearmed.reason})`);
|
|
6195
7355
|
}
|
|
6196
|
-
const byName = new Map(coord.tools.map((t) => [t.name, t]));
|
|
6197
|
-
const toolSpecs = [
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
7356
|
+
const byName = new Map([...coord.tools, ...opts.nodeTools ?? []].map((t) => [t.name, t]));
|
|
7357
|
+
const toolSpecs = [
|
|
7358
|
+
...coord.tools.map((t) => ({
|
|
7359
|
+
type: "function",
|
|
7360
|
+
function: {
|
|
7361
|
+
name: t.name,
|
|
7362
|
+
description: t.description,
|
|
7363
|
+
parameters: t.inputSchema
|
|
7364
|
+
}
|
|
7365
|
+
})),
|
|
7366
|
+
...(opts.nodeTools ?? []).map((t) => ({
|
|
7367
|
+
type: "function",
|
|
7368
|
+
function: {
|
|
7369
|
+
name: t.name,
|
|
7370
|
+
description: t.description,
|
|
7371
|
+
parameters: t.inputSchema
|
|
7372
|
+
}
|
|
7373
|
+
})),
|
|
7374
|
+
...(opts.extraTools ?? []).map((t) => ({
|
|
7375
|
+
type: "function",
|
|
7376
|
+
function: {
|
|
7377
|
+
name: t.name,
|
|
7378
|
+
description: t.description,
|
|
7379
|
+
parameters: t.parameters
|
|
7380
|
+
}
|
|
7381
|
+
}))
|
|
7382
|
+
];
|
|
6212
7383
|
const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
|
|
6213
7384
|
const tracker = opts.stopRule ? createProgressTracker({ now }) : void 0;
|
|
6214
7385
|
let progressStopReason;
|
|
@@ -6288,20 +7459,32 @@ function driverAgent(opts) {
|
|
|
6288
7459
|
...scope.resume ? [{
|
|
6289
7460
|
role: "user",
|
|
6290
7461
|
content: resumeBrief(scope.resume, opts.priorCoordination)
|
|
7462
|
+
}] : hasPriorCoordination(opts.priorCoordination) ? [{
|
|
7463
|
+
role: "user",
|
|
7464
|
+
content: priorCoordinationBrief(opts.priorCoordination)
|
|
6291
7465
|
}] : []
|
|
6292
7466
|
],
|
|
6293
7467
|
maxTurns,
|
|
6294
|
-
hooks: {
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
7468
|
+
hooks: {
|
|
7469
|
+
beforeTurn: (_turn, messages) => {
|
|
7470
|
+
const pending = inbox.drain();
|
|
7471
|
+
if (pending.length > 0) messages.push({
|
|
7472
|
+
role: "user",
|
|
7473
|
+
content: inbox.fold(pending)
|
|
7474
|
+
});
|
|
7475
|
+
},
|
|
7476
|
+
stopBefore: () => {
|
|
7477
|
+
if (coord.isStopped() || scope.signal.aborted || poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)) return true;
|
|
7478
|
+
if (!opts.stopRule || !tracker) return false;
|
|
7479
|
+
const decision = progressStop(tracker, opts.stopRule, coord, scope, now, opts.stallAfterMs);
|
|
7480
|
+
if (!decision.stop) return false;
|
|
7481
|
+
if (progressStopReason === void 0) {
|
|
7482
|
+
progressStopReason = decision.reason;
|
|
7483
|
+
opts.onProgressStop?.(decision.reason);
|
|
7484
|
+
}
|
|
7485
|
+
return true;
|
|
6302
7486
|
}
|
|
6303
|
-
|
|
6304
|
-
} }
|
|
7487
|
+
}
|
|
6305
7488
|
});
|
|
6306
7489
|
await coord.drainResolved();
|
|
6307
7490
|
const submitted = coord.submittedResult();
|
|
@@ -6318,8 +7501,9 @@ function driverAgent(opts) {
|
|
|
6318
7501
|
/**
|
|
6319
7502
|
* The factual context a resumed driver starts from — everything the durable stores prove about
|
|
6320
7503
|
* the prior process(es): committed settlements, per-key states (completed / lost / failed),
|
|
6321
|
-
* re-armed waits, carried-over questions
|
|
6322
|
-
* the brain's first user-context on a resumed run so it continues from
|
|
7504
|
+
* re-armed waits, carried-over questions/findings/continuation receipts, and spend already paid.
|
|
7505
|
+
* Injected as the brain's first user-context on a resumed run so it continues from unresolved work;
|
|
7506
|
+
* old continuation receipts are evidence and are never auto-delivered.
|
|
6323
7507
|
*/
|
|
6324
7508
|
function resumeBrief(resume, prior) {
|
|
6325
7509
|
const lines = [
|
|
@@ -6339,13 +7523,36 @@ function resumeBrief(resume, prior) {
|
|
|
6339
7523
|
if (lost.length > 0) lines.push("", "Keys LOST in flight with the prior process — this is the unresolved work; spawn_agent with the same key starts a fresh attempt:", ...lost.map(([k, v]) => `- ${k} (prior attempt ${v.id}, ${v.label})`));
|
|
6340
7524
|
if (failed.length > 0) lines.push("", "Keys whose prior attempt FAILED (settled down) — spawn_agent with the same key retries:", ...failed.map(([k, v]) => `- ${k} (prior attempt ${v.id}, ${v.label})`));
|
|
6341
7525
|
if (resume.waits.length > 0) lines.push("", "Pending waits RE-ARMED on their original deadlines (they settle through await_event):", ...resume.waits.map((w) => `- ${w.label} (${w.spec.kind})`));
|
|
6342
|
-
|
|
6343
|
-
if (openQuestions.length > 0) lines.push("", "Questions carried over, still undecided (answer_question decides them; list_questions shows all):", ...openQuestions.map((q) => `- [${q.id}] from=${q.from}, urgency=${q.urgency}: ${q.question}`));
|
|
6344
|
-
if ((prior?.findings.length ?? 0) > 0) lines.push("", "Analyst findings from the prior process:", ...(prior?.findings ?? []).map((f) => `- ${f.analyst} on ${f.fromWorker}: ${safeJson(f.findings)}`));
|
|
7526
|
+
appendPriorCoordination(lines, prior);
|
|
6345
7527
|
const spent = resume.priorSpend;
|
|
6346
7528
|
lines.push("", "Budget the run ALREADY spent before this process (it counts toward the run total):", `- child work: tokens in=${spent.childWork.tokens.input} out=${spent.childWork.tokens.output}, usd=${spent.childWork.usd}, iterations=${spent.childWork.iterations}`, `- driver inference: tokens in=${spent.driverInference.tokens.input} out=${spent.driverInference.tokens.output}, usd=${spent.driverInference.usd}`);
|
|
6347
7529
|
return lines.join("\n");
|
|
6348
7530
|
}
|
|
7531
|
+
function hasPriorCoordination(prior) {
|
|
7532
|
+
return prior !== void 0 && (prior.questions.length > 0 || prior.findings.length > 0 || prior.continuations.length > 0 || prior.deliveryEvidence.length > 0);
|
|
7533
|
+
}
|
|
7534
|
+
function priorCoordinationBrief(prior) {
|
|
7535
|
+
const lines = [
|
|
7536
|
+
"PRIOR COORDINATION EVIDENCE: this logical supervisor ran in an earlier process.",
|
|
7537
|
+
"Use the evidence below as context. Never auto-deliver an old continuation; issue a new",
|
|
7538
|
+
"authorized instruction only when current live state still warrants it."
|
|
7539
|
+
];
|
|
7540
|
+
appendPriorCoordination(lines, prior);
|
|
7541
|
+
return lines.join("\n");
|
|
7542
|
+
}
|
|
7543
|
+
function appendPriorCoordination(lines, prior) {
|
|
7544
|
+
const openQuestions = (prior?.questions ?? []).filter((q) => q.status === "open" || q.status === "escalated");
|
|
7545
|
+
if (openQuestions.length > 0) lines.push("", "Questions carried over, still undecided (answer_question decides them; list_questions shows all):", ...openQuestions.map((q) => `- [${q.id}] from=${q.from}, urgency=${q.urgency}: ${q.question}`));
|
|
7546
|
+
if ((prior?.findings.length ?? 0) > 0) lines.push("", "Analyst findings from the prior process:", ...(prior?.findings ?? []).map((f) => `- ${f.analyst} on ${f.fromWorker}: ${safeJson(f.findings)}`));
|
|
7547
|
+
if ((prior?.continuations.length ?? 0) > 0) {
|
|
7548
|
+
const attempts = new Set((prior?.deliveryEvidence ?? []).filter((event) => event.type === "delivery-attempt").map((event) => event.attempt.receiptId));
|
|
7549
|
+
const outcomes = new Map((prior?.deliveryEvidence ?? []).filter((event) => event.type === "steer" || event.type === "answer").map((event) => [event.down.receiptId, event.down.outcome]));
|
|
7550
|
+
lines.push("", "Authorized continuations committed by the prior process (evidence only; never replayed automatically):", ...(prior?.continuations ?? []).map((continuation) => {
|
|
7551
|
+
const delivery = outcomes.get(continuation.receiptId) ?? (attempts.has(continuation.receiptId) ? "unknown-after-crash" : "not-attempted-before-crash");
|
|
7552
|
+
return `- receipt=${continuation.receiptId}, ${continuation.kind} → ${continuation.toWorker}, instruction=${continuation.instructionDigest}, delivery=${delivery}`;
|
|
7553
|
+
}));
|
|
7554
|
+
}
|
|
7555
|
+
}
|
|
6349
7556
|
/** Run a work tool. A throw is data to the driver (it can recover next turn), not a crash — fold
|
|
6350
7557
|
* the error back as a string result. null/undefined passes through (the caller treats it as "not
|
|
6351
7558
|
* handled" and falls to the coordination dispatch). */
|
|
@@ -7243,203 +8450,6 @@ function canonicalize(value) {
|
|
|
7243
8450
|
return out;
|
|
7244
8451
|
}
|
|
7245
8452
|
//#endregion
|
|
7246
|
-
//#region src/runtime/supervise/authoring.ts
|
|
7247
|
-
/**
|
|
7248
|
-
*
|
|
7249
|
-
* The supervisor's intelligence is AUTHORING the agents it spawns — not pressing buttons.
|
|
7250
|
-
*
|
|
7251
|
-
* Every agent here is three things: instructions (system prompt), tools, and a model — its
|
|
7252
|
-
* `AgentProfile`. The supervisor's job is to WRITE those profiles: read the task, decompose it,
|
|
7253
|
-
* and for each sub-task author a tailored worker recipe. `supervisorInstructions` is the how-to the
|
|
7254
|
-
* supervisor reads (its system prompt); `authoredWorker` builds a worker AGENT from a profile the
|
|
7255
|
-
* supervisor authored — the authored systemPrompt + model shape the worker's call.
|
|
7256
|
-
*
|
|
7257
|
-
* The skill is the single OPTIMIZABLE surface: edit it → the supervisor designs better agents.
|
|
7258
|
-
* That is the self-improvement lever (the prompt/skill lever), not the execution plumbing.
|
|
7259
|
-
*
|
|
7260
|
-
* @experimental
|
|
7261
|
-
*/
|
|
7262
|
-
/** Narrow an untyped `spawn_agent` profile argument to an `AuthoredProfile`, or null if the
|
|
7263
|
-
* supervisor failed to author one (empty/placeholder profile — a skill violation worth catching). */
|
|
7264
|
-
function asAuthoredProfile(raw) {
|
|
7265
|
-
const p = raw;
|
|
7266
|
-
if (!p || typeof p.systemPrompt !== "string" || p.systemPrompt.trim().length === 0) return null;
|
|
7267
|
-
return {
|
|
7268
|
-
name: typeof p.name === "string" && p.name.length > 0 ? p.name : "worker",
|
|
7269
|
-
systemPrompt: p.systemPrompt,
|
|
7270
|
-
...typeof p.model === "string" ? { model: p.model } : {}
|
|
7271
|
-
};
|
|
7272
|
-
}
|
|
7273
|
-
/** The supervisor SKILL — the how-to the supervisor reads (its system prompt). THE optimizable
|
|
7274
|
-
* surface: editing this changes how the supervisor designs every agent it spawns. */
|
|
7275
|
-
function supervisorInstructions(opts) {
|
|
7276
|
-
return [
|
|
7277
|
-
"You are a SUPERVISOR. You do NOT do the work yourself — your job is to DESIGN and DRIVE specialist worker agents.",
|
|
7278
|
-
"",
|
|
7279
|
-
"For the task you are given:",
|
|
7280
|
-
"1. DECOMPOSE it into the smallest set of sub-tasks a single focused worker can each deliver.",
|
|
7281
|
-
"2. For EACH sub-task, AUTHOR a worker by calling spawn_agent with a COMPLETE `profile`:",
|
|
7282
|
-
" • name: a short id for the worker.",
|
|
7283
|
-
" • systemPrompt: rich, specific instructions for THIS sub-task — tell the worker exactly what to produce, how to use its tools fully, and what \"done\" means. Never a one-liner; write the prompt a power-user would write.",
|
|
7284
|
-
" • model: the model best suited to this sub-task (omit to use the default).",
|
|
7285
|
-
" NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
|
|
7286
|
-
"3. await_event (kinds:['settled']) to collect each worker. Its result says valid:true only if the deployable check passed.",
|
|
7287
|
-
"4. If a worker did NOT deliver, AUTHOR A NEW worker whose systemPrompt names the SPECIFIC failure and how to fix it — never just retry the same prompt.",
|
|
7288
|
-
"5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself — only a delivered (valid:true) worker counts.",
|
|
7289
|
-
...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
|
|
7290
|
-
].join("\n");
|
|
7291
|
-
}
|
|
7292
|
-
/** Build a worker AGENT from a profile the supervisor authored: the authored `systemPrompt` +
|
|
7293
|
-
* `model` shape the worker's one model call; the deliverable gates settlement (valid ⟺ delivered). */
|
|
7294
|
-
function authoredWorker(profile, opts) {
|
|
7295
|
-
let artifact;
|
|
7296
|
-
const model = profile.model ?? opts.cfg.model;
|
|
7297
|
-
const gated = gateOnDeliverable({
|
|
7298
|
-
runtime: "router",
|
|
7299
|
-
async execute(_t, signal) {
|
|
7300
|
-
const res = await routerChatWithUsage({
|
|
7301
|
-
...opts.cfg,
|
|
7302
|
-
model
|
|
7303
|
-
}, [{
|
|
7304
|
-
role: "system",
|
|
7305
|
-
content: profile.systemPrompt
|
|
7306
|
-
}, {
|
|
7307
|
-
role: "user",
|
|
7308
|
-
content: opts.taskPrompt
|
|
7309
|
-
}], {
|
|
7310
|
-
temperature: opts.temperature ?? .4,
|
|
7311
|
-
...signal ? { signal } : {}
|
|
7312
|
-
});
|
|
7313
|
-
artifact = {
|
|
7314
|
-
outRef: contentAddress(res.content),
|
|
7315
|
-
out: res.content,
|
|
7316
|
-
spent: {
|
|
7317
|
-
iterations: 1,
|
|
7318
|
-
tokens: res.usage ?? {
|
|
7319
|
-
input: 0,
|
|
7320
|
-
output: 0
|
|
7321
|
-
},
|
|
7322
|
-
usd: res.costUsd ?? 0,
|
|
7323
|
-
ms: 0
|
|
7324
|
-
}
|
|
7325
|
-
};
|
|
7326
|
-
return artifact;
|
|
7327
|
-
},
|
|
7328
|
-
teardown: () => Promise.resolve({ destroyed: true }),
|
|
7329
|
-
resultArtifact: () => {
|
|
7330
|
-
if (!artifact) throw new Error("authoredWorker: resultArtifact read before execute");
|
|
7331
|
-
return artifact;
|
|
7332
|
-
}
|
|
7333
|
-
}, opts.deliverable);
|
|
7334
|
-
const spec = {
|
|
7335
|
-
profile: { name: profile.name },
|
|
7336
|
-
harness: null,
|
|
7337
|
-
executor: gated
|
|
7338
|
-
};
|
|
7339
|
-
return {
|
|
7340
|
-
name: profile.name,
|
|
7341
|
-
act: async () => "",
|
|
7342
|
-
executorSpec: spec
|
|
7343
|
-
};
|
|
7344
|
-
}
|
|
7345
|
-
/** Default thresholds for `ProfileRichnessThresholds` — 600 chars / 6 lines minimum system prompt. */
|
|
7346
|
-
const defaultProfileRichnessThresholds = {
|
|
7347
|
-
minSystemPromptChars: 600,
|
|
7348
|
-
minSystemPromptLines: 6
|
|
7349
|
-
};
|
|
7350
|
-
/** Read the system prompt from any authored shape: canonical `prompt.systemPrompt`, the sandbox
|
|
7351
|
-
* `prompt.system` convention, or a bare-string `prompt`. */
|
|
7352
|
-
function resolveSystemPrompt(profile) {
|
|
7353
|
-
const pr = profile.prompt;
|
|
7354
|
-
if (typeof pr === "string") return pr;
|
|
7355
|
-
if (pr && typeof pr === "object") {
|
|
7356
|
-
const o = pr;
|
|
7357
|
-
if (typeof o.systemPrompt === "string") return o.systemPrompt;
|
|
7358
|
-
if (typeof o.system === "string") return o.system;
|
|
7359
|
-
}
|
|
7360
|
-
return "";
|
|
7361
|
-
}
|
|
7362
|
-
/** OBSERVE one authored `AgentProfile` and score its richness (no judge verdict is read). The task
|
|
7363
|
-
* context (`needsMcp`) lets a domain say "this work needs a data/tool MCP" so a missing MCP counts. */
|
|
7364
|
-
function assessAuthoredProfile(profile, opts) {
|
|
7365
|
-
const th = {
|
|
7366
|
-
...defaultProfileRichnessThresholds,
|
|
7367
|
-
...opts?.thresholds ?? {}
|
|
7368
|
-
};
|
|
7369
|
-
const systemPrompt = resolveSystemPrompt(profile);
|
|
7370
|
-
const trimmed = systemPrompt.trim();
|
|
7371
|
-
const systemPromptChars = trimmed.length;
|
|
7372
|
-
const systemPromptLines = trimmed ? trimmed.split("\n").filter((l) => l.trim().length > 0).length : 0;
|
|
7373
|
-
const sentenceCount = trimmed ? (trimmed.match(/[.!?](\s|$)/g) ?? []).length || (trimmed ? 1 : 0) : 0;
|
|
7374
|
-
const hasDescription = typeof profile.description === "string" && profile.description.trim().length > 0;
|
|
7375
|
-
const tools = profile.tools;
|
|
7376
|
-
const hasTools = !!tools && Object.keys(tools).length > 0;
|
|
7377
|
-
const skills = profile.resources?.skills;
|
|
7378
|
-
const hasSkills = Array.isArray(skills) && skills.length > 0;
|
|
7379
|
-
const mcp = profile.mcp;
|
|
7380
|
-
const hasMcp = !!mcp && Object.keys(mcp).length > 0;
|
|
7381
|
-
const subagents = profile.subagents;
|
|
7382
|
-
const hasSubagents = !!subagents && Object.keys(subagents).length > 0;
|
|
7383
|
-
const reasons = [];
|
|
7384
|
-
const promptThin = systemPromptChars < th.minSystemPromptChars || systemPromptLines < th.minSystemPromptLines;
|
|
7385
|
-
if (promptThin) reasons.push(`system prompt is thin (${systemPromptChars} chars, ${systemPromptLines} lines; need ≥${th.minSystemPromptChars} chars and ≥${th.minSystemPromptLines} lines)`);
|
|
7386
|
-
if (!hasTools) reasons.push("no tools granted (a worker can only act through the tools you grant it)");
|
|
7387
|
-
if (!hasSkills) reasons.push("no skills attached (no reusable how-to notes injected)");
|
|
7388
|
-
if (opts?.needsMcp && !hasMcp) reasons.push("no MCP server, but the task needs data/tool access");
|
|
7389
|
-
const signals = [
|
|
7390
|
-
!promptThin,
|
|
7391
|
-
hasTools,
|
|
7392
|
-
hasSkills,
|
|
7393
|
-
hasDescription,
|
|
7394
|
-
opts?.needsMcp ? hasMcp : true
|
|
7395
|
-
];
|
|
7396
|
-
const richness = signals.filter(Boolean).length / signals.length;
|
|
7397
|
-
const thin = promptThin || !hasTools && !hasSkills && !hasMcp;
|
|
7398
|
-
return {
|
|
7399
|
-
name: profile.name ?? "worker",
|
|
7400
|
-
systemPrompt,
|
|
7401
|
-
systemPromptChars,
|
|
7402
|
-
systemPromptLines,
|
|
7403
|
-
sentenceCount,
|
|
7404
|
-
hasDescription,
|
|
7405
|
-
hasTools,
|
|
7406
|
-
hasSkills,
|
|
7407
|
-
hasMcp,
|
|
7408
|
-
hasSubagents,
|
|
7409
|
-
richness,
|
|
7410
|
-
thin,
|
|
7411
|
-
reasons
|
|
7412
|
-
};
|
|
7413
|
-
}
|
|
7414
|
-
/** Turn a {@link ProfileRichness} verdict into a bus-routable `AnalystFinding` (area `profile-quality`).
|
|
7415
|
-
* Severity scales with thinness; the recommended action names the MISSING lever so the supervisor can
|
|
7416
|
-
* re-author. `subject` = the worker name so per-worker findings diff cleanly across re-authors. */
|
|
7417
|
-
function profileRichnessFinding(richness, opts) {
|
|
7418
|
-
const analyst_id = opts?.analystId ?? "profile-richness";
|
|
7419
|
-
const subject = richness.name;
|
|
7420
|
-
const claim = richness.thin ? `Worker "${richness.name}" was authored as a THIN profile: ${richness.reasons.join("; ")}.` : `Worker "${richness.name}" was authored as a rich profile (richness ${(richness.richness * 100).toFixed(0)}%).`;
|
|
7421
|
-
return makeFinding({
|
|
7422
|
-
analyst_id,
|
|
7423
|
-
severity: richness.thin ? richness.richness < .25 ? "high" : "medium" : "info",
|
|
7424
|
-
area: "profile-quality",
|
|
7425
|
-
claim,
|
|
7426
|
-
subject,
|
|
7427
|
-
confidence: .9,
|
|
7428
|
-
evidence_refs: [{
|
|
7429
|
-
kind: "metric",
|
|
7430
|
-
uri: `profile:${subject}`,
|
|
7431
|
-
excerpt: `chars=${richness.systemPromptChars} lines=${richness.systemPromptLines} tools=${richness.hasTools} skills=${richness.hasSkills} mcp=${richness.hasMcp} richness=${richness.richness.toFixed(2)}`
|
|
7432
|
-
}],
|
|
7433
|
-
...richness.thin ? { recommended_action: `Re-author "${richness.name}" with: ${richness.reasons.join("; ")}.` } : {},
|
|
7434
|
-
id_basis: computeFindingId({
|
|
7435
|
-
analyst_id,
|
|
7436
|
-
area: "profile-quality",
|
|
7437
|
-
subject,
|
|
7438
|
-
claim: `richness:${richness.thin ? "thin" : "rich"}`
|
|
7439
|
-
})
|
|
7440
|
-
});
|
|
7441
|
-
}
|
|
7442
|
-
//#endregion
|
|
7443
8453
|
//#region src/runtime/supervise/delegate.ts
|
|
7444
8454
|
/**
|
|
7445
8455
|
*
|
|
@@ -7468,15 +8478,15 @@ const defaultDelegateBudget = {
|
|
|
7468
8478
|
maxIterations: 50,
|
|
7469
8479
|
maxTokens: 2e5
|
|
7470
8480
|
};
|
|
7471
|
-
/** Build the DEFAULT authoring supervisor profile: a router-brained supervisor (`harness:
|
|
8481
|
+
/** Build the DEFAULT authoring supervisor profile: a router-brained supervisor (`harness: cli-base`)
|
|
7472
8482
|
* whose standing instruction IS the authoring-agent-profiles skill, so it decomposes the intent and
|
|
7473
8483
|
* AUTHORS a worker profile per sub-task. No worker profile is baked in here. */
|
|
7474
8484
|
function authoringSupervisorProfile(model, override) {
|
|
7475
8485
|
return {
|
|
7476
8486
|
name: override?.name ?? "delegate-supervisor",
|
|
7477
|
-
harness:
|
|
7478
|
-
...model ? { model } : {},
|
|
7479
|
-
systemPrompt: override?.systemPrompt ?? supervisorInstructions()
|
|
8487
|
+
harness: "cli-base",
|
|
8488
|
+
...model ? { model: { default: model } } : {},
|
|
8489
|
+
prompt: { systemPrompt: override?.systemPrompt ?? supervisorInstructions() }
|
|
7480
8490
|
};
|
|
7481
8491
|
}
|
|
7482
8492
|
/**
|
|
@@ -7561,11 +8571,19 @@ function validateDelegateArgs(raw) {
|
|
|
7561
8571
|
* conserved `spentTotal`, so the agent always learns the cost — even on a no-winner, never a faked
|
|
7562
8572
|
* output and never a fabricated zero spend. */
|
|
7563
8573
|
function toDelegateResult(result) {
|
|
7564
|
-
if (result.kind === "no-winner")
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
8574
|
+
if (result.kind === "no-winner") {
|
|
8575
|
+
const rejection = result.error;
|
|
8576
|
+
const error = typeof rejection?.name === "string" && typeof rejection.message === "string" ? {
|
|
8577
|
+
name: rejection.name,
|
|
8578
|
+
message: rejection.message
|
|
8579
|
+
} : void 0;
|
|
8580
|
+
return {
|
|
8581
|
+
status: "no-winner",
|
|
8582
|
+
reason: result.reason,
|
|
8583
|
+
...error ? { error } : {},
|
|
8584
|
+
spentTotal: result.spentTotal
|
|
8585
|
+
};
|
|
8586
|
+
}
|
|
7569
8587
|
return {
|
|
7570
8588
|
status: "winner",
|
|
7571
8589
|
out: result.out,
|
|
@@ -8325,6 +9343,7 @@ async function serveCoordinationMcp(opts) {
|
|
|
8325
9343
|
scope: opts.scope,
|
|
8326
9344
|
blobs: opts.blobs,
|
|
8327
9345
|
makeWorkerAgent: opts.makeWorkerAgent,
|
|
9346
|
+
...opts.authorizeDownMessage ? { authorizeDownMessage: opts.authorizeDownMessage } : {},
|
|
8328
9347
|
perWorker: opts.perWorker,
|
|
8329
9348
|
...opts.deliverable ? { deliverable: opts.deliverable } : {},
|
|
8330
9349
|
...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
|
|
@@ -8334,11 +9353,13 @@ async function serveCoordinationMcp(opts) {
|
|
|
8334
9353
|
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
8335
9354
|
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
8336
9355
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
9356
|
+
...opts.replaySettlements ? { replaySettlements: true } : {},
|
|
8337
9357
|
...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {},
|
|
8338
9358
|
...opts.priorQuestions?.length ? { priorQuestions: opts.priorQuestions } : {}
|
|
8339
9359
|
});
|
|
9360
|
+
await coord.ready();
|
|
8340
9361
|
const mcp = createMcpServer({
|
|
8341
|
-
extraTools: coord.tools,
|
|
9362
|
+
extraTools: [...coord.tools, ...opts.nodeTools ?? []],
|
|
8342
9363
|
serverName: "coordination"
|
|
8343
9364
|
});
|
|
8344
9365
|
const server = createServer((req, res) => {
|
|
@@ -8510,45 +9531,81 @@ function assertCoordinationBinding(binding) {
|
|
|
8510
9531
|
}
|
|
8511
9532
|
/** Build a supervisor `Agent` from its profile: the brain resolves from `profile.harness` (backend-as-data), the same resolution rule as every worker. */
|
|
8512
9533
|
function supervisorAgent(profile, deps) {
|
|
8513
|
-
const
|
|
8514
|
-
const
|
|
8515
|
-
const
|
|
9534
|
+
const stableProfile = detachedSnapshot(profile, "supervisorAgent profile");
|
|
9535
|
+
const resolveTools = deps.resolveSupervisorTools;
|
|
9536
|
+
const observeNodeEvent = deps.observeNodeEvent;
|
|
9537
|
+
const nodeContextSeed = deps.nodeContext === void 0 ? void 0 : detachedSnapshot(deps.nodeContext, "supervisorAgent node context");
|
|
9538
|
+
if ((resolveTools || observeNodeEvent) && !nodeContextSeed) throw new ValidationError("supervisorAgent: nodeContext is required with resolveSupervisorTools or observeNodeEvent");
|
|
9539
|
+
const name = stableProfile.name ?? "supervisor";
|
|
9540
|
+
const harness = stableProfile.harness === void 0 || stableProfile.harness === null || stableProfile.harness === "cli-base" ? null : stableProfile.harness;
|
|
9541
|
+
const profilePrompt = resolveSupervisorSystemPrompt(stableProfile);
|
|
8516
9542
|
const coordination = deps.coordination ? { ...deps.coordination } : void 0;
|
|
8517
9543
|
assertCoordinationBinding(coordination);
|
|
8518
9544
|
if (harness === null && coordination !== void 0) throw new ConfigError("supervisorAgent: coordination binding is only meaningful for a harness-brained supervisor (profile.harness set). A router-brained supervisor calls the coordination verbs in process and serves no MCP, so this binding would be silently ignored.");
|
|
8519
|
-
if (harness !== null && deps.compaction) throw new ValidationError("supervisorAgent: compaction is only supported for router-brained supervisors (profile.harness
|
|
8520
|
-
if (harness === null)
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
9545
|
+
if (harness !== null && deps.compaction) throw new ValidationError("supervisorAgent: compaction is only supported for router-brained supervisors (profile.harness omitted or cli-base)");
|
|
9546
|
+
if (harness === null) {
|
|
9547
|
+
const brain = deps.brain ?? routerBrainFromProfile(stableProfile, deps);
|
|
9548
|
+
const inbox = createInbox();
|
|
9549
|
+
const build = (priorCoordination, nodeTools, onEvent) => driverAgent({
|
|
9550
|
+
name,
|
|
9551
|
+
brain,
|
|
9552
|
+
blobs: deps.blobs,
|
|
9553
|
+
makeWorkerAgent: deps.makeWorkerAgent,
|
|
9554
|
+
...deps.authorizeDownMessage ? { authorizeDownMessage: deps.authorizeDownMessage } : {},
|
|
9555
|
+
perWorker: deps.perWorker,
|
|
9556
|
+
systemPrompt: resolveSupervisorSystemPrompt(stableProfile, defaultSupervisorPrompt) ?? defaultSupervisorPrompt,
|
|
9557
|
+
...deps.deliverable ? { deliverable: deps.deliverable } : {},
|
|
9558
|
+
...nodeTools?.length ? { nodeTools } : {},
|
|
9559
|
+
...deps.maxLiveWorkers !== void 0 ? { maxLiveWorkers: deps.maxLiveWorkers } : {},
|
|
9560
|
+
...deps.extraTools ? { extraTools: deps.extraTools } : {},
|
|
9561
|
+
...deps.executeExtraTool ? { executeExtraTool: deps.executeExtraTool } : {},
|
|
9562
|
+
...deps.analysts ? { analysts: deps.analysts } : {},
|
|
9563
|
+
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
|
|
9564
|
+
...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
|
|
9565
|
+
...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {},
|
|
9566
|
+
...deps.stopRule ? { stopRule: deps.stopRule } : {},
|
|
9567
|
+
...deps.onProgressStop ? { onProgressStop: deps.onProgressStop } : {},
|
|
9568
|
+
...deps.maxTurns !== void 0 ? { maxTurns: deps.maxTurns } : {},
|
|
9569
|
+
...deps.compaction ? { compaction: deps.compaction } : {},
|
|
9570
|
+
...onEvent ? { onEvent } : {},
|
|
9571
|
+
...deps.replaySettlements ? { replaySettlements: true } : {},
|
|
9572
|
+
...priorCoordination ? { priorCoordination } : {},
|
|
9573
|
+
...deps.finalizer ? { finalizer: deps.finalizer } : {},
|
|
9574
|
+
inbox
|
|
9575
|
+
});
|
|
9576
|
+
if (!deps.loadPriorCoordination && !resolveTools && !observeNodeEvent) return build(deps.priorCoordination, void 0, deps.onEvent);
|
|
9577
|
+
return {
|
|
9578
|
+
name,
|
|
9579
|
+
deliver(message) {
|
|
9580
|
+
return inbox.deliver(message);
|
|
9581
|
+
},
|
|
9582
|
+
async act(task, scope) {
|
|
9583
|
+
const context = nodeContextSeed ? supervisorNodeContext(nodeContextSeed, stableProfile, task, scope) : void 0;
|
|
9584
|
+
const priorCoordination = await deps.loadPriorCoordination?.();
|
|
9585
|
+
const nodeTools = resolveTools && context ? await bindSupervisorTools(resolveTools, context, scope.signal) : void 0;
|
|
9586
|
+
const onEvent = bindSupervisorNodeObserver(context, observeNodeEvent, deps.onEvent);
|
|
9587
|
+
return build(priorCoordination, nodeTools, onEvent).act(task, scope);
|
|
9588
|
+
}
|
|
9589
|
+
};
|
|
9590
|
+
}
|
|
8543
9591
|
const driveHarness = deps.driveHarness;
|
|
8544
9592
|
if (!driveHarness) throw new ValidationError(`supervisorAgent: profile.harness="${harness}" needs deps.driveHarness (how to run the harness with the coordination MCP mounted)`);
|
|
8545
|
-
|
|
9593
|
+
const deliver = driveHarness.deliver?.bind(driveHarness);
|
|
9594
|
+
const externalAgent = {
|
|
8546
9595
|
name,
|
|
9596
|
+
...deliver ? { deliver(message) {
|
|
9597
|
+
return deliver(message);
|
|
9598
|
+
} } : {},
|
|
8547
9599
|
async act(task, scope) {
|
|
9600
|
+
const context = nodeContextSeed ? supervisorNodeContext(nodeContextSeed, stableProfile, task, scope) : void 0;
|
|
9601
|
+
const priorCoordination = deps.loadPriorCoordination ? await deps.loadPriorCoordination() : deps.priorCoordination;
|
|
9602
|
+
const nodeTools = resolveTools && context ? await bindSupervisorTools(resolveTools, context, scope.signal) : void 0;
|
|
9603
|
+
const onEvent = bindSupervisorNodeObserver(context, observeNodeEvent, deps.onEvent);
|
|
8548
9604
|
const mcp = await serveCoordinationMcp({
|
|
8549
9605
|
scope,
|
|
8550
9606
|
blobs: deps.blobs,
|
|
8551
9607
|
makeWorkerAgent: deps.makeWorkerAgent,
|
|
9608
|
+
...deps.authorizeDownMessage ? { authorizeDownMessage: deps.authorizeDownMessage } : {},
|
|
8552
9609
|
perWorker: deps.perWorker,
|
|
8553
9610
|
...coordination?.host !== void 0 ? { host: coordination.host } : {},
|
|
8554
9611
|
...coordination?.port !== void 0 ? { port: coordination.port } : {},
|
|
@@ -8559,17 +9616,24 @@ function supervisorAgent(profile, deps) {
|
|
|
8559
9616
|
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
|
|
8560
9617
|
...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
|
|
8561
9618
|
...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {},
|
|
8562
|
-
...
|
|
8563
|
-
...deps.
|
|
9619
|
+
...onEvent ? { onEvent } : {},
|
|
9620
|
+
...deps.replaySettlements ? { replaySettlements: true } : {},
|
|
9621
|
+
...priorCoordination?.questions.length ? { priorQuestions: priorCoordination.questions } : {},
|
|
9622
|
+
...nodeTools?.length ? { nodeTools } : {}
|
|
8564
9623
|
});
|
|
8565
9624
|
try {
|
|
8566
9625
|
try {
|
|
8567
9626
|
await driveHarness({
|
|
8568
|
-
profile,
|
|
9627
|
+
profile: stableProfile,
|
|
8569
9628
|
...profilePrompt !== void 0 ? { systemPrompt: profilePrompt } : {},
|
|
8570
9629
|
task,
|
|
8571
9630
|
scope,
|
|
8572
|
-
coordinationMcpUrl: mcp.url
|
|
9631
|
+
coordinationMcpUrl: mcp.url,
|
|
9632
|
+
coordinationTools: (nodeTools ?? []).map(({ name, description, inputSchema }) => ({
|
|
9633
|
+
name,
|
|
9634
|
+
description,
|
|
9635
|
+
inputSchema
|
|
9636
|
+
}))
|
|
8573
9637
|
});
|
|
8574
9638
|
} catch (error) {
|
|
8575
9639
|
if (!mcp.submittedResult()) throw error;
|
|
@@ -8588,9 +9652,57 @@ function supervisorAgent(profile, deps) {
|
|
|
8588
9652
|
}
|
|
8589
9653
|
}
|
|
8590
9654
|
};
|
|
9655
|
+
const runtime = runtimeOwnedScopeOwnerRuntime(driveHarness);
|
|
9656
|
+
return runtime === void 0 ? externalAgent : attestRuntimeOwnedScopeOwner(externalAgent, runtime);
|
|
9657
|
+
}
|
|
9658
|
+
function supervisorNodeContext(seed, profile, task, scope) {
|
|
9659
|
+
return detachedSnapshot({
|
|
9660
|
+
...seed,
|
|
9661
|
+
nodeId: scope.view.root,
|
|
9662
|
+
profile,
|
|
9663
|
+
task
|
|
9664
|
+
}, "supervisorAgent trusted node context");
|
|
9665
|
+
}
|
|
9666
|
+
async function bindSupervisorTools(resolveTools, context, signal) {
|
|
9667
|
+
const resolved = await resolveTools(context);
|
|
9668
|
+
if (!Array.isArray(resolved)) throw new ValidationError("supervisorAgent: resolveSupervisorTools must return an array");
|
|
9669
|
+
const invocationContext = Object.freeze({
|
|
9670
|
+
...context,
|
|
9671
|
+
signal
|
|
9672
|
+
});
|
|
9673
|
+
const names = new Set(coordinationVerbNames);
|
|
9674
|
+
return Object.freeze(resolved.map((rawTool, index) => {
|
|
9675
|
+
if (typeof rawTool !== "object" || rawTool === null || Array.isArray(rawTool)) throw new ValidationError(`supervisorAgent: resolved tool at index ${index} must be a descriptor`);
|
|
9676
|
+
const { name, description, inputSchema, handler } = rawTool;
|
|
9677
|
+
if (typeof name !== "string" || name.length === 0) throw new ValidationError(`supervisorAgent: resolved tool at index ${index} needs a non-empty name`);
|
|
9678
|
+
if (names.has(name)) throw new ValidationError(`supervisorAgent: resolved tool "${name}" collides with a coordination verb or another resolved tool`);
|
|
9679
|
+
names.add(name);
|
|
9680
|
+
if (typeof description !== "string" || description.length === 0) throw new ValidationError(`supervisorAgent: resolved tool "${name}" needs a description`);
|
|
9681
|
+
if (typeof inputSchema !== "object" || inputSchema === null || Array.isArray(inputSchema)) throw new ValidationError(`supervisorAgent: resolved tool "${name}" needs an inputSchema`);
|
|
9682
|
+
if (typeof handler !== "function") throw new ValidationError(`supervisorAgent: resolved tool "${name}" needs a handler`);
|
|
9683
|
+
const descriptor = detachedSnapshot({
|
|
9684
|
+
name,
|
|
9685
|
+
description,
|
|
9686
|
+
inputSchema
|
|
9687
|
+
}, `supervisorAgent resolved tool ${JSON.stringify(name)}`);
|
|
9688
|
+
return Object.freeze({
|
|
9689
|
+
...descriptor,
|
|
9690
|
+
handler: (raw) => handler(detachedSnapshot(raw, `supervisorAgent tool ${JSON.stringify(name)} input`), invocationContext)
|
|
9691
|
+
});
|
|
9692
|
+
}));
|
|
9693
|
+
}
|
|
9694
|
+
function bindSupervisorNodeObserver(context, observeNodeEvent, onEvent) {
|
|
9695
|
+
if (!observeNodeEvent && !onEvent) return void 0;
|
|
9696
|
+
return async (event, record) => {
|
|
9697
|
+
if (observeNodeEvent) {
|
|
9698
|
+
if (!context) throw new ValidationError("supervisorAgent: observeNodeEvent has no trusted node context");
|
|
9699
|
+
await observeNodeEvent(context, event, record);
|
|
9700
|
+
}
|
|
9701
|
+
await onEvent?.(event, record);
|
|
9702
|
+
};
|
|
8591
9703
|
}
|
|
8592
9704
|
function routerBrainFromProfile(profile, deps) {
|
|
8593
|
-
if (!deps.router) throw new ValidationError("supervisorAgent: a router-brained supervisor (harness
|
|
9705
|
+
if (!deps.router) throw new ValidationError("supervisorAgent: a router-brained supervisor (harness omitted or cli-base) needs deps.router (or deps.brain)");
|
|
8594
9706
|
const modelId = resolveSupervisorModelId(profile);
|
|
8595
9707
|
return routerBrain({
|
|
8596
9708
|
...deps.router,
|
|
@@ -8600,6 +9712,15 @@ function routerBrainFromProfile(profile, deps) {
|
|
|
8600
9712
|
//#endregion
|
|
8601
9713
|
//#region src/runtime/supervise/supervise.ts
|
|
8602
9714
|
/**
|
|
9715
|
+
* `supervise` — the one-call "just invoke the supervisor". Builds + runs a supervisor from its
|
|
9716
|
+
* profile with sensible defaults, so the common case is `supervise(profile, task, { backend, budget })`
|
|
9717
|
+
* instead of hand-wiring `blobs` / `perWorker` / `journal` / `executors` / `maxDepth`. The raw seams
|
|
9718
|
+
* (`supervisorAgent` + `createSupervisor().run`) stay available for power use.
|
|
9719
|
+
*
|
|
9720
|
+
* `workerFromBackend` derives the worker seam (`makeWorkerAgent`) from a backend config + an optional
|
|
9721
|
+
* completion oracle — so "where the workers run" is one data choice, not a hand-rolled factory.
|
|
9722
|
+
*/
|
|
9723
|
+
/**
|
|
8603
9724
|
* Build the worker seam from a backend (WHERE workers run) + an optional completion oracle (the
|
|
8604
9725
|
* deliverable check that makes "settled ⟺ delivered" true — the guard against "ran but didn't
|
|
8605
9726
|
* deliver"). The ONE place a backend becomes a spawnable worker.
|
|
@@ -8613,28 +9734,291 @@ function routerBrainFromProfile(profile, deps) {
|
|
|
8613
9734
|
* ahead of the worker seam.
|
|
8614
9735
|
*/
|
|
8615
9736
|
function workerFromBackend(backend, deliverable, seams) {
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
9737
|
+
const capturedBackend = captureReusableExecutorConfig(backend, "workerFromBackend");
|
|
9738
|
+
const unscopedNamespace = randomUUID();
|
|
9739
|
+
let unscopedOrdinal = 0;
|
|
9740
|
+
return (rawProfile, spawnContext) => {
|
|
9741
|
+
const parsed = agentProfileSchema.safeParse(canonicalizeAuthoredProfile(rawProfile));
|
|
9742
|
+
if (!parsed.success) throw new ValidationError(`workerFromBackend: invalid AgentProfile: ${parsed.error.message}`);
|
|
9743
|
+
const profile = parsed.data;
|
|
9744
|
+
assertBackendProfileMaterialization(profile, capturedBackend, "workerFromBackend");
|
|
9745
|
+
const name = profile.name ?? "worker";
|
|
9746
|
+
const assignmentId = spawnContext?.assignmentId ?? `unscoped:${unscopedNamespace}:${unscopedOrdinal++}`;
|
|
9747
|
+
const baseFactory = createExecutor(bindReusableExecutorExecutionId(capturedBackend, externalExecutionId("supervised-worker", { assignmentId })));
|
|
9748
|
+
const executorFactory = (spec, ctx) => {
|
|
9749
|
+
const extraSeams = seams?.();
|
|
9750
|
+
const built = baseFactory(spec, extraSeams === void 0 ? ctx : {
|
|
9751
|
+
...ctx,
|
|
9752
|
+
seams: {
|
|
9753
|
+
...extraSeams,
|
|
9754
|
+
...ctx.seams
|
|
9755
|
+
}
|
|
9756
|
+
});
|
|
9757
|
+
return deliverable ? gateOnDeliverable(built, deliverable) : built;
|
|
8626
9758
|
};
|
|
8627
|
-
const built = createExecutor(backend)(spec, ctx);
|
|
8628
|
-
const executor = deliverable ? gateOnDeliverable(built, deliverable) : built;
|
|
8629
9759
|
return {
|
|
8630
9760
|
name,
|
|
8631
9761
|
act: async () => "",
|
|
8632
9762
|
executorSpec: {
|
|
8633
|
-
|
|
8634
|
-
|
|
9763
|
+
profile,
|
|
9764
|
+
harness: null,
|
|
9765
|
+
executorFactory,
|
|
9766
|
+
...spawnContext?.execution ? { execution: spawnContext.execution } : {}
|
|
9767
|
+
}
|
|
9768
|
+
};
|
|
9769
|
+
};
|
|
9770
|
+
}
|
|
9771
|
+
function externalExecutionId(kind, identity) {
|
|
9772
|
+
return `${kind}-${canonicalCandidateDigest({
|
|
9773
|
+
kind,
|
|
9774
|
+
identity
|
|
9775
|
+
}).slice(7)}`;
|
|
9776
|
+
}
|
|
9777
|
+
function backendProfileMaterialization(backend) {
|
|
9778
|
+
switch (backend.backend) {
|
|
9779
|
+
case "bridge":
|
|
9780
|
+
case "sandbox":
|
|
9781
|
+
case "provider": return fullProfileMaterialization;
|
|
9782
|
+
case "cli-worktree": return backend.bridge ? fullProfileMaterialization : worktreeCliProfileMaterialization;
|
|
9783
|
+
case "router":
|
|
9784
|
+
case "router-tools":
|
|
9785
|
+
case "pi": return promptModelProfileMaterialization;
|
|
9786
|
+
case "cli": return controlProfileMaterialization;
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
function assertProfileContract(profile, contract, context) {
|
|
9790
|
+
assertProfileMaterialization({
|
|
9791
|
+
contract,
|
|
9792
|
+
changedAxes: profileMaterializationAxes(profile),
|
|
9793
|
+
context
|
|
9794
|
+
});
|
|
9795
|
+
}
|
|
9796
|
+
function assertBackendProfileMaterialization(profile, backend, context) {
|
|
9797
|
+
assertProfileContract(profile, backendProfileMaterialization(backend), context);
|
|
9798
|
+
}
|
|
9799
|
+
/**
|
|
9800
|
+
* The ROOT router-brained supervisor's materialization claim. The router arm consumes the
|
|
9801
|
+
* identity fields, the resolved system prompt (`systemPrompt` + `prompt.instructions` +
|
|
9802
|
+
* `resources.instructions`), and the resolved model id (`model.default`); the remaining model
|
|
9803
|
+
* HINTS (`small`, `provider`, `reasoningEffort`, `metadata`) are accepted as documented-unhonored
|
|
9804
|
+
* router-arm material (`supervisorAgent`'s contract table states each one), so a canonical
|
|
9805
|
+
* profile carrying ordinary hints is not refused. Every behavioral axis — tools, permissions,
|
|
9806
|
+
* MCP, hooks, modes, subagents, file resources — still fails loud before any compute.
|
|
9807
|
+
*/
|
|
9808
|
+
const routerSupervisorProfileMaterialization = defineProfileMaterializationContract({
|
|
9809
|
+
name: "router-supervisor-execution",
|
|
9810
|
+
axes: [
|
|
9811
|
+
"name",
|
|
9812
|
+
"description",
|
|
9813
|
+
"version",
|
|
9814
|
+
"tags",
|
|
9815
|
+
"systemPrompt",
|
|
9816
|
+
"instructions",
|
|
9817
|
+
"resourceInstructions",
|
|
9818
|
+
"modelDefault",
|
|
9819
|
+
"modelSmall",
|
|
9820
|
+
"modelProvider",
|
|
9821
|
+
"modelReasoningEffort",
|
|
9822
|
+
"modelMetadata",
|
|
9823
|
+
"harness",
|
|
9824
|
+
"metadata"
|
|
9825
|
+
]
|
|
9826
|
+
});
|
|
9827
|
+
const coordinationMcpAlias = "agent-runtime-coordination";
|
|
9828
|
+
const defaultAllowedMcpHosts = [];
|
|
9829
|
+
Object.freeze(defaultAllowedMcpHosts);
|
|
9830
|
+
/** Manager-authored profiles are untrusted until product policy says otherwise. Remote MCP and
|
|
9831
|
+
* ambient connection grants therefore fail closed by default, in addition to local MCP and hooks. */
|
|
9832
|
+
const DEFAULT_AUTHORED_PROFILE_SECURITY_POLICY = Object.freeze({
|
|
9833
|
+
allowLocalMcp: false,
|
|
9834
|
+
allowHooks: false,
|
|
9835
|
+
allowedMcpHosts: defaultAllowedMcpHosts,
|
|
9836
|
+
allowConnections: false
|
|
9837
|
+
});
|
|
9838
|
+
function isExternalSupervisor(profile) {
|
|
9839
|
+
return profile.harness !== void 0 && profile.harness !== "cli-base";
|
|
9840
|
+
}
|
|
9841
|
+
function automaticDriverBackendSupported(backend) {
|
|
9842
|
+
return backend.backend === "bridge";
|
|
9843
|
+
}
|
|
9844
|
+
function backendProfileOverlays(backend) {
|
|
9845
|
+
if (!backend) return [];
|
|
9846
|
+
if (backend.backend === "bridge" && backend.agentProfile) return [backend.agentProfile];
|
|
9847
|
+
if (backend.backend === "cli-worktree" && backend.bridge?.agentProfile) return [backend.bridge.agentProfile];
|
|
9848
|
+
return [];
|
|
9849
|
+
}
|
|
9850
|
+
/** Run a harness-brained manager through the same executor factory as its children. The manager's
|
|
9851
|
+
* full profile is preserved, the live coordination server is added under one reserved alias, and
|
|
9852
|
+
* every streamed turn is charged to the manager's scope before it may continue. */
|
|
9853
|
+
function driveHarnessFromBackend(backend, executionId, now = Date.now) {
|
|
9854
|
+
const boundBackend = bindReusableExecutorExecutionId(captureReusableExecutorConfig(backend, "driveHarnessFromBackend"), executionId);
|
|
9855
|
+
const baseFactory = createExecutor(boundBackend);
|
|
9856
|
+
let activeExecutor;
|
|
9857
|
+
const drive = async ({ profile, task, scope, coordinationMcpUrl, coordinationTools }) => {
|
|
9858
|
+
const initialBudget = scope.budget;
|
|
9859
|
+
if (!(scope.view.inFlight > 0 || scope.view.waiting > 0) && (initialBudget.tokensLeft <= 0 || initialBudget.iterationsLeft <= 0 || initialBudget.usdCapped && initialBudget.usdLeft <= 0 || initialBudget.deadlineMs > 0 && now() >= initialBudget.deadlineMs)) throw new ValidationError("driveHarnessFromBackend: supervisor budget exhausted");
|
|
9860
|
+
const canonicalDriverProfile = agentProfileSchema.parse(profile);
|
|
9861
|
+
if (canonicalDriverProfile.mcp?.[coordinationMcpAlias] !== void 0) throw new ValidationError(`driveHarnessFromBackend: profile MCP alias ${JSON.stringify(coordinationMcpAlias)} is reserved`);
|
|
9862
|
+
const effectiveProfile = agentProfileSchema.parse({
|
|
9863
|
+
...canonicalDriverProfile,
|
|
9864
|
+
mcp: {
|
|
9865
|
+
...canonicalDriverProfile.mcp,
|
|
9866
|
+
[coordinationMcpAlias]: {
|
|
9867
|
+
transport: "http",
|
|
9868
|
+
url: coordinationMcpUrl
|
|
9869
|
+
}
|
|
8635
9870
|
}
|
|
9871
|
+
});
|
|
9872
|
+
const stableCoordinationTools = detachedSnapshot(coordinationTools, "driveHarnessFromBackend coordination tools");
|
|
9873
|
+
const spec = {
|
|
9874
|
+
profile: effectiveProfile,
|
|
9875
|
+
harness: boundBackend.backend === "sandbox" ? effectiveProfile.harness ?? boundBackend.harness ?? null : null
|
|
9876
|
+
};
|
|
9877
|
+
const executor = baseFactory(spec, {
|
|
9878
|
+
signal: scope.signal,
|
|
9879
|
+
node: scopeOwnerExecutorNodeContext(scope),
|
|
9880
|
+
seams: {}
|
|
9881
|
+
});
|
|
9882
|
+
activeExecutor = executor;
|
|
9883
|
+
let completed = false;
|
|
9884
|
+
let started = false;
|
|
9885
|
+
let terminalAccountingCaptured = false;
|
|
9886
|
+
let pendingUsage = [];
|
|
9887
|
+
let teardownStarted = false;
|
|
9888
|
+
const deadlineAtMs = scope.budget.deadlineMs || void 0;
|
|
9889
|
+
const teardownOnce = async (grace) => {
|
|
9890
|
+
if (teardownStarted) return;
|
|
9891
|
+
teardownStarted = true;
|
|
9892
|
+
await teardownExecutor(executor, grace, deadlineAtMs, now);
|
|
8636
9893
|
};
|
|
9894
|
+
const meterPending = async () => {
|
|
9895
|
+
if (pendingUsage.length === 0) return;
|
|
9896
|
+
const batch = pendingUsage;
|
|
9897
|
+
pendingUsage = [];
|
|
9898
|
+
await scope.meter(spendFromUsageEvents(batch), {
|
|
9899
|
+
role: "driver",
|
|
9900
|
+
runtime: executor.runtime
|
|
9901
|
+
});
|
|
9902
|
+
const budget = scope.budget;
|
|
9903
|
+
if (budget.tokensLeft <= 0 || budget.usdCapped && budget.usdLeft <= 0 || budget.deadlineMs > 0 && now() >= budget.deadlineMs) throw new ValidationError("driveHarnessFromBackend: supervisor budget exhausted");
|
|
9904
|
+
};
|
|
9905
|
+
let failed = false;
|
|
9906
|
+
let failure;
|
|
9907
|
+
try {
|
|
9908
|
+
const declaration = runtimeOwnedExecutorMaterialization(executor);
|
|
9909
|
+
const executionBinding = runtimeOwnedExecutorExecutionBinding(executor);
|
|
9910
|
+
if (declaration === void 0 || executionBinding === void 0) throw new ValidationError(`driveHarnessFromBackend: built-in runtime ${JSON.stringify(executor.runtime)} has no trusted materialization declaration or execution binding`);
|
|
9911
|
+
await recordScopeOwnerMaterialization(scope, executor.runtime, {
|
|
9912
|
+
...declaration,
|
|
9913
|
+
effectiveProfile: canonicalDriverProfile,
|
|
9914
|
+
platformAttachments: { [coordinationMcpAlias]: {
|
|
9915
|
+
kind: "coordination-mcp",
|
|
9916
|
+
transport: "http",
|
|
9917
|
+
tools: stableCoordinationTools
|
|
9918
|
+
} }
|
|
9919
|
+
}, {
|
|
9920
|
+
...executionBinding,
|
|
9921
|
+
binding: {
|
|
9922
|
+
stableBinding: executionBinding.binding,
|
|
9923
|
+
platformAttachments: { [coordinationMcpAlias]: {
|
|
9924
|
+
transport: "http",
|
|
9925
|
+
url: coordinationMcpUrl
|
|
9926
|
+
} }
|
|
9927
|
+
},
|
|
9928
|
+
descriptor: {
|
|
9929
|
+
...executionBinding.descriptor,
|
|
9930
|
+
coordination: true
|
|
9931
|
+
}
|
|
9932
|
+
});
|
|
9933
|
+
if (executor.budgetExempt) throw new ValidationError(`driveHarnessFromBackend: runtime ${JSON.stringify(executor.runtime)} does not report usage and cannot drive a budgeted supervisor`);
|
|
9934
|
+
started = true;
|
|
9935
|
+
const run = executor.execute(task, scope.signal);
|
|
9936
|
+
if (isAsyncIterable(run)) {
|
|
9937
|
+
for await (const event of run) if (event.kind === "iteration") await meterPending();
|
|
9938
|
+
else pendingUsage.push(event);
|
|
9939
|
+
await meterPending();
|
|
9940
|
+
const artifact = executor.resultArtifact();
|
|
9941
|
+
terminalAccountingCaptured = true;
|
|
9942
|
+
if (artifact.spent.tokensKnown === false || artifact.spent.usdKnown === false) await scope.meter({
|
|
9943
|
+
iterations: 0,
|
|
9944
|
+
tokens: {
|
|
9945
|
+
input: 0,
|
|
9946
|
+
output: 0
|
|
9947
|
+
},
|
|
9948
|
+
...artifact.spent.tokensKnown === false ? { tokensKnown: false } : {},
|
|
9949
|
+
usd: 0,
|
|
9950
|
+
...artifact.spent.usdKnown === false ? { usdKnown: false } : {},
|
|
9951
|
+
ms: 0
|
|
9952
|
+
}, {
|
|
9953
|
+
role: "driver",
|
|
9954
|
+
runtime: executor.runtime,
|
|
9955
|
+
telemetry: "unknown"
|
|
9956
|
+
});
|
|
9957
|
+
} else {
|
|
9958
|
+
const artifact = await run;
|
|
9959
|
+
terminalAccountingCaptured = true;
|
|
9960
|
+
await scope.meter({
|
|
9961
|
+
...artifact.spent,
|
|
9962
|
+
iterations: 0
|
|
9963
|
+
}, {
|
|
9964
|
+
role: "driver",
|
|
9965
|
+
runtime: executor.runtime
|
|
9966
|
+
});
|
|
9967
|
+
}
|
|
9968
|
+
completed = true;
|
|
9969
|
+
} catch (error) {
|
|
9970
|
+
failed = true;
|
|
9971
|
+
failure = error;
|
|
9972
|
+
} finally {
|
|
9973
|
+
try {
|
|
9974
|
+
await meterPending();
|
|
9975
|
+
} catch (error) {
|
|
9976
|
+
if (!failed) {
|
|
9977
|
+
failed = true;
|
|
9978
|
+
failure = error;
|
|
9979
|
+
}
|
|
9980
|
+
}
|
|
9981
|
+
if (failed && started && !terminalAccountingCaptured) try {
|
|
9982
|
+
await scope.meter({
|
|
9983
|
+
iterations: 0,
|
|
9984
|
+
tokens: {
|
|
9985
|
+
input: 0,
|
|
9986
|
+
output: 0
|
|
9987
|
+
},
|
|
9988
|
+
tokensKnown: false,
|
|
9989
|
+
usd: 0,
|
|
9990
|
+
usdKnown: false,
|
|
9991
|
+
ms: 0
|
|
9992
|
+
}, {
|
|
9993
|
+
role: "driver",
|
|
9994
|
+
runtime: executor.runtime,
|
|
9995
|
+
telemetry: "unknown-after-failure"
|
|
9996
|
+
});
|
|
9997
|
+
} catch (error) {
|
|
9998
|
+
const budget = scope.budget;
|
|
9999
|
+
if (budget.tokensKnown !== false || budget.usdCapped && budget.usdKnown !== false) failure = error;
|
|
10000
|
+
}
|
|
10001
|
+
try {
|
|
10002
|
+
await teardownOnce(completed ? DEFAULT_SUCCESSFUL_SHUTDOWN_MS : "brutalKill");
|
|
10003
|
+
} catch (error) {
|
|
10004
|
+
if (!failed) {
|
|
10005
|
+
failed = true;
|
|
10006
|
+
failure = error;
|
|
10007
|
+
}
|
|
10008
|
+
}
|
|
10009
|
+
if (activeExecutor === executor) activeExecutor = void 0;
|
|
10010
|
+
}
|
|
10011
|
+
if (failed) throw failure;
|
|
10012
|
+
};
|
|
10013
|
+
drive.deliver = (message) => {
|
|
10014
|
+
const deliver = activeExecutor?.deliver;
|
|
10015
|
+
if (!deliver) return false;
|
|
10016
|
+
return deliver.call(activeExecutor, message) !== false;
|
|
8637
10017
|
};
|
|
10018
|
+
return attestRuntimeOwnedScopeOwner(drive, "cli");
|
|
10019
|
+
}
|
|
10020
|
+
function isAsyncIterable(value) {
|
|
10021
|
+
return value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
8638
10022
|
}
|
|
8639
10023
|
/** Resolve one option that may be given as a value OR as a name into `opts.registry`. Both failure
|
|
8640
10024
|
* modes name the option, the requested name, and the table it was looked up in — a typo must not
|
|
@@ -8648,80 +10032,473 @@ function resolveNamed(option, table, value, registry) {
|
|
|
8648
10032
|
if (entry === void 0) throw new ConfigError(`supervise: opts.${option} = ${JSON.stringify(value)} is not in opts.registry.${table} — the table resolved no entry under that name`);
|
|
8649
10033
|
return entry;
|
|
8650
10034
|
}
|
|
8651
|
-
|
|
10035
|
+
function captureDeliverable(deliverable, context) {
|
|
10036
|
+
if (typeof deliverable !== "object" || deliverable === null || Array.isArray(deliverable)) throw new ValidationError(`${context}: deliverable must be an object`);
|
|
10037
|
+
if (typeof deliverable.check !== "function") throw new ValidationError(`${context}: deliverable.check must be a function`);
|
|
10038
|
+
return Object.freeze({
|
|
10039
|
+
...detachedSnapshot({ describe: deliverable.describe }, `${context} configuration`),
|
|
10040
|
+
check: deliverable.check
|
|
10041
|
+
});
|
|
10042
|
+
}
|
|
10043
|
+
/** Capture the public one-call configuration before any asynchronous work starts. Decision data is
|
|
10044
|
+
* detached and frozen; executable ports are copied as the exact references selected at intake.
|
|
10045
|
+
* Service internals intentionally remain live, while replacing a callback/service on the caller's
|
|
10046
|
+
* mutable options object can no longer change an in-flight run. */
|
|
10047
|
+
function captureSuperviseOptions(opts) {
|
|
10048
|
+
const { backend, driverBackend, deliverable, resolveDeliverable, router, compaction, watchWorkers, analysts, makeWorkerAgent, blobs, journal, probes, registry, hooks, otel, authorizeSpawn, authorizeMessage, isDriverProfile, brain, driveHarness, resolveDriveHarness, resolveSupervisorTools, onCoordinationEvent, executeExtraTool, stopRule, onProgressStop, finalizer, now, signal, rootHandle, ...decisionData } = opts;
|
|
10049
|
+
const capturedData = detachedSnapshot(decisionData, "supervise options");
|
|
10050
|
+
const capturedBackend = backend === void 0 ? void 0 : snapshotExecutorConfig(backend);
|
|
10051
|
+
const capturedDriverBackend = driverBackend === void 0 ? void 0 : snapshotExecutorConfig(driverBackend);
|
|
10052
|
+
const capturedDeliverable = deliverable === void 0 || typeof deliverable === "string" ? deliverable : captureDeliverable(deliverable, "supervise deliverable");
|
|
10053
|
+
const capturedRouter = router === void 0 ? void 0 : (() => {
|
|
10054
|
+
const { complete, ...routerData } = router;
|
|
10055
|
+
return Object.freeze({
|
|
10056
|
+
...detachedSnapshot(routerData, "supervise router configuration"),
|
|
10057
|
+
...complete === void 0 ? {} : { complete }
|
|
10058
|
+
});
|
|
10059
|
+
})();
|
|
10060
|
+
const capturedCompaction = compaction === void 0 ? void 0 : (() => {
|
|
10061
|
+
const { distill, estimateTokens, onCompact, ...compactionData } = compaction;
|
|
10062
|
+
return Object.freeze({
|
|
10063
|
+
...detachedSnapshot(compactionData, "supervise compaction configuration"),
|
|
10064
|
+
...distill === void 0 ? {} : { distill },
|
|
10065
|
+
...estimateTokens === void 0 ? {} : { estimateTokens },
|
|
10066
|
+
...onCompact === void 0 ? {} : { onCompact }
|
|
10067
|
+
});
|
|
10068
|
+
})();
|
|
10069
|
+
const capturedWatchWorkers = watchWorkers === void 0 ? void 0 : Object.freeze({
|
|
10070
|
+
...detachedSnapshot({ maxFindingsPerWorker: watchWorkers.maxFindingsPerWorker }, "supervise worker-watch configuration"),
|
|
10071
|
+
...watchWorkers.detectors === void 0 ? {} : { detectors: Object.freeze([...watchWorkers.detectors]) }
|
|
10072
|
+
});
|
|
10073
|
+
const capturedAnalysts = analysts === void 0 || typeof analysts === "string" ? analysts : Object.freeze({
|
|
10074
|
+
kinds: detachedSnapshot(analysts.kinds, "supervise analyst kinds"),
|
|
10075
|
+
run: analysts.run
|
|
10076
|
+
});
|
|
10077
|
+
return Object.freeze({
|
|
10078
|
+
...capturedData,
|
|
10079
|
+
...capturedBackend === void 0 ? {} : { backend: capturedBackend },
|
|
10080
|
+
...capturedDriverBackend === void 0 ? {} : { driverBackend: capturedDriverBackend },
|
|
10081
|
+
...capturedDeliverable === void 0 ? {} : { deliverable: capturedDeliverable },
|
|
10082
|
+
...resolveDeliverable === void 0 ? {} : { resolveDeliverable },
|
|
10083
|
+
...capturedRouter === void 0 ? {} : { router: capturedRouter },
|
|
10084
|
+
...capturedCompaction === void 0 ? {} : { compaction: capturedCompaction },
|
|
10085
|
+
...capturedWatchWorkers === void 0 ? {} : { watchWorkers: capturedWatchWorkers },
|
|
10086
|
+
...capturedAnalysts === void 0 ? {} : { analysts: capturedAnalysts },
|
|
10087
|
+
...makeWorkerAgent === void 0 ? {} : { makeWorkerAgent },
|
|
10088
|
+
...blobs === void 0 ? {} : { blobs },
|
|
10089
|
+
...journal === void 0 ? {} : { journal },
|
|
10090
|
+
...probes === void 0 ? {} : { probes },
|
|
10091
|
+
...authorizeSpawn === void 0 ? {} : { authorizeSpawn },
|
|
10092
|
+
...authorizeMessage === void 0 ? {} : { authorizeMessage },
|
|
10093
|
+
...isDriverProfile === void 0 ? {} : { isDriverProfile },
|
|
10094
|
+
...brain === void 0 ? {} : { brain },
|
|
10095
|
+
...driveHarness === void 0 ? {} : { driveHarness },
|
|
10096
|
+
...resolveDriveHarness === void 0 ? {} : { resolveDriveHarness },
|
|
10097
|
+
...resolveSupervisorTools === void 0 ? {} : { resolveSupervisorTools },
|
|
10098
|
+
...onCoordinationEvent === void 0 ? {} : { onCoordinationEvent },
|
|
10099
|
+
...executeExtraTool === void 0 ? {} : { executeExtraTool },
|
|
10100
|
+
...stopRule === void 0 ? {} : { stopRule },
|
|
10101
|
+
...onProgressStop === void 0 ? {} : { onProgressStop },
|
|
10102
|
+
...finalizer === void 0 ? {} : { finalizer },
|
|
10103
|
+
...now === void 0 ? {} : { now },
|
|
10104
|
+
...signal === void 0 ? {} : { signal },
|
|
10105
|
+
...rootHandle === void 0 ? {} : { rootHandle },
|
|
10106
|
+
...registry === void 0 ? {} : { registry },
|
|
10107
|
+
...hooks === void 0 ? {} : { hooks },
|
|
10108
|
+
...otel === void 0 ? {} : { otel }
|
|
10109
|
+
});
|
|
10110
|
+
}
|
|
10111
|
+
/** A quarter of token and optional dollar capacity per worker; nested managers partition again. */
|
|
8652
10112
|
function defaultPerWorker(budget) {
|
|
8653
10113
|
return {
|
|
8654
|
-
maxIterations: budget.maxIterations,
|
|
8655
|
-
maxTokens: Math.max(1, Math.floor(budget.maxTokens / 4))
|
|
10114
|
+
maxIterations: Math.max(1, Math.floor(budget.maxIterations / 4)),
|
|
10115
|
+
maxTokens: Math.max(1, Math.floor(budget.maxTokens / 4)),
|
|
10116
|
+
...budget.maxUsd !== void 0 ? { maxUsd: budget.maxUsd / 4 } : {}
|
|
8656
10117
|
};
|
|
8657
10118
|
}
|
|
10119
|
+
function freezeDetached(value) {
|
|
10120
|
+
return detachedSnapshot(value, "supervise");
|
|
10121
|
+
}
|
|
10122
|
+
function freezeDetachedProfile(value) {
|
|
10123
|
+
return freezeDetached(agentProfileSchema.parse(value));
|
|
10124
|
+
}
|
|
10125
|
+
/**
|
|
10126
|
+
* Map the two loose `SupervisorProfile` spellings onto their canonical `AgentProfile` form before
|
|
10127
|
+
* the strict schema parse, so both documented spellings run the SAME canonical pipeline and share
|
|
10128
|
+
* one identity digest:
|
|
10129
|
+
* - a string `model` IS `model.default`;
|
|
10130
|
+
* - a top-level `systemPrompt` IS `prompt.systemPrompt` (two disagreeing values are a fault);
|
|
10131
|
+
* - `harness: null` selects the router brain, which canonically is an ABSENT harness.
|
|
10132
|
+
* A canonical profile passes through byte-identical; every other field is left for the schema to
|
|
10133
|
+
* accept or refuse.
|
|
10134
|
+
*/
|
|
10135
|
+
function canonicalSupervisorProfileInput(profile) {
|
|
10136
|
+
if (typeof profile !== "object" || profile === null) return profile;
|
|
10137
|
+
const { harness, model, systemPrompt, prompt, ...rest } = profile;
|
|
10138
|
+
const promptSystem = prompt?.systemPrompt;
|
|
10139
|
+
if (systemPrompt !== void 0 && promptSystem !== void 0 && systemPrompt !== promptSystem) throw new ValidationError("supervise: profile.prompt.systemPrompt and profile.systemPrompt are both set and differ — they are the same standing instruction, so keep exactly one");
|
|
10140
|
+
const canonicalPrompt = systemPrompt !== void 0 ? {
|
|
10141
|
+
...prompt,
|
|
10142
|
+
systemPrompt
|
|
10143
|
+
} : prompt;
|
|
10144
|
+
return {
|
|
10145
|
+
...rest,
|
|
10146
|
+
...harness === null || harness === void 0 ? {} : { harness },
|
|
10147
|
+
...model === void 0 ? {} : { model: typeof model === "string" ? { default: model } : model },
|
|
10148
|
+
...canonicalPrompt === void 0 ? {} : { prompt: canonicalPrompt }
|
|
10149
|
+
};
|
|
10150
|
+
}
|
|
10151
|
+
function canonicalExecution(profile, task, rawExecution, context) {
|
|
10152
|
+
const execution = rawExecution === void 0 ? void 0 : freezeDetached(rawExecution);
|
|
10153
|
+
if (execution !== void 0) {
|
|
10154
|
+
if (typeof execution !== "object" || execution === null || Array.isArray(execution)) throw new ValidationError(`${context}: execution must be an object`);
|
|
10155
|
+
const unknown = Object.keys(execution).filter((key) => key !== "candidateDigest" && key !== "correlation");
|
|
10156
|
+
if (unknown.length > 0) throw new ValidationError(`${context}: unknown execution fields: ${unknown.join(", ")}`);
|
|
10157
|
+
}
|
|
10158
|
+
const identity = deriveNodeExecutionIdentity({
|
|
10159
|
+
profile,
|
|
10160
|
+
execution
|
|
10161
|
+
}, task);
|
|
10162
|
+
if (!identity?.profileDigest || !identity.taskDigest) throw new ValidationError(`${context}: profile and task must be finite, acyclic canonical JSON for durable identity`);
|
|
10163
|
+
const ref = identity.candidateDigest || identity.correlation ? Object.freeze({
|
|
10164
|
+
...identity.candidateDigest ? { candidateDigest: identity.candidateDigest } : {},
|
|
10165
|
+
...identity.correlation ? { correlation: identity.correlation } : {}
|
|
10166
|
+
}) : void 0;
|
|
10167
|
+
return {
|
|
10168
|
+
identity,
|
|
10169
|
+
...ref ? { ref } : {}
|
|
10170
|
+
};
|
|
10171
|
+
}
|
|
10172
|
+
function rootCoordinationOwner(identity) {
|
|
10173
|
+
return canonicalCandidateDigest({
|
|
10174
|
+
kind: "supervisor-root",
|
|
10175
|
+
identity
|
|
10176
|
+
});
|
|
10177
|
+
}
|
|
10178
|
+
function childCoordinationOwner(parentOwnerId, identity, context, depth) {
|
|
10179
|
+
return canonicalCandidateDigest({
|
|
10180
|
+
kind: "supervisor-child",
|
|
10181
|
+
parentOwnerId,
|
|
10182
|
+
identity,
|
|
10183
|
+
assignment: {
|
|
10184
|
+
id: context.assignmentId,
|
|
10185
|
+
label: context.label,
|
|
10186
|
+
key: context.key ?? null,
|
|
10187
|
+
depth
|
|
10188
|
+
}
|
|
10189
|
+
});
|
|
10190
|
+
}
|
|
10191
|
+
function supervisionRunNamespace(runDir, runId) {
|
|
10192
|
+
return canonicalCandidateDigest(runDir === void 0 ? {
|
|
10193
|
+
kind: "supervise-ephemeral-run",
|
|
10194
|
+
runId,
|
|
10195
|
+
nonce: randomUUID()
|
|
10196
|
+
} : {
|
|
10197
|
+
kind: "supervise-durable-run",
|
|
10198
|
+
runId,
|
|
10199
|
+
runDir: resolve(runDir)
|
|
10200
|
+
});
|
|
10201
|
+
}
|
|
10202
|
+
function workerAssignmentNamespace(runNamespace, parentOwnerId, assignmentId) {
|
|
10203
|
+
return canonicalCandidateDigest({
|
|
10204
|
+
kind: "supervise-worker-assignment",
|
|
10205
|
+
runNamespace,
|
|
10206
|
+
parentOwnerId,
|
|
10207
|
+
assignmentId
|
|
10208
|
+
});
|
|
10209
|
+
}
|
|
10210
|
+
/** Hash only durable coordination meaning. Bus sequence/timestamp are delivery metadata and a
|
|
10211
|
+
* resumed projection's marker describes the reader, not the original settlement. */
|
|
10212
|
+
function coordinationEventId(context, event) {
|
|
10213
|
+
const durableEvent = event.type === "settled" && event.worker.resumed === true ? (() => {
|
|
10214
|
+
const { resumed: _resumed, ...worker } = event.worker;
|
|
10215
|
+
return {
|
|
10216
|
+
type: "settled",
|
|
10217
|
+
worker
|
|
10218
|
+
};
|
|
10219
|
+
})() : event;
|
|
10220
|
+
return canonicalCandidateDigest({
|
|
10221
|
+
kind: "supervise-coordination-event",
|
|
10222
|
+
runNamespace: context.runNamespace,
|
|
10223
|
+
ownerId: context.ownerId,
|
|
10224
|
+
event: detachedSnapshot(durableEvent, "supervise coordination event identity")
|
|
10225
|
+
});
|
|
10226
|
+
}
|
|
8658
10227
|
/** One-call supervisor: build + run a supervisor from its profile with sensible defaults; the raw `supervisorAgent` + `createSupervisor().run` seams stay available for power use. */
|
|
8659
10228
|
function supervise(profile, task, opts) {
|
|
8660
|
-
const
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
const
|
|
8665
|
-
const
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
10229
|
+
const options = captureSuperviseOptions(opts);
|
|
10230
|
+
assertValidBudget(options.budget, "supervise budget");
|
|
10231
|
+
const parsedProfile = agentProfileSchema.safeParse(canonicalSupervisorProfileInput(profile));
|
|
10232
|
+
if (!parsedProfile.success) throw new ValidationError(`supervise: invalid AgentProfile: ${parsedProfile.error.message}`);
|
|
10233
|
+
const canonicalProfile = freezeDetachedProfile(parsedProfile.data);
|
|
10234
|
+
const canonicalTask = freezeDetached(task);
|
|
10235
|
+
if (options.makeWorkerAgent && options.authorizeSpawn) throw new ValidationError("supervise: authorizeSpawn cannot be combined with caller-owned makeWorkerAgent; wrap and authorize the custom factory explicitly or use backend-derived workers");
|
|
10236
|
+
if (options.makeWorkerAgent && options.resolveDeliverable) throw new ValidationError("supervise: resolveDeliverable applies only to backend-derived workers; wrap a caller-owned makeWorkerAgent with its completion checks explicitly");
|
|
10237
|
+
const authorizeDownFor = (parent, depth) => {
|
|
10238
|
+
if (!options.authorizeSpawn && !options.authorizeMessage) return void 0;
|
|
10239
|
+
return (input) => {
|
|
10240
|
+
if (!options.authorizeMessage) throw new ValidationError("supervise: authorizeMessage is required before steer_agent or answer_question when authorizeSpawn is enabled");
|
|
10241
|
+
return freezeDetached(options.authorizeMessage(freezeDetached({
|
|
10242
|
+
...input,
|
|
10243
|
+
parent,
|
|
10244
|
+
depth
|
|
10245
|
+
})));
|
|
10246
|
+
};
|
|
10247
|
+
};
|
|
10248
|
+
const rootExecution = canonicalExecution(canonicalProfile, canonicalTask, options.execution, "supervise root");
|
|
10249
|
+
const backendModel = options.backend?.model;
|
|
10250
|
+
const driverBackendModel = options.driverBackend?.model;
|
|
10251
|
+
if ([...backendProfileOverlays(options.backend), ...backendProfileOverlays(options.driverBackend)].length > 0) throw new ValidationError("supervise: backend agentProfile overlays are not allowed because they run after spawn authorization; merge the overlay into the exact profile before calling supervise");
|
|
10252
|
+
assertModelAllowed(options.router?.model, options.allowedModels);
|
|
10253
|
+
assertProfileModelsAllowed(canonicalProfile, options.allowedModels);
|
|
10254
|
+
assertModelAllowed(typeof backendModel === "string" ? backendModel : void 0, options.allowedModels);
|
|
10255
|
+
assertModelAllowed(typeof driverBackendModel === "string" ? driverBackendModel : void 0, options.allowedModels);
|
|
10256
|
+
const deliverable = resolveNamed("deliverable", "deliverables", options.deliverable, options.registry?.deliverables);
|
|
10257
|
+
const finalizer = resolveNamed("finalizer", "finalizers", options.finalizer, options.registry?.finalizers);
|
|
10258
|
+
const analysts = resolveNamed("analysts", "analysts", options.analysts, options.registry?.analysts);
|
|
10259
|
+
const probes = resolveNamed("probes", "probes", options.probes, options.registry?.probes);
|
|
10260
|
+
assertCoordinationBinding(options.coordination);
|
|
10261
|
+
const ctx = options.runDir !== void 0 ? createFileRunContext(options.runDir, { withDriver: true }) : createInMemoryRunContext({ withDriver: true });
|
|
10262
|
+
const blobs = options.blobs ?? ctx.blobs;
|
|
10263
|
+
const perWorker = options.perWorker ?? defaultPerWorker(options.budget);
|
|
10264
|
+
assertValidBudget(perWorker, "supervise perWorker");
|
|
10265
|
+
const journal = options.journal ?? ctx.journal;
|
|
10266
|
+
const runId = options.runId ?? "supervise";
|
|
10267
|
+
const runNamespace = supervisionRunNamespace(options.runDir, runId);
|
|
8673
10268
|
const log = ctx.coordinationLog;
|
|
8674
|
-
const
|
|
10269
|
+
const rootOwnerId = rootCoordinationOwner(rootExecution.identity);
|
|
10270
|
+
const observeNodeEvent = options.onCoordinationEvent ? async (context, event, record) => {
|
|
10271
|
+
await options.onCoordinationEvent?.(context, coordinationEventId(context, event), record);
|
|
10272
|
+
} : void 0;
|
|
10273
|
+
const managerBackend = options.driverBackend ?? options.backend;
|
|
10274
|
+
if (options.driveHarness && options.resolveDriveHarness) throw new ValidationError("supervise: provide driveHarness or resolveDriveHarness, not both");
|
|
10275
|
+
const driverMaterialization = Boolean(options.driveHarness || options.resolveDriveHarness) ? options.driveHarnessMaterialization ?? fullProfileMaterialization : managerBackend && automaticDriverBackendSupported(managerBackend) ? backendProfileMaterialization(managerBackend) : void 0;
|
|
10276
|
+
if (isExternalSupervisor(canonicalProfile) && !options.driveHarness && !options.resolveDriveHarness && (!managerBackend || !automaticDriverBackendSupported(managerBackend))) throw new ValidationError(`supervise: external supervisor profile.harness=${JSON.stringify(canonicalProfile.harness)} requires a local bridge driverBackend, an explicit driveHarness, or resolveDriveHarness with reachable coordination transport`);
|
|
10277
|
+
const harnessClaims = /* @__PURE__ */ new WeakMap();
|
|
10278
|
+
const claimDriveHarness = (rawHarness, ownerId) => {
|
|
10279
|
+
if (typeof rawHarness !== "function") throw new ValidationError("supervise: resolveDriveHarness must return a DriveHarness function");
|
|
10280
|
+
const harness = rawHarness;
|
|
10281
|
+
const deliver = harness.deliver;
|
|
10282
|
+
if (deliver !== void 0 && typeof deliver !== "function") throw new ValidationError("supervise: driveHarness.deliver must be a function when provided");
|
|
10283
|
+
const claim = harnessClaims.get(harness);
|
|
10284
|
+
const conflictingOwner = claim ? [...claim.owners].find((claimedOwner) => claimedOwner !== ownerId) : void 0;
|
|
10285
|
+
const steerable = typeof deliver === "function";
|
|
10286
|
+
if (conflictingOwner !== void 0 && (steerable || claim?.steerable === true)) throw new ValidationError(`supervise: steerable driveHarness is already bound to manager owner ${JSON.stringify(conflictingOwner)}; resolveDriveHarness must return a distinct steerable instance for owner ${JSON.stringify(ownerId)}`);
|
|
10287
|
+
if (claim) {
|
|
10288
|
+
claim.owners.add(ownerId);
|
|
10289
|
+
claim.steerable ||= steerable;
|
|
10290
|
+
} else harnessClaims.set(harness, {
|
|
10291
|
+
owners: /* @__PURE__ */ new Set([ownerId]),
|
|
10292
|
+
steerable
|
|
10293
|
+
});
|
|
10294
|
+
return harness;
|
|
10295
|
+
};
|
|
10296
|
+
const driveHarnessForOwner = (context) => {
|
|
10297
|
+
if (options.resolveDriveHarness) return claimDriveHarness(options.resolveDriveHarness(context), context.ownerId);
|
|
10298
|
+
if (options.driveHarness) return claimDriveHarness(options.driveHarness, context.ownerId);
|
|
10299
|
+
return managerBackend && automaticDriverBackendSupported(managerBackend) ? driveHarnessFromBackend(managerBackend, externalExecutionId("supervised-manager", {
|
|
10300
|
+
runNamespace,
|
|
10301
|
+
ownerId: context.ownerId
|
|
10302
|
+
}), options.now ?? Date.now) : void 0;
|
|
10303
|
+
};
|
|
10304
|
+
const rootDriveHarness = isExternalSupervisor(canonicalProfile) ? driveHarnessForOwner(freezeDetached({
|
|
10305
|
+
runId,
|
|
10306
|
+
runNamespace,
|
|
10307
|
+
ownerId: rootOwnerId,
|
|
10308
|
+
depth: 0,
|
|
10309
|
+
identity: rootExecution.identity,
|
|
10310
|
+
profile: canonicalProfile,
|
|
10311
|
+
task: canonicalTask
|
|
10312
|
+
})) : void 0;
|
|
10313
|
+
const rootOwnerRuntime = !isExternalSupervisor(canonicalProfile) || rootDriveHarness === void 0 ? void 0 : runtimeOwnedScopeOwnerRuntime(rootDriveHarness);
|
|
10314
|
+
assertProfileContract(canonicalProfile, isExternalSupervisor(canonicalProfile) ? driverMaterialization : options.brain ? promptControlProfileMaterialization : routerSupervisorProfileMaterialization, "supervise root");
|
|
10315
|
+
const now = options.now ?? Date.now;
|
|
8675
10316
|
let spans;
|
|
8676
|
-
let makeWorkerAgent =
|
|
10317
|
+
let makeWorkerAgent = options.makeWorkerAgent;
|
|
8677
10318
|
if (!makeWorkerAgent) {
|
|
8678
|
-
if (!
|
|
8679
|
-
|
|
10319
|
+
if (!options.backend) throw new ValidationError("supervise: provide opts.backend (where workers run) or opts.makeWorkerAgent");
|
|
10320
|
+
const makeLeaf = workerFromBackend(options.backend, deliverable);
|
|
10321
|
+
const securityPolicy = options.profileSecurity ?? DEFAULT_AUTHORED_PROFILE_SECURITY_POLICY;
|
|
10322
|
+
const makeRecursiveWorkerFor = (parent, parentIdentity, depth, parentOwnerId) => {
|
|
10323
|
+
const makeRecursiveWorker = (authoredProfile, spawnContext) => {
|
|
10324
|
+
if (!spawnContext) throw new ValidationError("supervise: backend-derived workers require spawn context");
|
|
10325
|
+
const input = freezeDetachedProfile(authoredProfile);
|
|
10326
|
+
const authorizationInput = Object.freeze({
|
|
10327
|
+
profile: input,
|
|
10328
|
+
parent,
|
|
10329
|
+
parentIdentity,
|
|
10330
|
+
parentNodeId: spawnContext.parentNodeId,
|
|
10331
|
+
assignmentId: spawnContext.assignmentId,
|
|
10332
|
+
task: spawnContext.task,
|
|
10333
|
+
budget: spawnContext.budget,
|
|
10334
|
+
label: spawnContext.label,
|
|
10335
|
+
...spawnContext.key !== void 0 ? { key: spawnContext.key } : {},
|
|
10336
|
+
depth
|
|
10337
|
+
});
|
|
10338
|
+
const decision = options.authorizeSpawn ? freezeDetached(options.authorizeSpawn(authorizationInput)) : Object.freeze({
|
|
10339
|
+
profile: input,
|
|
10340
|
+
...spawnContext.execution ? { execution: spawnContext.execution } : {}
|
|
10341
|
+
});
|
|
10342
|
+
if (typeof decision !== "object" || decision === null || Array.isArray(decision)) throw new ValidationError("supervise: authorizeSpawn must return an AuthorizedSpawn");
|
|
10343
|
+
const authorized = freezeDetachedProfile(decision.profile);
|
|
10344
|
+
const childExecution = canonicalExecution(authorized, spawnContext.task, decision.execution, `supervise spawn ${JSON.stringify(spawnContext.label)}`);
|
|
10345
|
+
const authorizedContext = Object.freeze({
|
|
10346
|
+
...spawnContext,
|
|
10347
|
+
...childExecution.ref ? { execution: childExecution.ref } : {}
|
|
10348
|
+
});
|
|
10349
|
+
const postAuthorizationContext = freezeDetached({
|
|
10350
|
+
profile: authorized,
|
|
10351
|
+
parent,
|
|
10352
|
+
parentIdentity,
|
|
10353
|
+
execution: childExecution.identity,
|
|
10354
|
+
parentNodeId: spawnContext.parentNodeId,
|
|
10355
|
+
assignmentId: spawnContext.assignmentId,
|
|
10356
|
+
task: spawnContext.task,
|
|
10357
|
+
budget: spawnContext.budget,
|
|
10358
|
+
label: spawnContext.label,
|
|
10359
|
+
...spawnContext.key !== void 0 ? { key: spawnContext.key } : {},
|
|
10360
|
+
depth
|
|
10361
|
+
});
|
|
10362
|
+
const security = validateAgentProfileSecurity(authorized, securityPolicy);
|
|
10363
|
+
if (!security.ok) throw new ValidationError(`supervise: spawned AgentProfile refused: ${security.issues.filter((issue) => issue.level === "error").map((issue) => `${issue.code}${issue.path ? ` at ${issue.path}` : ""}`).join(", ")}`);
|
|
10364
|
+
assertProfileModelsAllowed(authorized, options.allowedModels);
|
|
10365
|
+
let isDriver;
|
|
10366
|
+
if (options.isDriverProfile) {
|
|
10367
|
+
const driverDecision = options.isDriverProfile(postAuthorizationContext);
|
|
10368
|
+
if (typeof driverDecision !== "boolean") throw new ValidationError("supervise: isDriverProfile must return a boolean");
|
|
10369
|
+
isDriver = driverDecision;
|
|
10370
|
+
} else isDriver = authorized.metadata?.role === "driver";
|
|
10371
|
+
if (!isDriver) {
|
|
10372
|
+
const selectedDeliverable = options.resolveDeliverable?.(postAuthorizationContext);
|
|
10373
|
+
const leafDeliverable = selectedDeliverable === void 0 ? deliverable : captureDeliverable(selectedDeliverable, `supervise deliverable for ${JSON.stringify(spawnContext.label)}`);
|
|
10374
|
+
return (leafDeliverable === deliverable ? makeLeaf : workerFromBackend(options.backend, leafDeliverable))(authorized, Object.freeze({
|
|
10375
|
+
...authorizedContext,
|
|
10376
|
+
assignmentId: workerAssignmentNamespace(runNamespace, parentOwnerId, spawnContext.assignmentId)
|
|
10377
|
+
}));
|
|
10378
|
+
}
|
|
10379
|
+
const ownerId = childCoordinationOwner(parentOwnerId, childExecution.identity, spawnContext, depth);
|
|
10380
|
+
const nestedDriveHarness = isExternalSupervisor(authorized) ? driveHarnessForOwner(freezeDetached({
|
|
10381
|
+
runId,
|
|
10382
|
+
runNamespace,
|
|
10383
|
+
ownerId,
|
|
10384
|
+
depth,
|
|
10385
|
+
identity: childExecution.identity,
|
|
10386
|
+
assignmentId: spawnContext.assignmentId,
|
|
10387
|
+
profile: authorized,
|
|
10388
|
+
task: spawnContext.task
|
|
10389
|
+
})) : void 0;
|
|
10390
|
+
if (isExternalSupervisor(authorized) && !nestedDriveHarness) throw new ValidationError(`supervise: authored external supervisor profile.harness=${JSON.stringify(authorized.harness)} requires a local bridge driverBackend, an explicit driveHarness, or resolveDriveHarness with reachable coordination transport`);
|
|
10391
|
+
assertProfileContract(authorized, isExternalSupervisor(authorized) ? driverMaterialization : promptModelProfileMaterialization, `supervise driver ${JSON.stringify(spawnContext.label)}`);
|
|
10392
|
+
const childFactory = makeRecursiveWorkerFor(authorized, childExecution.identity, depth + 1, ownerId);
|
|
10393
|
+
const nestedPerWorker = defaultPerWorker(spawnContext.budget);
|
|
10394
|
+
const authorizeNestedMessage = authorizeDownFor(authorized, depth + 1);
|
|
10395
|
+
return driverChild(authorized, supervisorAgent(authorized, {
|
|
10396
|
+
blobs,
|
|
10397
|
+
makeWorkerAgent: childFactory,
|
|
10398
|
+
...authorizeNestedMessage ? { authorizeDownMessage: authorizeNestedMessage } : {},
|
|
10399
|
+
perWorker: nestedPerWorker,
|
|
10400
|
+
...options.router ? { router: options.router } : {},
|
|
10401
|
+
...nestedDriveHarness ? { driveHarness: nestedDriveHarness } : {},
|
|
10402
|
+
nodeContext: {
|
|
10403
|
+
runId,
|
|
10404
|
+
runNamespace,
|
|
10405
|
+
ownerId,
|
|
10406
|
+
depth,
|
|
10407
|
+
identity: childExecution.identity,
|
|
10408
|
+
assignmentId: spawnContext.assignmentId
|
|
10409
|
+
},
|
|
10410
|
+
...options.resolveSupervisorTools ? { resolveSupervisorTools: options.resolveSupervisorTools } : {},
|
|
10411
|
+
...observeNodeEvent ? {
|
|
10412
|
+
observeNodeEvent,
|
|
10413
|
+
replaySettlements: true
|
|
10414
|
+
} : {},
|
|
10415
|
+
...analysts ? { analysts } : {},
|
|
10416
|
+
...options.analyzeOnSettle ? { analyzeOnSettle: options.analyzeOnSettle } : {},
|
|
10417
|
+
...options.watchWorkers ? { watchWorkers: options.watchWorkers } : {},
|
|
10418
|
+
...options.stallAfterMs !== void 0 ? { stallAfterMs: options.stallAfterMs } : {},
|
|
10419
|
+
...options.stopRule ? { stopRule: options.stopRule } : {},
|
|
10420
|
+
...options.onProgressStop ? { onProgressStop: options.onProgressStop } : {},
|
|
10421
|
+
...options.maxTurns !== void 0 ? { maxTurns: options.maxTurns } : {},
|
|
10422
|
+
...options.compaction ? { compaction: options.compaction } : {},
|
|
10423
|
+
...log ? {
|
|
10424
|
+
onEvent: (_event, record) => log.append(runId, record, ownerId),
|
|
10425
|
+
loadPriorCoordination: () => log.load(runId, ownerId)
|
|
10426
|
+
} : {},
|
|
10427
|
+
...finalizer ? { finalizer } : {}
|
|
10428
|
+
}), journal, childExecution.ref);
|
|
10429
|
+
};
|
|
10430
|
+
return makeRecursiveWorker;
|
|
10431
|
+
};
|
|
10432
|
+
makeWorkerAgent = makeRecursiveWorkerFor(canonicalProfile, rootExecution.identity, 1, rootOwnerId);
|
|
8680
10433
|
}
|
|
8681
10434
|
const workerFactory = makeWorkerAgent;
|
|
8682
10435
|
const start = async () => {
|
|
8683
|
-
const priorCoordination = log ? await log.load(runId) : void 0;
|
|
8684
|
-
const
|
|
10436
|
+
const priorCoordination = log ? await log.load(runId, rootOwnerId) : void 0;
|
|
10437
|
+
const authorizeRootMessage = authorizeDownFor(canonicalProfile, 1);
|
|
10438
|
+
const agent = supervisorAgent(canonicalProfile, {
|
|
8685
10439
|
blobs,
|
|
8686
10440
|
makeWorkerAgent: workerFactory,
|
|
10441
|
+
...authorizeRootMessage ? { authorizeDownMessage: authorizeRootMessage } : {},
|
|
8687
10442
|
perWorker,
|
|
10443
|
+
...log ? { onEvent: (_event, record) => log.append(runId, record, rootOwnerId) } : {},
|
|
8688
10444
|
...deliverable ? { deliverable } : {},
|
|
8689
|
-
...
|
|
8690
|
-
...priorCoordination && (priorCoordination.questions.length > 0 || priorCoordination.findings.length > 0) ? { priorCoordination } : {},
|
|
10445
|
+
...priorCoordination && (priorCoordination.questions.length > 0 || priorCoordination.findings.length > 0 || priorCoordination.continuations.length > 0 || priorCoordination.deliveryEvidence.length > 0) ? { priorCoordination } : {},
|
|
8691
10446
|
...finalizer ? { finalizer } : {},
|
|
8692
|
-
...
|
|
8693
|
-
...
|
|
8694
|
-
...
|
|
8695
|
-
...
|
|
8696
|
-
...
|
|
8697
|
-
|
|
8698
|
-
|
|
10447
|
+
...options.coordination ? { coordination: options.coordination } : {},
|
|
10448
|
+
...options.maxLiveWorkers !== void 0 ? { maxLiveWorkers: options.maxLiveWorkers } : {},
|
|
10449
|
+
...options.router ? { router: options.router } : {},
|
|
10450
|
+
...options.brain ? { brain: options.brain } : {},
|
|
10451
|
+
...rootDriveHarness ? { driveHarness: rootDriveHarness } : {},
|
|
10452
|
+
nodeContext: {
|
|
10453
|
+
runId,
|
|
10454
|
+
runNamespace,
|
|
10455
|
+
ownerId: rootOwnerId,
|
|
10456
|
+
depth: 0,
|
|
10457
|
+
identity: rootExecution.identity
|
|
10458
|
+
},
|
|
10459
|
+
...options.resolveSupervisorTools ? { resolveSupervisorTools: options.resolveSupervisorTools } : {},
|
|
10460
|
+
...observeNodeEvent ? {
|
|
10461
|
+
observeNodeEvent,
|
|
10462
|
+
replaySettlements: true
|
|
10463
|
+
} : {},
|
|
10464
|
+
...options.extraTools ? { extraTools: options.extraTools } : {},
|
|
10465
|
+
...options.executeExtraTool ? { executeExtraTool: options.executeExtraTool } : {},
|
|
8699
10466
|
...analysts ? { analysts } : {},
|
|
8700
|
-
...
|
|
8701
|
-
...
|
|
8702
|
-
...
|
|
8703
|
-
...
|
|
8704
|
-
...
|
|
8705
|
-
...
|
|
8706
|
-
...
|
|
10467
|
+
...options.analyzeOnSettle ? { analyzeOnSettle: options.analyzeOnSettle } : {},
|
|
10468
|
+
...options.watchWorkers ? { watchWorkers: options.watchWorkers } : {},
|
|
10469
|
+
...options.stallAfterMs !== void 0 ? { stallAfterMs: options.stallAfterMs } : {},
|
|
10470
|
+
...options.stopRule ? { stopRule: options.stopRule } : {},
|
|
10471
|
+
...options.onProgressStop ? { onProgressStop: options.onProgressStop } : {},
|
|
10472
|
+
...options.maxTurns !== void 0 ? { maxTurns: options.maxTurns } : {},
|
|
10473
|
+
...options.compaction ? { compaction: options.compaction } : {}
|
|
8707
10474
|
});
|
|
8708
|
-
spans =
|
|
10475
|
+
spans = options.otel ? createSupervisorSpanRecorder({
|
|
8709
10476
|
runId,
|
|
8710
|
-
...
|
|
10477
|
+
...options.otel,
|
|
8711
10478
|
now
|
|
8712
10479
|
}) : void 0;
|
|
8713
10480
|
const recorder = spans;
|
|
8714
|
-
const hooks = recorder ? composeRuntimeHooks(
|
|
8715
|
-
const
|
|
8716
|
-
|
|
10481
|
+
const hooks = recorder ? composeRuntimeHooks(options.hooks, recorder.hooks) : options.hooks;
|
|
10482
|
+
const supervisor = createSupervisor();
|
|
10483
|
+
if (options.rootHandle) supervisor.attach(options.rootHandle);
|
|
10484
|
+
const run = supervisor.run(agent, canonicalTask, {
|
|
10485
|
+
budget: options.budget,
|
|
8717
10486
|
runId,
|
|
8718
|
-
journal
|
|
10487
|
+
journal,
|
|
8719
10488
|
blobs,
|
|
8720
10489
|
executors: ctx.executors,
|
|
8721
|
-
|
|
10490
|
+
rootIdentity: rootExecution.identity,
|
|
10491
|
+
...rootOwnerRuntime === void 0 ? {} : { rootMaterialization: {
|
|
10492
|
+
runtime: rootOwnerRuntime,
|
|
10493
|
+
declaration: "deferred",
|
|
10494
|
+
authoredProfile: canonicalProfile
|
|
10495
|
+
} },
|
|
10496
|
+
maxDepth: options.maxDepth ?? 8,
|
|
10497
|
+
...options.maxLiveWorkers !== void 0 ? { maxLiveWorkers: options.maxLiveWorkers } : {},
|
|
8722
10498
|
...probes ? { probes } : {},
|
|
8723
10499
|
...ctx.resume === true ? { resume: true } : {},
|
|
8724
|
-
...
|
|
10500
|
+
...options.now ? { now: options.now } : {},
|
|
10501
|
+
...options.signal ? { signal: options.signal } : {},
|
|
8725
10502
|
...hooks ? { hooks } : {},
|
|
8726
10503
|
...recorder ? { workerTrace: recorder.workerTrace } : {}
|
|
8727
10504
|
});
|
|
@@ -8738,6 +10515,6 @@ function supervise(profile, task, opts) {
|
|
|
8738
10515
|
return start();
|
|
8739
10516
|
}
|
|
8740
10517
|
//#endregion
|
|
8741
|
-
export {
|
|
10518
|
+
export { allOf as $, profileRichnessFinding as $t, DELEGATE_DESCRIPTION as A, PI_MCP_ADAPTER as At, DELEGATION_TRACE_MAX_SPANS as B, runLoop as Bt, createDelegateUiAuditHandler as C, createSteerableSandboxSession as Ct, DELEGATE_FEEDBACK_TOOL_NAME as D, createPushTraceSource as Dt, DELEGATE_FEEDBACK_INPUT_SCHEMA as E, piSeamKey as Et, defaultDelegateBudget as F, preparePiMcp as Ft, DelegationPersistenceError as G, createSupervisorSpanRecorder as Gt, capDelegationTrace as H, probeSandboxCapabilities as Ht, delegate as I, createInbox as It, InMemoryDelegationStore as J, asAuthoredProfile as Jt, DelegationStateCorruptError as K, assertModelAllowed as Kt, DelegationTaskQueue as L, createSandboxForSpec as Lt, DELEGATE_TOOL_NAME as M, PI_MCP_CONFIG_FLAG as Mt, createDelegateHandler as N, buildPiMcpServers as Nt, createDelegateFeedbackHandler as O, decodeToolPart as Ot, validateDelegateArgs as P, piMcpAdapterAvailable as Pt, finalizeBestDelivered as Q, defaultProfileRichnessThresholds as Qt, hashIdempotencyInput as R, defaultSelectWinner as Rt, DELEGATE_UI_AUDIT_TOOL_NAME as S, DEFAULT_SANDBOX_STEERING_MAX_TURNS as St, DELEGATE_FEEDBACK_DESCRIPTION as T, piExecutor as Tt, composeLoopTraceEmitters as U, acquireSandbox as Ut, buildDelegationTraceSpans as V, createSandboxLineage as Vt, createDelegationTraceCollector as W, FileCoordinationLog as Wt, eventToSnapshot as X, authoredWorker as Xt, InMemoryFeedbackStore as Y, assessAuthoredProfile as Yt, driverAgent as Z, canonicalizeAuthoredProfile as Zt, DELEGATION_HISTORY_TOOL_NAME as _, createInMemoryRunContext as _t, resolveSupervisorProfile as a, sampleFromSettled as at, DELEGATE_UI_AUDIT_DESCRIPTION as b, createExecutorRegistry as bt, createInProcessTransport as c, bestSoFar as ct, DELEGATION_STATUS_INPUT_SCHEMA as d, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as dt, supervisorInstructions as en, allWorkersStalled as et, DELEGATION_STATUS_TOOL_NAME as f, createCoordinationTools as ft, DELEGATION_HISTORY_INPUT_SCHEMA as g, createFileRunContext as gt, DELEGATION_HISTORY_DESCRIPTION as h, watchTrace as ht, assertCoordinationBinding as i, plateau as it, DELEGATE_INPUT_SCHEMA as j, PI_MCP_ADAPTER_ENV as jt, validateDelegateFeedbackArgs as k, sandboxSessionTraceSource as kt, createMcpServer as l, plateauLength as lt, validateDelegationStatusArgs as m, defaultToolDetectors as mt, supervise as n, createProgressTracker as nt, supervisorAgent as o, anytimeReport as ot, createDelegationStatusHandler as p, createEventBus as pt, FileDelegationStore as q, assertProfileModelsAllowed as qt, workerFromBackend as r, noProgressFor as rt, serveCoordinationMcp as s, areaUnderCurve as st, DEFAULT_AUTHORED_PROFILE_SECURITY_POLICY as t, gateOnDeliverable as tn, anyOf as tt, DELEGATION_STATUS_DESCRIPTION as u, renderAnytimeTable as ut, createDelegationHistoryHandler as v, cliWorktreeExecutor as vt, validateDelegateUiAuditArgs as w, PI_RUNTIME as wt, DELEGATE_UI_AUDIT_INPUT_SCHEMA as x, createWorktreeCliExecutor as xt, validateDelegationHistoryArgs as y, createExecutor as yt, DELEGATION_TRACE_MAX_BYTES as z, runAgentRounds as zt };
|
|
8742
10519
|
|
|
8743
|
-
//# sourceMappingURL=supervise-
|
|
10520
|
+
//# sourceMappingURL=supervise-U7_K5yuV.js.map
|