@tangle-network/agent-runtime 0.115.1 → 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-Bt_XVkjK.js → activation-BZ2FyX0O.js} +3 -3
- package/dist/{activation-Bt_XVkjK.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-Cygthiy3.d.ts → environment-provider-CTZjdIUg.d.ts} +661 -34
- 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-tEswzEPr.js → improvement-cycle-Bcqz8rZI.js} +160 -19
- package/dist/improvement-cycle-Bcqz8rZI.js.map +1 -0
- package/dist/{index-DZukewLl.d.ts → index-BHSYaWrC.d.ts} +3 -3
- package/dist/{index-K7nucOmw.d.ts → index-BTUG4Xln.d.ts} +8 -7
- package/dist/{index-BUWd8QJq.d.ts → index-DPWJxSRa.d.ts} +884 -497
- package/dist/{index-C-FYUuFG.d.ts → index-X2YwyWkc.d.ts} +2 -2
- package/dist/index.d.ts +9 -10
- package/dist/index.js +12 -13
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +10 -9
- package/dist/intelligence.js +15 -9
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +8 -8
- package/dist/{knowledge-CUXQE8Sq.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/{local-harness-BnB1E9nM.d.ts → local-harness-t6cDWDQ2.d.ts} +3 -105
- package/dist/{loop-runner-bin-DzJz48Fb.js → loop-runner-bin-BxhhDtmG.js} +4 -4
- package/dist/{loop-runner-bin-DzJz48Fb.js.map → loop-runner-bin-BxhhDtmG.js.map} +1 -1
- package/dist/{loop-runner-bin-BZl5vp7t.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 +4 -43
- package/dist/mcp/index.js +5 -205
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CynwZMZd.js → openai-tools-CO9X36zy.js} +2 -2
- package/dist/{openai-tools-CynwZMZd.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/{redact-BRaHUFMf.d.ts → redact-BEtQtvd6.d.ts} +2 -2
- package/dist/{runtime-BatQajPB.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-DEf37yQy.js → structural-rollout-DLwW69R4.js} +4 -4
- package/dist/structural-rollout-DLwW69R4.js.map +1 -0
- package/dist/{supervise-B7TIJR3D.js → supervise-U7_K5yuV.js} +2645 -829
- package/dist/supervise-U7_K5yuV.js.map +1 -0
- package/dist/{supervisor-DzQu5Ydu.js → supervisor-CAXiCKnX.js} +3802 -967
- 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-tEswzEPr.js.map +0 -1
- package/dist/knowledge-CUXQE8Sq.js.map +0 -1
- package/dist/otel-export-CPZTSADj.js +0 -852
- package/dist/otel-export-CPZTSADj.js.map +0 -1
- package/dist/prepare-BHQBb02e.js.map +0 -1
- package/dist/runtime-BatQajPB.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-DEf37yQy.js.map +0 -1
- package/dist/supervise-B7TIJR3D.js.map +0 -1
- package/dist/supervisor-DzQu5Ydu.js.map +0 -1
- package/dist/trace-propagation-B-pL7xn_.js +0 -57
- package/dist/trace-propagation-B-pL7xn_.js.map +0 -1
|
@@ -1,19 +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 { A as captureWorktreeDiff, C as routerChatWithUsage, E as runBrainLoop, M as removeWorktree, O as runWorktreeChecks, a as runFinalizer, i as pickBestDelivered, j as createWorktree, k as runWorktreeHarness, l as withDriverExecutor, n as bestDelivered, o as runTree, t as createSupervisor, x as routerBrain, y as createActivityLog } from "./supervisor-DzQu5Ydu.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 {
|
|
8
|
-
import { i as resolveAgentEnvironmentProvider, n as providerAsExecutor, s as buildBackendOptions } from "./environment-provider-CCaEhA-l.js";
|
|
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";
|
|
9
8
|
import { t as createStdioToolServer } from "./tool-server-RcWgLIsL.js";
|
|
10
9
|
import { n as UI_LENSES } from "./substrate-B0TYNrXn.js";
|
|
11
10
|
import { argHash, computeFindingId, errorStreakDetector, estimateCost, isModelPriced, makeFinding, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
|
|
12
|
-
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
11
|
+
import { agentProfileSchema, canonicalCandidateDigest, mergeAgentProfiles, validateAgentProfileSecurity } from "@tangle-network/agent-interface";
|
|
13
12
|
import { randomUUID } from "node:crypto";
|
|
14
13
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
15
14
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
-
import path, { dirname, isAbsolute, join, sep } from "node:path";
|
|
15
|
+
import path, { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
17
16
|
import { spawn } from "node:child_process";
|
|
18
17
|
import { homedir, tmpdir } from "node:os";
|
|
19
18
|
import { createServer, request } from "node:http";
|
|
@@ -21,6 +20,28 @@ import { request as request$1 } from "node:https";
|
|
|
21
20
|
import { Readable, Writable } from "node:stream";
|
|
22
21
|
//#region src/runtime/supervise/completion-gate.ts
|
|
23
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
|
+
/**
|
|
24
45
|
* Wrap an `Executor` so its settlement `valid` reflects the deliverable check, not the
|
|
25
46
|
* inner verdict. Handles both `execute` shapes (one-shot `Promise<ExecutorResult>` and
|
|
26
47
|
* streaming `AsyncIterable<UsageEvent>` + `resultArtifact()`); the check runs once the inner
|
|
@@ -40,7 +61,22 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
40
61
|
score: baseScore ?? (delivered ? 1 : 0)
|
|
41
62
|
};
|
|
42
63
|
};
|
|
43
|
-
|
|
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, {
|
|
44
80
|
runtime: inner.runtime,
|
|
45
81
|
...inner.budgetExempt !== void 0 ? { budgetExempt: inner.budgetExempt } : {},
|
|
46
82
|
...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
|
|
@@ -49,13 +85,21 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
49
85
|
...inner.metered ? { metered: () => inner.metered?.() } : {},
|
|
50
86
|
execute(task, signal) {
|
|
51
87
|
const r = inner.execute(task, signal);
|
|
52
|
-
if (isAsyncIterable$
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
94
|
})();
|
|
57
95
|
return (async () => {
|
|
58
|
-
|
|
96
|
+
let res;
|
|
97
|
+
try {
|
|
98
|
+
res = await r;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await settleVerdict();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
59
103
|
gated = await check(res.out, res.verdict?.score);
|
|
60
104
|
return {
|
|
61
105
|
...res,
|
|
@@ -71,19 +115,281 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
71
115
|
verdict: gated ?? art.verdict
|
|
72
116
|
};
|
|
73
117
|
}
|
|
74
|
-
};
|
|
118
|
+
});
|
|
75
119
|
}
|
|
76
|
-
function isAsyncIterable$
|
|
120
|
+
function isAsyncIterable$2(v) {
|
|
77
121
|
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
78
122
|
}
|
|
79
123
|
//#endregion
|
|
80
|
-
//#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
|
+
}
|
|
81
156
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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.
|
|
86
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
|
|
87
393
|
/**
|
|
88
394
|
* Throw a `ConfigError` when `allowed` is set, `model` is defined, and `model` is not a
|
|
89
395
|
* member of `allowed`. No-op when `allowed` is unset (the unrestricted default) or when
|
|
@@ -93,6 +399,14 @@ function assertModelAllowed(model, allowed) {
|
|
|
93
399
|
if (!allowed || model === void 0) return;
|
|
94
400
|
if (!allowed.includes(model)) throw new ConfigError(`model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`);
|
|
95
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
|
+
}
|
|
96
410
|
//#endregion
|
|
97
411
|
//#region src/runtime/supervise/otel-spans.ts
|
|
98
412
|
/**
|
|
@@ -282,6 +596,12 @@ function createSupervisorSpanRecorder(opts) {
|
|
|
282
596
|
} },
|
|
283
597
|
traceId,
|
|
284
598
|
rootSpanId,
|
|
599
|
+
workerTrace(spawningNodeId) {
|
|
600
|
+
return {
|
|
601
|
+
traceId,
|
|
602
|
+
parentSpanId: spanIdOf.get(spawningNodeId) ?? rootSpanId
|
|
603
|
+
};
|
|
604
|
+
},
|
|
285
605
|
async finish(outcome) {
|
|
286
606
|
if (finished) return;
|
|
287
607
|
finished = true;
|
|
@@ -397,59 +717,96 @@ function truncate(value) {
|
|
|
397
717
|
}
|
|
398
718
|
//#endregion
|
|
399
719
|
//#region src/runtime/supervise/coordination-log.ts
|
|
400
|
-
/**
|
|
401
|
-
*
|
|
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. */
|
|
402
739
|
function persisted(event) {
|
|
403
|
-
return event.type
|
|
740
|
+
return event.type !== "settled";
|
|
404
741
|
}
|
|
405
742
|
/** FS-backed `CoordinationLog`: append-only JSONL, fsynced per record. */
|
|
406
743
|
var FileCoordinationLog = class {
|
|
407
744
|
path;
|
|
745
|
+
appendTail = Promise.resolve();
|
|
408
746
|
constructor(path) {
|
|
409
747
|
this.path = path;
|
|
410
748
|
}
|
|
411
|
-
async append(runId,
|
|
412
|
-
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) {
|
|
413
756
|
const fs = await import("node:fs/promises");
|
|
414
757
|
const path = await import("node:path");
|
|
415
758
|
await fs.mkdir(path.dirname(this.path), { recursive: true });
|
|
416
759
|
const record = {
|
|
417
760
|
runId,
|
|
418
|
-
|
|
419
|
-
|
|
761
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
762
|
+
...busRecord
|
|
420
763
|
};
|
|
764
|
+
const needsSeparator = await prepareJsonlAppend(this.path);
|
|
421
765
|
const fh = await fs.open(this.path, "a");
|
|
422
766
|
try {
|
|
423
|
-
await fh
|
|
767
|
+
await writeAllBytes(fh, `${needsSeparator ? "\n" : ""}${JSON.stringify(record)}\n`);
|
|
424
768
|
await fh.sync();
|
|
425
769
|
} finally {
|
|
426
770
|
await fh.close();
|
|
427
771
|
}
|
|
428
772
|
}
|
|
429
|
-
async load(runId) {
|
|
773
|
+
async load(runId, ownerId) {
|
|
430
774
|
const fs = await import("node:fs/promises");
|
|
431
775
|
let text;
|
|
432
776
|
try {
|
|
433
777
|
text = await fs.readFile(this.path, "utf8");
|
|
434
778
|
} catch (err) {
|
|
435
|
-
if (isNoEntError(err)) return
|
|
436
|
-
questions: [],
|
|
437
|
-
findings: []
|
|
438
|
-
};
|
|
779
|
+
if (isNoEntError(err)) return emptyPriorCoordination(ownerId);
|
|
439
780
|
throw err;
|
|
440
781
|
}
|
|
441
782
|
const byId = /* @__PURE__ */ new Map();
|
|
442
783
|
const findings = [];
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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);
|
|
447
803
|
const ev = record.event;
|
|
804
|
+
if (ev.type === "delivery-attempt" || ev.type === "steer" || ev.type === "answer") deliveryEvidence.push(ev);
|
|
448
805
|
if (ev.type === "question") byId.set(ev.question.id, ev.question);
|
|
449
806
|
else if (ev.type === "finding") findings.push(ev.finding);
|
|
450
807
|
else if (ev.type === "answer") {
|
|
451
808
|
const prior = byId.get(ev.questionId);
|
|
452
|
-
if (prior) byId.set(ev.questionId, {
|
|
809
|
+
if (prior && ev.down.delivered) byId.set(ev.questionId, {
|
|
453
810
|
...prior,
|
|
454
811
|
status: "answered",
|
|
455
812
|
decision: {
|
|
@@ -458,14 +815,28 @@ var FileCoordinationLog = class {
|
|
|
458
815
|
by: "prior-run"
|
|
459
816
|
}
|
|
460
817
|
});
|
|
461
|
-
}
|
|
818
|
+
} else if (ev.type === "instruction") continuations.push(ev.instruction);
|
|
462
819
|
}
|
|
463
820
|
return {
|
|
821
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
464
822
|
questions: [...byId.values()],
|
|
465
|
-
findings
|
|
823
|
+
findings,
|
|
824
|
+
continuations,
|
|
825
|
+
deliveryEvidence,
|
|
826
|
+
records
|
|
466
827
|
};
|
|
467
828
|
}
|
|
468
829
|
};
|
|
830
|
+
function emptyPriorCoordination(ownerId) {
|
|
831
|
+
return {
|
|
832
|
+
...ownerId !== void 0 ? { ownerId } : {},
|
|
833
|
+
questions: [],
|
|
834
|
+
findings: [],
|
|
835
|
+
continuations: [],
|
|
836
|
+
deliveryEvidence: [],
|
|
837
|
+
records: []
|
|
838
|
+
};
|
|
839
|
+
}
|
|
469
840
|
function isNoEntError(err) {
|
|
470
841
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
471
842
|
}
|
|
@@ -1556,9 +1927,10 @@ function createInbox() {
|
|
|
1556
1927
|
return {
|
|
1557
1928
|
deliver(msg) {
|
|
1558
1929
|
const m = parseDown(msg);
|
|
1559
|
-
if (!m) return;
|
|
1930
|
+
if (!m) return false;
|
|
1560
1931
|
pending.push(m);
|
|
1561
1932
|
if (m.interrupt && live && !live.signal.aborted) live.abort();
|
|
1933
|
+
return true;
|
|
1562
1934
|
},
|
|
1563
1935
|
drain() {
|
|
1564
1936
|
return pending.splice(0, pending.length);
|
|
@@ -2215,8 +2587,8 @@ function sandboxSessionTraceSource(box, sessionId, opts = {}) {
|
|
|
2215
2587
|
* | `profile.prompt.systemPrompt` | honored — prepended to the task text (pi RPC takes no separate system-prompt channel) |
|
|
2216
2588
|
* | `profile.mcp` | honored — written to this execution's own file and passed as `--mcp-config` for `pi-mcp-adapter`; see `pi-mcp.ts` |
|
|
2217
2589
|
* | `profile.extensions.pi.load` | honored — lowered to `--no-extensions` + `--extension <abs>` |
|
|
2218
|
-
* | `profile.prompt.instructions` |
|
|
2219
|
-
* | `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 |
|
|
2220
2592
|
* | `profile.model.reasoningEffort` | DROPPED — no `--thinking` flag is emitted, so pi's configured `defaultThinkingLevel` applies |
|
|
2221
2593
|
* | `profile.tools` | DROPPED — no `--no-tools` / allow-deny mapping; pi runs its full builtin tool set |
|
|
2222
2594
|
* | `profile.permissions` | DROPPED |
|
|
@@ -2237,11 +2609,17 @@ const PI_RUNTIME = "pi";
|
|
|
2237
2609
|
const piSeamKey = "pi";
|
|
2238
2610
|
/** Build the `Executor` for one pi worker. Registered as runtime `'pi'`. */
|
|
2239
2611
|
const piExecutor = (spec, ctx) => {
|
|
2240
|
-
const seam =
|
|
2612
|
+
const seam = {
|
|
2613
|
+
...readPiSeam(ctx),
|
|
2614
|
+
...spec.profile.model?.default ? { model: spec.profile.model.default } : {}
|
|
2615
|
+
};
|
|
2616
|
+
const traceEnv = workerTraceEnv(ctx);
|
|
2241
2617
|
const inbox = createInbox();
|
|
2242
2618
|
const activity = createActivityLog(seam.activityWindow ?? 12);
|
|
2243
2619
|
const runId = `pi-${spec.profile.name ?? "worker"}-${Date.now()}`;
|
|
2244
2620
|
const trace = createPushTraceSource({ runId });
|
|
2621
|
+
const executionId = ctx.node?.nodeId ?? `pi-run-${randomUUID()}`;
|
|
2622
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
2245
2623
|
const derived = [];
|
|
2246
2624
|
const controller = new AbortController();
|
|
2247
2625
|
const cascade = () => controller.abort();
|
|
@@ -2254,7 +2632,7 @@ const piExecutor = (spec, ctx) => {
|
|
|
2254
2632
|
proc: void 0,
|
|
2255
2633
|
artifact: void 0
|
|
2256
2634
|
};
|
|
2257
|
-
|
|
2635
|
+
const executor = {
|
|
2258
2636
|
runtime: "pi",
|
|
2259
2637
|
deliver: (m) => inbox.deliver(m),
|
|
2260
2638
|
progress: () => ({
|
|
@@ -2271,6 +2649,7 @@ const piExecutor = (spec, ctx) => {
|
|
|
2271
2649
|
signal,
|
|
2272
2650
|
controller,
|
|
2273
2651
|
seam,
|
|
2652
|
+
traceEnv,
|
|
2274
2653
|
spec,
|
|
2275
2654
|
runId,
|
|
2276
2655
|
inbox,
|
|
@@ -2294,6 +2673,44 @@ const piExecutor = (spec, ctx) => {
|
|
|
2294
2673
|
return state.artifact;
|
|
2295
2674
|
}
|
|
2296
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
|
+
});
|
|
2297
2714
|
};
|
|
2298
2715
|
/**
|
|
2299
2716
|
* One pi RPC session, run to `agent_settled`. Every steer delivered while the turn is in flight is
|
|
@@ -2310,6 +2727,7 @@ async function* streamPiSession(args) {
|
|
|
2310
2727
|
};
|
|
2311
2728
|
let usd = 0;
|
|
2312
2729
|
let usdKnown = true;
|
|
2730
|
+
let tokensKnown = true;
|
|
2313
2731
|
throwIfAborted(args.signal);
|
|
2314
2732
|
throwIfAborted(args.controller.signal);
|
|
2315
2733
|
const piMcp = preparePiMcp(args.spec.profile, {
|
|
@@ -2329,7 +2747,7 @@ async function* streamPiSession(args) {
|
|
|
2329
2747
|
}
|
|
2330
2748
|
let proc;
|
|
2331
2749
|
try {
|
|
2332
|
-
proc = spawnPi(seam, piMcp.args);
|
|
2750
|
+
proc = spawnPi(seam, piMcp.args, args.traceEnv);
|
|
2333
2751
|
} catch (spawnFailure) {
|
|
2334
2752
|
piMcp.mount?.cleanup();
|
|
2335
2753
|
throw spawnFailure;
|
|
@@ -2376,7 +2794,7 @@ async function* streamPiSession(args) {
|
|
|
2376
2794
|
};
|
|
2377
2795
|
args.signal.addEventListener("abort", abortAll, { once: true });
|
|
2378
2796
|
args.controller.signal.addEventListener("abort", abortAll, { once: true });
|
|
2379
|
-
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");
|
|
2380
2798
|
const opening = system ? `${system}\n\n${taskText(args.task)}` : taskText(args.task);
|
|
2381
2799
|
const deadline = seam.turnTimeoutMs ? Date.now() + seam.turnTimeoutMs : void 0;
|
|
2382
2800
|
const sendPrompt = (message, streamingBehavior) => {
|
|
@@ -2406,6 +2824,7 @@ async function* streamPiSession(args) {
|
|
|
2406
2824
|
}
|
|
2407
2825
|
const projected = projectPiEvent(ev, args, tokens, pendingTools);
|
|
2408
2826
|
if (projected.assistant) lastAssistant = projected.assistant;
|
|
2827
|
+
if (projected.tokensUnknown) tokensKnown = false;
|
|
2409
2828
|
for (const usage of projected.events) {
|
|
2410
2829
|
if (usage.kind === "cost") {
|
|
2411
2830
|
usd += usage.usd;
|
|
@@ -2459,6 +2878,7 @@ async function* streamPiSession(args) {
|
|
|
2459
2878
|
const spent = {
|
|
2460
2879
|
iterations: state.turns,
|
|
2461
2880
|
tokens,
|
|
2881
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
2462
2882
|
usd,
|
|
2463
2883
|
...usdKnown ? {} : { usdKnown: false },
|
|
2464
2884
|
ms: Date.now() - started
|
|
@@ -2562,9 +2982,11 @@ function projectPiEvent(ev, args, tokens, pendingTools) {
|
|
|
2562
2982
|
});
|
|
2563
2983
|
out.push({ kind: "iteration" });
|
|
2564
2984
|
const assistant = readAssistantOutcome(ev.message);
|
|
2985
|
+
const tokensUnknown = !usage || usage.tokensKnown === false;
|
|
2565
2986
|
return {
|
|
2566
2987
|
events: out,
|
|
2567
|
-
...assistant ? { assistant } : {}
|
|
2988
|
+
...assistant ? { assistant } : {},
|
|
2989
|
+
...tokensUnknown ? { tokensUnknown: true } : {}
|
|
2568
2990
|
};
|
|
2569
2991
|
}
|
|
2570
2992
|
return { events: out };
|
|
@@ -2577,14 +2999,18 @@ function readUsage(message) {
|
|
|
2577
2999
|
const usage = message.usage;
|
|
2578
3000
|
if (!usage || typeof usage !== "object") return void 0;
|
|
2579
3001
|
const u = usage;
|
|
2580
|
-
const
|
|
2581
|
-
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;
|
|
2582
3007
|
const costRaw = u.cost;
|
|
2583
3008
|
const reportedUsd = num(costRaw) ?? (costRaw && typeof costRaw === "object" ? num(costRaw.total) ?? num(costRaw.totalCost) : void 0);
|
|
2584
3009
|
return {
|
|
2585
3010
|
input,
|
|
2586
3011
|
output,
|
|
2587
|
-
...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
|
|
2588
3014
|
};
|
|
2589
3015
|
}
|
|
2590
3016
|
function readAssistantOutcome(message) {
|
|
@@ -2649,8 +3075,14 @@ function taskText(task) {
|
|
|
2649
3075
|
* the profile-derived extension flags (`--no-extensions` / `--extension`). Seam args go LAST so an
|
|
2650
3076
|
* operator's explicit flag wins over a derived one under pi's last-flag-wins parsing; RPC mode has
|
|
2651
3077
|
* no positional prompt, so nothing here has to precede an argument.
|
|
3078
|
+
*
|
|
3079
|
+
* `traceEnv` is the inherited `TRACE_ID` / `PARENT_SPAN_ID` pair (empty when the run records no
|
|
3080
|
+
* spans). It sits ABOVE the supervisor's ambient `process.env` — a supervisor that was itself
|
|
3081
|
+
* launched as someone's worker holds ids describing ITS place in an outer trace, which are the
|
|
3082
|
+
* wrong parent for this child — and BELOW `seam.env`, so an operator who sets either id explicitly
|
|
3083
|
+
* still wins. See `worker-trace.ts` for the full precedence rule.
|
|
2652
3084
|
*/
|
|
2653
|
-
function spawnPi(seam, profileArgs = []) {
|
|
3085
|
+
function spawnPi(seam, profileArgs = [], traceEnv = {}) {
|
|
2654
3086
|
const bin = seam.bin ?? "pi";
|
|
2655
3087
|
const argv = ["--mode", "rpc"];
|
|
2656
3088
|
if (seam.model) {
|
|
@@ -2664,6 +3096,7 @@ function spawnPi(seam, profileArgs = []) {
|
|
|
2664
3096
|
...seam.cwd ? { cwd: seam.cwd } : {},
|
|
2665
3097
|
env: {
|
|
2666
3098
|
...process.env,
|
|
3099
|
+
...traceEnv,
|
|
2667
3100
|
...seam.env ?? {}
|
|
2668
3101
|
},
|
|
2669
3102
|
stdio: [
|
|
@@ -2810,7 +3243,10 @@ function createSteerableSandboxSession(args) {
|
|
|
2810
3243
|
profile: args.profile,
|
|
2811
3244
|
taskToPrompt: args.taskToPrompt,
|
|
2812
3245
|
name: args.profile.name ?? String(args.harness),
|
|
2813
|
-
sandboxOverrides: {
|
|
3246
|
+
sandboxOverrides: {
|
|
3247
|
+
backend: { type: args.harness },
|
|
3248
|
+
...args.traceEnv && Object.keys(args.traceEnv).length > 0 ? { env: args.traceEnv } : {}
|
|
3249
|
+
}
|
|
2814
3250
|
};
|
|
2815
3251
|
const promptOptions = readPromptOptions(args.loopCtx);
|
|
2816
3252
|
const started = now();
|
|
@@ -3009,34 +3445,38 @@ function readPromptOptions(loopCtx) {
|
|
|
3009
3445
|
* Build a worktree-CLI leaf `Executor`. Per-spawn (a fresh worktree + abort + teardown each), so a
|
|
3010
3446
|
* fanout of N profiles = N parallel worktrees that never clobber each other.
|
|
3011
3447
|
*
|
|
3012
|
-
* Fail-loud: an empty `repoRoot`/`harness
|
|
3013
|
-
*
|
|
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.
|
|
3014
3451
|
*
|
|
3015
3452
|
* @experimental
|
|
3016
3453
|
*/
|
|
3017
3454
|
function createWorktreeCliExecutor(options) {
|
|
3018
3455
|
if (!options.repoRoot) throw new ValidationError("createWorktreeCliExecutor: repoRoot required");
|
|
3019
3456
|
if (!options.harness) throw new ValidationError("createWorktreeCliExecutor: harness required");
|
|
3020
|
-
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");
|
|
3021
3458
|
if (options.codexReproducible && options.harness !== "codex") throw new ValidationError("createWorktreeCliExecutor: codexReproducible requires harness \"codex\"");
|
|
3022
3459
|
if (options.codexReproducible && options.budgetExempt === true) throw new ValidationError("createWorktreeCliExecutor: codexReproducible cannot be budgetExempt");
|
|
3023
3460
|
if (options.codexReadDeniedPaths !== void 0 && !options.codexReproducible) throw new ValidationError("createWorktreeCliExecutor: codexReadDeniedPaths requires codexReproducible");
|
|
3024
3461
|
const runId = options.runId ?? randomUUID();
|
|
3462
|
+
const attemptId = options.executionAttemptId ?? newExecutionAttemptId(runId);
|
|
3025
3463
|
const controller = new AbortController();
|
|
3026
3464
|
const budgetExempt = options.budgetExempt ?? !options.codexReproducible;
|
|
3027
3465
|
let run;
|
|
3028
3466
|
let artifact;
|
|
3029
|
-
|
|
3467
|
+
const profilePlan = worktreeProfileExecutionPlan(options.profile, options.harness);
|
|
3468
|
+
return attestRuntimeOwnedExecutor({
|
|
3030
3469
|
runtime: "cli",
|
|
3031
3470
|
budgetExempt,
|
|
3032
|
-
async execute(
|
|
3471
|
+
async execute(task, signal) {
|
|
3033
3472
|
const linked = linkSignals$1(signal, controller.signal);
|
|
3034
3473
|
const started = Date.now();
|
|
3474
|
+
const taskPrompt = executionTaskPrompt(task, options.taskPrompt);
|
|
3035
3475
|
run = await runWorktreeHarness({
|
|
3036
3476
|
repoRoot: options.repoRoot,
|
|
3037
3477
|
profile: options.profile,
|
|
3038
3478
|
harness: options.harness,
|
|
3039
|
-
taskPrompt
|
|
3479
|
+
taskPrompt,
|
|
3040
3480
|
runId,
|
|
3041
3481
|
...options.baseRef ? { baseRef: options.baseRef } : {},
|
|
3042
3482
|
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
@@ -3091,7 +3531,65 @@ function createWorktreeCliExecutor(options) {
|
|
|
3091
3531
|
if (!artifact) throw new ValidationError("createWorktreeCliExecutor: resultArtifact() read before execute() resolved");
|
|
3092
3532
|
return artifact;
|
|
3093
3533
|
}
|
|
3094
|
-
}
|
|
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");
|
|
3095
3593
|
}
|
|
3096
3594
|
/** Link two abort signals into one that fires when either does. Returns `undefined` when neither
|
|
3097
3595
|
* is present so the harness runner gets no signal at all. */
|
|
@@ -3178,7 +3676,7 @@ function zeroSpend() {
|
|
|
3178
3676
|
*/
|
|
3179
3677
|
const routerInlineExecutor = (spec, ctx) => {
|
|
3180
3678
|
const seam = readSeam(ctx, routerSeamKey, "router/inline");
|
|
3181
|
-
const model =
|
|
3679
|
+
const model = spec.profile.model?.default ?? seam.model;
|
|
3182
3680
|
if (!model) throw new ValidationError("routerInlineExecutor: no model — set RouterSeam.model or AgentProfile.model.default");
|
|
3183
3681
|
if (!seam.routerBaseUrl || !seam.routerKey) throw new ValidationError("routerInlineExecutor: RouterSeam.routerBaseUrl + routerKey required");
|
|
3184
3682
|
const controller = new AbortController();
|
|
@@ -3188,7 +3686,9 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3188
3686
|
abortIfSignalled();
|
|
3189
3687
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3190
3688
|
let artifact;
|
|
3191
|
-
|
|
3689
|
+
const executionId = ctx.node?.nodeId ?? `router-request-${randomUUID()}`;
|
|
3690
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
3691
|
+
return attestRuntimeOwnedExecutor({
|
|
3192
3692
|
runtime: "router",
|
|
3193
3693
|
async execute(task, signal) {
|
|
3194
3694
|
const messages = taskToMessages(task, spec);
|
|
@@ -3206,6 +3706,8 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3206
3706
|
output: r.usage.output
|
|
3207
3707
|
} : zeroTokenUsage(),
|
|
3208
3708
|
usd: r.costUsd ?? 0,
|
|
3709
|
+
...r.usage ? {} : { tokensKnown: false },
|
|
3710
|
+
...r.costUsd === void 0 ? { usdKnown: false } : {},
|
|
3209
3711
|
ms: Date.now() - started
|
|
3210
3712
|
};
|
|
3211
3713
|
const out = { content: r.content };
|
|
@@ -3230,7 +3732,35 @@ const routerInlineExecutor = (spec, ctx) => {
|
|
|
3230
3732
|
spent: artifact.spent
|
|
3231
3733
|
};
|
|
3232
3734
|
}
|
|
3233
|
-
}
|
|
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
|
+
});
|
|
3234
3764
|
};
|
|
3235
3765
|
const routerToolsSeamKey = "router-tools";
|
|
3236
3766
|
/**
|
|
@@ -3239,7 +3769,7 @@ const routerToolsSeamKey = "router-tools";
|
|
|
3239
3769
|
*/
|
|
3240
3770
|
const routerToolsInlineExecutor = (spec, ctx) => {
|
|
3241
3771
|
const seam = readSeam(ctx, routerToolsSeamKey, "router-tools");
|
|
3242
|
-
const model =
|
|
3772
|
+
const model = spec.profile.model?.default ?? seam.model;
|
|
3243
3773
|
if (!model) throw new ValidationError("routerToolsInlineExecutor: no model — set RouterToolsSeam.model or AgentProfile.model.default");
|
|
3244
3774
|
if (!seam.routerBaseUrl || !seam.routerKey) throw new ValidationError("routerToolsInlineExecutor: RouterToolsSeam.routerBaseUrl + routerKey required");
|
|
3245
3775
|
const maxTurns = seam.maxTurns ?? 200;
|
|
@@ -3251,13 +3781,16 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3251
3781
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3252
3782
|
const inbox = createInbox();
|
|
3253
3783
|
let artifact;
|
|
3254
|
-
|
|
3784
|
+
const executionId = ctx.node?.nodeId ?? `router-tools-run-${randomUUID()}`;
|
|
3785
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
3786
|
+
return attestRuntimeOwnedExecutor({
|
|
3255
3787
|
runtime: "router",
|
|
3256
3788
|
deliver: (m) => inbox.deliver(m),
|
|
3257
3789
|
async execute(task, signal) {
|
|
3258
3790
|
const started = Date.now();
|
|
3259
3791
|
const messages = [...taskToMessages(task, spec)];
|
|
3260
3792
|
const tokens = zeroTokenUsage();
|
|
3793
|
+
let tokensKnown = true;
|
|
3261
3794
|
let turns = 0;
|
|
3262
3795
|
let lastText = "";
|
|
3263
3796
|
const flush = () => {
|
|
@@ -3308,7 +3841,7 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3308
3841
|
if (u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number") {
|
|
3309
3842
|
tokens.input += u.prompt_tokens;
|
|
3310
3843
|
tokens.output += u.completion_tokens;
|
|
3311
|
-
}
|
|
3844
|
+
} else tokensKnown = false;
|
|
3312
3845
|
const msg = data.choices?.[0]?.message;
|
|
3313
3846
|
if (msg?.content) lastText = msg.content;
|
|
3314
3847
|
const toolCalls = msg?.tool_calls ?? [];
|
|
@@ -3370,11 +3903,14 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3370
3903
|
} catch {}
|
|
3371
3904
|
}
|
|
3372
3905
|
}
|
|
3373
|
-
const
|
|
3906
|
+
const priced = isModelPriced(model);
|
|
3907
|
+
const usd = priced ? estimateCost(tokens.input, tokens.output, model) : 0;
|
|
3374
3908
|
const spent = {
|
|
3375
3909
|
iterations: turns,
|
|
3376
3910
|
tokens,
|
|
3911
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
3377
3912
|
usd,
|
|
3913
|
+
...!priced || !tokensKnown ? { usdKnown: false } : {},
|
|
3378
3914
|
ms: Date.now() - started
|
|
3379
3915
|
};
|
|
3380
3916
|
const out = { content: lastText };
|
|
@@ -3399,7 +3935,37 @@ const routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3399
3935
|
spent: artifact.spent
|
|
3400
3936
|
};
|
|
3401
3937
|
}
|
|
3402
|
-
}
|
|
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
|
+
});
|
|
3403
3969
|
};
|
|
3404
3970
|
/**
|
|
3405
3971
|
* COMPOSES `runAgentRounds` as a single-task leaf: one box, a refine driver bounded to
|
|
@@ -3420,6 +3986,7 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3420
3986
|
if (!seam.sandboxClient || typeof seam.sandboxClient.create !== "function") throw new ValidationError("sandboxExecutor: SandboxSeam.sandboxClient.create required");
|
|
3421
3987
|
const maxIterations = seam.maxIterations ?? 1;
|
|
3422
3988
|
if (!Number.isFinite(maxIterations) || maxIterations <= 0) throw new ValidationError("sandboxExecutor: maxIterations must be > 0");
|
|
3989
|
+
const traceEnv = workerTraceEnv(ctx);
|
|
3423
3990
|
const controller = new AbortController();
|
|
3424
3991
|
const abortIfSignalled = () => {
|
|
3425
3992
|
if (ctx.signal.aborted) controller.abort();
|
|
@@ -3427,6 +3994,43 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3427
3994
|
abortIfSignalled();
|
|
3428
3995
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3429
3996
|
let artifact;
|
|
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
|
+
};
|
|
3430
4034
|
if (seam.steering) {
|
|
3431
4035
|
const inbox = createInbox();
|
|
3432
4036
|
const session = createSteerableSandboxSession({
|
|
@@ -3438,9 +4042,10 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3438
4042
|
taskToPrompt: (t) => taskToPrompt(t),
|
|
3439
4043
|
options: seam.steering,
|
|
3440
4044
|
...seam.loopCtx ? { loopCtx: seam.loopCtx } : {},
|
|
4045
|
+
...Object.keys(traceEnv).length > 0 ? { traceEnv } : {},
|
|
3441
4046
|
contentRef
|
|
3442
4047
|
});
|
|
3443
|
-
return {
|
|
4048
|
+
return attestRuntimeOwnedExecutor({
|
|
3444
4049
|
runtime: "sandbox",
|
|
3445
4050
|
deliver: (m) => inbox.deliver(m),
|
|
3446
4051
|
progress: () => session.progress(),
|
|
@@ -3458,13 +4063,13 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3458
4063
|
if (!a) throw new ValidationError("sandboxExecutor(steering): resultArtifact() read before stream drained");
|
|
3459
4064
|
return a;
|
|
3460
4065
|
}
|
|
3461
|
-
};
|
|
4066
|
+
}, sandboxMaterialization, sandboxBinding);
|
|
3462
4067
|
}
|
|
3463
4068
|
const output = { parse(events) {
|
|
3464
4069
|
return { events };
|
|
3465
4070
|
} };
|
|
3466
4071
|
const driver = singleShotDriver(maxIterations);
|
|
3467
|
-
return {
|
|
4072
|
+
return attestRuntimeOwnedExecutor({
|
|
3468
4073
|
runtime: "sandbox",
|
|
3469
4074
|
execute(task, signal) {
|
|
3470
4075
|
return streamSandboxLeaf({
|
|
@@ -3478,6 +4083,7 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3478
4083
|
maxIterations,
|
|
3479
4084
|
controller,
|
|
3480
4085
|
loopCtx: seam.loopCtx,
|
|
4086
|
+
traceEnv,
|
|
3481
4087
|
onArtifact: (a) => {
|
|
3482
4088
|
artifact = a;
|
|
3483
4089
|
}
|
|
@@ -3491,7 +4097,7 @@ const sandboxExecutor = (spec, ctx) => {
|
|
|
3491
4097
|
if (!artifact) throw new ValidationError("sandboxExecutor: resultArtifact() read before stream drained");
|
|
3492
4098
|
return artifact;
|
|
3493
4099
|
}
|
|
3494
|
-
};
|
|
4100
|
+
}, sandboxMaterialization, sandboxBinding);
|
|
3495
4101
|
};
|
|
3496
4102
|
async function* streamSandboxLeaf(args) {
|
|
3497
4103
|
const linked = new AbortController();
|
|
@@ -3505,7 +4111,10 @@ async function* streamSandboxLeaf(args) {
|
|
|
3505
4111
|
profile: args.spec.profile,
|
|
3506
4112
|
taskToPrompt: (t) => taskToPrompt(t),
|
|
3507
4113
|
name: args.spec.profile.name ?? args.harness,
|
|
3508
|
-
sandboxOverrides: {
|
|
4114
|
+
sandboxOverrides: {
|
|
4115
|
+
backend: { type: args.harness },
|
|
4116
|
+
...Object.keys(args.traceEnv).length > 0 ? { env: args.traceEnv } : {}
|
|
4117
|
+
}
|
|
3509
4118
|
};
|
|
3510
4119
|
const started = Date.now();
|
|
3511
4120
|
const loopOptions = {
|
|
@@ -3524,8 +4133,10 @@ async function* streamSandboxLeaf(args) {
|
|
|
3524
4133
|
};
|
|
3525
4134
|
try {
|
|
3526
4135
|
const result = await runAgentRounds(loopOptions);
|
|
4136
|
+
const failure = failedRound(result);
|
|
4137
|
+
if (failure) throw failure;
|
|
3527
4138
|
const out = result.winner?.output ?? { events: [] };
|
|
3528
|
-
const verdict = result.winner?.verdict;
|
|
4139
|
+
const verdict = result.winner?.verdict ?? leafVerdict(result);
|
|
3529
4140
|
const spent = {
|
|
3530
4141
|
iterations: result.iterations.length,
|
|
3531
4142
|
tokens: {
|
|
@@ -3559,16 +4170,45 @@ async function* streamSandboxLeaf(args) {
|
|
|
3559
4170
|
args.controller.signal.removeEventListener("abort", cascade);
|
|
3560
4171
|
}
|
|
3561
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
|
+
}
|
|
3562
4202
|
/**
|
|
3563
4203
|
* Spawns a subprocess (`bin` + `args`). It cannot account tokens, so it is
|
|
3564
|
-
* `budgetExempt: true`:
|
|
3565
|
-
*
|
|
3566
|
-
* 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
|
|
3567
4206
|
* with a grace window. Streaming: yields one `iteration` event on clean exit.
|
|
3568
4207
|
*/
|
|
3569
4208
|
const cliExecutor = (_spec, ctx) => {
|
|
3570
4209
|
const seam = readSeam(ctx, cliSeamKey, "cli");
|
|
3571
4210
|
if (!seam.bin) throw new ValidationError("cliExecutor: CliSeam.bin required");
|
|
4211
|
+
const traceEnv = workerTraceEnv(ctx);
|
|
3572
4212
|
const controller = new AbortController();
|
|
3573
4213
|
const abortIfSignalled = () => {
|
|
3574
4214
|
if (ctx.signal.aborted) controller.abort();
|
|
@@ -3577,13 +4217,16 @@ const cliExecutor = (_spec, ctx) => {
|
|
|
3577
4217
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3578
4218
|
let proc;
|
|
3579
4219
|
let artifact;
|
|
3580
|
-
|
|
4220
|
+
const executionId = ctx.node?.nodeId ?? `cli-process-${randomUUID()}`;
|
|
4221
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
4222
|
+
return attestRuntimeOwnedExecutor({
|
|
3581
4223
|
runtime: "cli",
|
|
3582
4224
|
budgetExempt: true,
|
|
3583
4225
|
execute(task, signal) {
|
|
3584
4226
|
return streamCliLeaf({
|
|
3585
4227
|
task,
|
|
3586
4228
|
signal,
|
|
4229
|
+
traceEnv,
|
|
3587
4230
|
seam,
|
|
3588
4231
|
controller,
|
|
3589
4232
|
onProc: (p) => {
|
|
@@ -3603,7 +4246,39 @@ const cliExecutor = (_spec, ctx) => {
|
|
|
3603
4246
|
if (!artifact) throw new ValidationError("cliExecutor: resultArtifact() read before stream drained");
|
|
3604
4247
|
return artifact;
|
|
3605
4248
|
}
|
|
3606
|
-
}
|
|
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
|
+
});
|
|
3607
4282
|
};
|
|
3608
4283
|
async function* streamCliLeaf(args) {
|
|
3609
4284
|
const prompt = taskToPrompt(args.task);
|
|
@@ -3611,6 +4286,7 @@ async function* streamCliLeaf(args) {
|
|
|
3611
4286
|
...args.seam.cwd ? { cwd: args.seam.cwd } : {},
|
|
3612
4287
|
env: {
|
|
3613
4288
|
...process.env,
|
|
4289
|
+
...args.traceEnv,
|
|
3614
4290
|
...args.seam.env ?? {}
|
|
3615
4291
|
},
|
|
3616
4292
|
stdio: [
|
|
@@ -3691,33 +4367,34 @@ function killWithGrace(proc, grace) {
|
|
|
3691
4367
|
* - STEERABLE: the down-leg `inbox` is drained at each turn boundary; a queued
|
|
3692
4368
|
* steer becomes the next turn's prompt on the same session, and the worker can't
|
|
3693
4369
|
* settle while a steer it never read is pending (the sandbox/router contract).
|
|
3694
|
-
* - ABORT:
|
|
3695
|
-
*
|
|
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.
|
|
3696
4372
|
*
|
|
3697
4373
|
* Reports REAL usage when the bridge surfaces it, never a fabricated cost.
|
|
3698
4374
|
*/
|
|
3699
|
-
/** Resolve the bridge wire model for this spawn
|
|
3700
|
-
*
|
|
3701
|
-
|
|
3702
|
-
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) {
|
|
3703
4378
|
const backend = ctx.seams.createOptions?.backend;
|
|
3704
|
-
const
|
|
3705
|
-
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;
|
|
3706
4382
|
if (!harness && !model) return seamModel;
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
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;
|
|
3711
4386
|
}
|
|
3712
4387
|
const bridgeExecutor = (spec, ctx) => {
|
|
3713
4388
|
const base = readSeam(ctx, bridgeSeamKey, "bridge");
|
|
4389
|
+
const effectiveProfile = agentProfileSchema.parse(mergeAgentProfiles(spec.profile, base.agentProfile) ?? spec.profile);
|
|
3714
4390
|
const seam = {
|
|
3715
4391
|
...base,
|
|
3716
|
-
model: bridgeCellModel(base.model, ctx)
|
|
4392
|
+
model: bridgeCellModel(base.model, ctx, effectiveProfile)
|
|
3717
4393
|
};
|
|
3718
|
-
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");
|
|
3719
4395
|
const maxTurns = seam.maxTurns ?? 200;
|
|
3720
4396
|
const sessionId = seam.sessionId ?? `bridge-${spec.profile.name ?? "worker"}-${randomUUID()}`;
|
|
4397
|
+
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(sessionId);
|
|
3721
4398
|
const controller = new AbortController();
|
|
3722
4399
|
const abortIfSignalled = () => {
|
|
3723
4400
|
if (ctx.signal.aborted) controller.abort();
|
|
@@ -3726,27 +4403,31 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
3726
4403
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3727
4404
|
const inbox = createInbox();
|
|
3728
4405
|
let artifact;
|
|
3729
|
-
|
|
4406
|
+
const activeRuns = /* @__PURE__ */ new Map();
|
|
4407
|
+
return attestRuntimeOwnedExecutor({
|
|
3730
4408
|
runtime: "cli",
|
|
3731
4409
|
deliver: (m) => inbox.deliver(m),
|
|
3732
4410
|
execute(task, signal) {
|
|
3733
4411
|
return streamBridgeSession({
|
|
3734
4412
|
task,
|
|
3735
4413
|
signal,
|
|
3736
|
-
|
|
4414
|
+
profile: effectiveProfile,
|
|
3737
4415
|
seam,
|
|
3738
4416
|
sessionId,
|
|
3739
4417
|
maxTurns,
|
|
3740
4418
|
inbox,
|
|
3741
4419
|
controller,
|
|
4420
|
+
activeRuns,
|
|
3742
4421
|
onArtifact: (a) => {
|
|
3743
4422
|
artifact = a;
|
|
3744
4423
|
}
|
|
3745
4424
|
});
|
|
3746
4425
|
},
|
|
3747
|
-
teardown(
|
|
4426
|
+
async teardown(grace) {
|
|
3748
4427
|
controller.abort();
|
|
3749
|
-
|
|
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) };
|
|
3750
4431
|
},
|
|
3751
4432
|
resultArtifact() {
|
|
3752
4433
|
if (!artifact) throw new ValidationError("bridgeExecutor: resultArtifact() read before stream drained");
|
|
@@ -3755,7 +4436,40 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
3755
4436
|
spent: artifact.spent
|
|
3756
4437
|
};
|
|
3757
4438
|
}
|
|
3758
|
-
}
|
|
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
|
+
});
|
|
3759
4473
|
};
|
|
3760
4474
|
/**
|
|
3761
4475
|
* One resumable cli-bridge session, run as a streamed turn loop. Turn 0 sends the
|
|
@@ -3769,12 +4483,13 @@ async function* streamBridgeSession(args) {
|
|
|
3769
4483
|
const started = Date.now();
|
|
3770
4484
|
const external = mergeAbortSignals(args.signal, args.controller.signal);
|
|
3771
4485
|
const tokens = zeroTokenUsage();
|
|
4486
|
+
let tokensKnown = true;
|
|
3772
4487
|
let usd = 0;
|
|
4488
|
+
let usdKnown = true;
|
|
3773
4489
|
let turns = 0;
|
|
3774
4490
|
let lastText = "";
|
|
3775
4491
|
const toolCalls = [];
|
|
3776
4492
|
let nextPrompt = taskToPrompt(args.task);
|
|
3777
|
-
const system = args.spec.profile.prompt?.systemPrompt;
|
|
3778
4493
|
for (let t = 0; t < args.maxTurns; t += 1) {
|
|
3779
4494
|
const pending = inbox.drain();
|
|
3780
4495
|
if (pending.length) {
|
|
@@ -3783,10 +4498,6 @@ async function* streamBridgeSession(args) {
|
|
|
3783
4498
|
}
|
|
3784
4499
|
if (nextPrompt === void 0) break;
|
|
3785
4500
|
const messages = [];
|
|
3786
|
-
if (t === 0 && typeof system === "string" && system.length > 0) messages.push({
|
|
3787
|
-
role: "system",
|
|
3788
|
-
content: system
|
|
3789
|
-
});
|
|
3790
4501
|
messages.push({
|
|
3791
4502
|
role: "user",
|
|
3792
4503
|
content: nextPrompt
|
|
@@ -3798,45 +4509,46 @@ async function* streamBridgeSession(args) {
|
|
|
3798
4509
|
if (external.aborted) turnController.abort();
|
|
3799
4510
|
else external.addEventListener("abort", abortTurn);
|
|
3800
4511
|
interruptSig.addEventListener("abort", abortTurn, { once: true });
|
|
3801
|
-
|
|
4512
|
+
let timedOut = false;
|
|
4513
|
+
const timer = seam.timeoutMs ? setTimeout(() => {
|
|
4514
|
+
timedOut = true;
|
|
4515
|
+
abortTurn();
|
|
4516
|
+
}, seam.timeoutMs) : void 0;
|
|
3802
4517
|
const cleanup = () => {
|
|
3803
4518
|
external.removeEventListener("abort", abortTurn);
|
|
3804
4519
|
if (timer) clearTimeout(timer);
|
|
3805
4520
|
};
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
} catch (e) {
|
|
3822
|
-
cleanup();
|
|
3823
|
-
if (e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) continue;
|
|
3824
|
-
throw e;
|
|
3825
|
-
}
|
|
3826
|
-
if (!res.ok) {
|
|
3827
|
-
cleanup();
|
|
3828
|
-
throw new ValidationError(`bridgeExecutor: bridge ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
3829
|
-
}
|
|
3830
|
-
if (!res.body) {
|
|
3831
|
-
cleanup();
|
|
3832
|
-
throw new ValidationError("bridgeExecutor: bridge response had no body to stream");
|
|
3833
|
-
}
|
|
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
|
+
};
|
|
3834
4536
|
let turnText = "";
|
|
4537
|
+
let turnTokensKnown = false;
|
|
4538
|
+
let turnUsdKnown = false;
|
|
4539
|
+
let interrupted = false;
|
|
3835
4540
|
try {
|
|
3836
|
-
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
|
+
})) {
|
|
3837
4548
|
if (chunk.content) turnText += chunk.content;
|
|
3838
4549
|
if (chunk.toolCall) toolCalls.push(chunk.toolCall);
|
|
3839
4550
|
if (chunk.usage) {
|
|
4551
|
+
turnTokensKnown = true;
|
|
3840
4552
|
tokens.input += chunk.usage.input;
|
|
3841
4553
|
tokens.output += chunk.usage.output;
|
|
3842
4554
|
yield {
|
|
@@ -3845,26 +4557,43 @@ async function* streamBridgeSession(args) {
|
|
|
3845
4557
|
output: chunk.usage.output
|
|
3846
4558
|
};
|
|
3847
4559
|
}
|
|
3848
|
-
if (typeof chunk.cost === "number"
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
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
|
+
}
|
|
3854
4569
|
}
|
|
3855
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
|
+
}
|
|
3856
4579
|
} finally {
|
|
3857
4580
|
cleanup();
|
|
3858
4581
|
}
|
|
4582
|
+
if (interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) interrupted = true;
|
|
3859
4583
|
turns += 1;
|
|
4584
|
+
if (!turnTokensKnown) tokensKnown = false;
|
|
4585
|
+
if (!turnUsdKnown) usdKnown = false;
|
|
3860
4586
|
yield { kind: "iteration" };
|
|
3861
|
-
if (turnText) lastText = turnText;
|
|
4587
|
+
if (!interrupted && turnText) lastText = turnText;
|
|
4588
|
+
if (interrupted) continue;
|
|
3862
4589
|
if (inbox.pending() === 0) break;
|
|
3863
4590
|
}
|
|
3864
4591
|
const spent = {
|
|
3865
4592
|
iterations: turns,
|
|
3866
4593
|
tokens,
|
|
4594
|
+
...tokensKnown ? {} : { tokensKnown: false },
|
|
3867
4595
|
usd,
|
|
4596
|
+
...usdKnown ? {} : { usdKnown: false },
|
|
3868
4597
|
ms: Date.now() - started
|
|
3869
4598
|
};
|
|
3870
4599
|
const out = {
|
|
@@ -3881,6 +4610,69 @@ async function* streamBridgeSession(args) {
|
|
|
3881
4610
|
spent
|
|
3882
4611
|
});
|
|
3883
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
|
+
}
|
|
3884
4676
|
/**
|
|
3885
4677
|
* POST one streamed turn to the cli-bridge over the `node:http(s)` core client
|
|
3886
4678
|
* instead of global `fetch`. The bridge runs a harness CLI and streams SSE only
|
|
@@ -3908,15 +4700,22 @@ function bridgeStreamPost(url, args) {
|
|
|
3908
4700
|
"content-type": "application/json",
|
|
3909
4701
|
authorization: `Bearer ${args.bearer}`,
|
|
3910
4702
|
"x-session-id": args.sessionId,
|
|
4703
|
+
"x-run-id": args.runId,
|
|
4704
|
+
...args.afterEventId > 0 ? { "last-event-id": String(args.afterEventId) } : {},
|
|
3911
4705
|
"content-length": Buffer.byteLength(payload)
|
|
3912
4706
|
},
|
|
3913
4707
|
timeout: 0
|
|
3914
4708
|
}, (res) => {
|
|
4709
|
+
response = res;
|
|
4710
|
+
res.once("close", () => args.signal.removeEventListener("abort", onAbort));
|
|
3915
4711
|
const status = res.statusCode ?? 0;
|
|
4712
|
+
const ok = status >= 200 && status < 300;
|
|
4713
|
+
const body = Readable.toWeb(res);
|
|
3916
4714
|
resolve({
|
|
3917
|
-
ok
|
|
4715
|
+
ok,
|
|
3918
4716
|
status,
|
|
3919
|
-
|
|
4717
|
+
headers: res.headers,
|
|
4718
|
+
body,
|
|
3920
4719
|
text: async () => {
|
|
3921
4720
|
const chunks = [];
|
|
3922
4721
|
for await (const c of res) chunks.push(c);
|
|
@@ -3924,8 +4723,10 @@ function bridgeStreamPost(url, args) {
|
|
|
3924
4723
|
}
|
|
3925
4724
|
});
|
|
3926
4725
|
});
|
|
4726
|
+
let response;
|
|
3927
4727
|
const onAbort = () => {
|
|
3928
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"));
|
|
3929
4730
|
};
|
|
3930
4731
|
if (args.signal.aborted) onAbort();
|
|
3931
4732
|
else args.signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -3933,16 +4734,109 @@ function bridgeStreamPost(url, args) {
|
|
|
3933
4734
|
args.signal.removeEventListener("abort", onAbort);
|
|
3934
4735
|
reject(e);
|
|
3935
4736
|
});
|
|
3936
|
-
req.on("close", () =>
|
|
4737
|
+
req.on("close", () => {
|
|
4738
|
+
if (!response) args.signal.removeEventListener("abort", onAbort);
|
|
4739
|
+
});
|
|
3937
4740
|
req.write(payload);
|
|
3938
4741
|
req.end();
|
|
3939
4742
|
});
|
|
3940
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
|
+
}
|
|
3941
4835
|
/**
|
|
3942
4836
|
* Parse cli-bridge's OpenAI-compatible SSE stream into normalized chunks. Each
|
|
3943
|
-
* `data:` line is an OpenAI chat-completion chunk (`choices[].delta`)
|
|
3944
|
-
*
|
|
3945
|
-
*
|
|
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.
|
|
3946
4840
|
*/
|
|
3947
4841
|
async function* parseSseChatStream(body) {
|
|
3948
4842
|
const reader = body.getReader();
|
|
@@ -3953,18 +4847,18 @@ async function* parseSseChatStream(body) {
|
|
|
3953
4847
|
const { done, value } = await reader.read();
|
|
3954
4848
|
if (done) break;
|
|
3955
4849
|
buf += decoder.decode(value, { stream: true });
|
|
3956
|
-
let
|
|
3957
|
-
while (
|
|
3958
|
-
const frame = buf.slice(0,
|
|
3959
|
-
buf = buf.slice(
|
|
3960
|
-
const
|
|
3961
|
-
if (
|
|
3962
|
-
|
|
3963
|
-
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);
|
|
3964
4857
|
}
|
|
3965
4858
|
}
|
|
4859
|
+
buf += decoder.decode();
|
|
3966
4860
|
const tail = parseSseStreamTail(buf);
|
|
3967
|
-
if (tail !== void 0
|
|
4861
|
+
if (tail !== void 0) yield tail;
|
|
3968
4862
|
} finally {
|
|
3969
4863
|
reader.releaseLock();
|
|
3970
4864
|
}
|
|
@@ -3986,25 +4880,41 @@ function parseSseStreamTail(buf) {
|
|
|
3986
4880
|
}
|
|
3987
4881
|
if (parsed.error) throw new ValidationError(`bridgeExecutor: bridge upstream error: ${parsed.error.message ?? parsed.error.type ?? "unknown"}`);
|
|
3988
4882
|
}
|
|
3989
|
-
/** Parse one SSE frame
|
|
3990
|
-
* or undefined (comment/keepalive/empty). */
|
|
4883
|
+
/** Parse one SSE frame into a numbered run event, terminal marker, or unnumbered keepalive. */
|
|
3991
4884
|
function parseSseFrame(frame) {
|
|
3992
4885
|
const dataLines = [];
|
|
4886
|
+
let id;
|
|
3993
4887
|
for (const rawLine of frame.split("\n")) {
|
|
3994
4888
|
const line = rawLine.replace(/\r$/, "");
|
|
3995
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
|
+
}
|
|
3996
4898
|
if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
3997
4899
|
}
|
|
3998
|
-
if (dataLines.length === 0) return void 0
|
|
4900
|
+
if (dataLines.length === 0) return id === void 0 ? void 0 : {
|
|
4901
|
+
kind: "event",
|
|
4902
|
+
id
|
|
4903
|
+
};
|
|
3999
4904
|
const data = dataLines.join("\n");
|
|
4000
|
-
if (data === "[DONE]") return "done";
|
|
4905
|
+
if (data === "[DONE]") return { kind: "done" };
|
|
4001
4906
|
let parsed;
|
|
4002
4907
|
try {
|
|
4003
4908
|
parsed = JSON.parse(data);
|
|
4004
4909
|
} catch {
|
|
4005
|
-
|
|
4910
|
+
throw new ValidationError("bridgeExecutor: bridge emitted a non-JSON SSE data frame");
|
|
4006
4911
|
}
|
|
4007
|
-
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
|
+
};
|
|
4008
4918
|
const out = {};
|
|
4009
4919
|
const choice = parsed.choices?.[0];
|
|
4010
4920
|
const content = choice?.delta?.content ?? choice?.message?.content;
|
|
@@ -4017,7 +4927,11 @@ function parseSseFrame(frame) {
|
|
|
4017
4927
|
output: u.completion_tokens ?? 0
|
|
4018
4928
|
};
|
|
4019
4929
|
if (typeof u?.cost === "number") out.cost = u.cost;
|
|
4020
|
-
return
|
|
4930
|
+
return {
|
|
4931
|
+
kind: "event",
|
|
4932
|
+
id,
|
|
4933
|
+
...Object.keys(out).length > 0 ? { chunk: out } : {}
|
|
4934
|
+
};
|
|
4021
4935
|
}
|
|
4022
4936
|
function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
4023
4937
|
const bridge = seam.bridge;
|
|
@@ -4025,6 +4939,9 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4025
4939
|
if (!bridge.bridgeUrl || !bridge.bridgeBearer) throw new ValidationError("cliWorktreeExecutor: bridge.bridgeUrl + bridge.bridgeBearer required");
|
|
4026
4940
|
const runId = seam.runId ?? randomUUID();
|
|
4027
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);
|
|
4028
4945
|
const controller = new AbortController();
|
|
4029
4946
|
const pending = [];
|
|
4030
4947
|
let inner;
|
|
@@ -4049,11 +4966,11 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4049
4966
|
}
|
|
4050
4967
|
pending.push(msg);
|
|
4051
4968
|
};
|
|
4052
|
-
return {
|
|
4969
|
+
return attestRuntimeOwnedExecutor({
|
|
4053
4970
|
runtime: "cli",
|
|
4054
4971
|
budgetExempt: seam.budgetExempt ?? false,
|
|
4055
4972
|
deliver,
|
|
4056
|
-
execute(
|
|
4973
|
+
execute(task, signal) {
|
|
4057
4974
|
return (async function* bridgeWorktreeStream() {
|
|
4058
4975
|
const started = Date.now();
|
|
4059
4976
|
const linked = mergeAbortSignals(signal, controller.signal);
|
|
@@ -4069,10 +4986,10 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4069
4986
|
const bridgeSeam = {
|
|
4070
4987
|
bridgeUrl: bridge.bridgeUrl,
|
|
4071
4988
|
bridgeBearer: bridge.bridgeBearer,
|
|
4072
|
-
model: resolveBridgeWorktreeModel(spec, bridge),
|
|
4073
4989
|
cwd: worktree.path,
|
|
4074
4990
|
sessionId,
|
|
4075
|
-
...bridge.
|
|
4991
|
+
...bridge.model ? { model: bridge.model } : {},
|
|
4992
|
+
...bridge.agentProfile ? { agentProfile: bridge.agentProfile } : {},
|
|
4076
4993
|
...bridge.timeoutMs !== void 0 ? { timeoutMs: bridge.timeoutMs } : {},
|
|
4077
4994
|
...bridge.maxTurns !== void 0 ? { maxTurns: bridge.maxTurns } : {}
|
|
4078
4995
|
};
|
|
@@ -4086,8 +5003,8 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4086
5003
|
};
|
|
4087
5004
|
inner = bridgeExecutor(spec, bridgeCtx);
|
|
4088
5005
|
for (const msg of pending.splice(0)) inner.deliver?.(msg);
|
|
4089
|
-
const run = inner.execute(
|
|
4090
|
-
if (isAsyncIterable(run)) {
|
|
5006
|
+
const run = inner.execute(task, linked);
|
|
5007
|
+
if (isAsyncIterable$1(run)) {
|
|
4091
5008
|
for await (const event of run) yield event;
|
|
4092
5009
|
bridgeArtifact = inner.resultArtifact();
|
|
4093
5010
|
} else bridgeArtifact = await run;
|
|
@@ -4153,13 +5070,48 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
4153
5070
|
if (!artifact) throw new ValidationError("cliWorktreeExecutor: bridge resultArtifact() read before stream drained");
|
|
4154
5071
|
return artifact;
|
|
4155
5072
|
}
|
|
4156
|
-
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
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
|
+
});
|
|
4163
5115
|
}
|
|
4164
5116
|
function bridgeOutputText(out) {
|
|
4165
5117
|
if (typeof out === "string") return out;
|
|
@@ -4173,7 +5125,7 @@ function bridgeOutputText(out) {
|
|
|
4173
5125
|
return String(out);
|
|
4174
5126
|
}
|
|
4175
5127
|
}
|
|
4176
|
-
function isAsyncIterable(value) {
|
|
5128
|
+
function isAsyncIterable$1(value) {
|
|
4177
5129
|
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
4178
5130
|
}
|
|
4179
5131
|
/**
|
|
@@ -4183,14 +5135,14 @@ function isAsyncIterable(value) {
|
|
|
4183
5135
|
*/
|
|
4184
5136
|
const cliWorktreeExecutor = (spec, ctx) => {
|
|
4185
5137
|
const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
|
|
4186
|
-
if (!seam.repoRoot
|
|
5138
|
+
if (!seam.repoRoot) throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot required");
|
|
4187
5139
|
if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
|
|
4188
5140
|
if (!seam.harness) throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set");
|
|
4189
5141
|
return createWorktreeCliExecutor({
|
|
4190
5142
|
repoRoot: seam.repoRoot,
|
|
4191
5143
|
profile: spec.profile,
|
|
4192
5144
|
harness: seam.harness,
|
|
4193
|
-
taskPrompt: seam.taskPrompt,
|
|
5145
|
+
...seam.taskPrompt !== void 0 ? { taskPrompt: seam.taskPrompt } : {},
|
|
4194
5146
|
...seam.runId ? { runId: seam.runId } : {},
|
|
4195
5147
|
...seam.baseRef ? { baseRef: seam.baseRef } : {},
|
|
4196
5148
|
...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
|
|
@@ -4202,9 +5154,110 @@ const cliWorktreeExecutor = (spec, ctx) => {
|
|
|
4202
5154
|
...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
|
|
4203
5155
|
...seam.runGit ? { runGit: seam.runGit } : {},
|
|
4204
5156
|
...seam.runCommand ? { runCommand: seam.runCommand } : {},
|
|
4205
|
-
...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 } : {}
|
|
4206
5159
|
});
|
|
4207
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
|
+
}
|
|
4208
5261
|
/**
|
|
4209
5262
|
* The single built-in executor factory. Picks a leaf backend by data (`config.backend`),
|
|
4210
5263
|
* injects the matching seam, and delegates to that backend's built-in implementation.
|
|
@@ -4214,8 +5267,10 @@ const cliWorktreeExecutor = (spec, ctx) => {
|
|
|
4214
5267
|
* `UsageEvent` reporting channel.
|
|
4215
5268
|
*/
|
|
4216
5269
|
function createExecutor(config) {
|
|
5270
|
+
const captured = snapshotExecutorConfig(config);
|
|
4217
5271
|
return (spec, ctx) => {
|
|
4218
|
-
const { backend, ...
|
|
5272
|
+
const { backend, ...seamData } = captured;
|
|
5273
|
+
const seam = Object.freeze(seamData);
|
|
4219
5274
|
const seamed = {
|
|
4220
5275
|
...ctx,
|
|
4221
5276
|
seams: {
|
|
@@ -4223,7 +5278,7 @@ function createExecutor(config) {
|
|
|
4223
5278
|
[backend]: seam
|
|
4224
5279
|
}
|
|
4225
5280
|
};
|
|
4226
|
-
switch (
|
|
5281
|
+
switch (captured.backend) {
|
|
4227
5282
|
case "router": return routerInlineExecutor(spec, seamed);
|
|
4228
5283
|
case "router-tools": return routerToolsInlineExecutor(spec, seamed);
|
|
4229
5284
|
case "bridge": return bridgeExecutor(spec, seamed);
|
|
@@ -4232,10 +5287,41 @@ function createExecutor(config) {
|
|
|
4232
5287
|
case "pi": return piExecutor(spec, seamed);
|
|
4233
5288
|
case "provider": {
|
|
4234
5289
|
const providerSeam = readSeam(seamed, providerSeamKey, "provider");
|
|
4235
|
-
|
|
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);
|
|
4236
5322
|
}
|
|
4237
5323
|
case "sandbox": {
|
|
4238
|
-
const harness = spec.harness ??
|
|
5324
|
+
const harness = spec.harness ?? captured.harness ?? null;
|
|
4239
5325
|
return sandboxExecutor({
|
|
4240
5326
|
...spec,
|
|
4241
5327
|
harness
|
|
@@ -4244,6 +5330,13 @@ function createExecutor(config) {
|
|
|
4244
5330
|
}
|
|
4245
5331
|
};
|
|
4246
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
|
+
}
|
|
4247
5340
|
/**
|
|
4248
5341
|
* The open resolver/registry. Pre-registers the three built-ins under their
|
|
4249
5342
|
* runtime tags (`'router'`, `'sandbox'`, `'cli'`) and accepts `register(name,
|
|
@@ -4251,8 +5344,8 @@ function createExecutor(config) {
|
|
|
4251
5344
|
* without touching the registry at all. NOT a closed switch; registration + BYO
|
|
4252
5345
|
* ARE the extension points.
|
|
4253
5346
|
*
|
|
4254
|
-
* `resolve` precedence (frozen in `ExecutorRegistry`): a BYO `spec.
|
|
4255
|
-
* `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
|
|
4256
5349
|
* harness-derived runtime (`'sandbox'` for any `BackendType`); else fail loud.
|
|
4257
5350
|
*/
|
|
4258
5351
|
function createExecutorRegistry() {
|
|
@@ -4268,6 +5361,10 @@ function createExecutorRegistry() {
|
|
|
4268
5361
|
factories.set(runtime, factory);
|
|
4269
5362
|
},
|
|
4270
5363
|
resolve(spec) {
|
|
5364
|
+
if (spec.executorFactory) return {
|
|
5365
|
+
succeeded: true,
|
|
5366
|
+
value: spec.executorFactory
|
|
5367
|
+
};
|
|
4271
5368
|
if (spec.executor) {
|
|
4272
5369
|
const byo = spec.executor;
|
|
4273
5370
|
return {
|
|
@@ -4321,11 +5418,11 @@ function taskToPrompt(task) {
|
|
|
4321
5418
|
}
|
|
4322
5419
|
return JSON.stringify(task);
|
|
4323
5420
|
}
|
|
4324
|
-
/** Router messages from the opaque task +
|
|
5421
|
+
/** Router messages from the opaque task + every portable profile prompt instruction. */
|
|
4325
5422
|
function taskToMessages(task, spec) {
|
|
4326
5423
|
const messages = [];
|
|
4327
|
-
const system = spec.profile.prompt?.systemPrompt;
|
|
4328
|
-
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({
|
|
4329
5426
|
role: "system",
|
|
4330
5427
|
content: system
|
|
4331
5428
|
});
|
|
@@ -4423,8 +5520,9 @@ function createInMemoryRunContext(opts = {}) {
|
|
|
4423
5520
|
* back on `Scope.resume` (rehydrated by `replaySpawnTree`) instead of being re-executed.
|
|
4424
5521
|
*
|
|
4425
5522
|
* Layout: `${dir}/spawn-journal.jsonl` (one JSONL record per event), `${dir}/blobs/` (one
|
|
4426
|
-
* content-addressed JSON file per settled result), and `${dir}/coordination-log.jsonl`
|
|
4427
|
-
*
|
|
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.
|
|
4428
5526
|
*
|
|
4429
5527
|
* Opt-in by construction — `createInMemoryRunContext()` is unchanged and stays the default, so no
|
|
4430
5528
|
* existing consumer writes to disk or resumes unless it asks for this.
|
|
@@ -4482,168 +5580,6 @@ function watchTrace(source, opts = {}) {
|
|
|
4482
5580
|
});
|
|
4483
5581
|
}
|
|
4484
5582
|
//#endregion
|
|
4485
|
-
//#region src/runtime/supervise/dispatch.ts
|
|
4486
|
-
/**
|
|
4487
|
-
*
|
|
4488
|
-
* `rollingDispatch` — the REFILLING dispatch policy over a `Scope`. It keeps `width` children in
|
|
4489
|
-
* flight and admits the next queued unit of work the instant one settles, instead of waiting for
|
|
4490
|
-
* a whole round to drain (`fanout`) or for a driver to decide again (`spawn → await → spawn`).
|
|
4491
|
-
*
|
|
4492
|
-
* The gap it closes: `fanout` opens every item at once and then drains — one round, no refill —
|
|
4493
|
-
* and a driver brain's manual loop opens one worker per model turn. Between those two there was
|
|
4494
|
-
* nothing that holds N slots full, which is why a 5-worker run can peak at 2 live workers and sit
|
|
4495
|
-
* ~50% idle. This is a policy over the EXISTING `Scope.spawn` / `Scope.next` primitives: it adds
|
|
4496
|
-
* no second admission path, so the conserved budget pool stays the only fence on total work and
|
|
4497
|
-
* `width` is only a fence on simultaneous work.
|
|
4498
|
-
*
|
|
4499
|
-
* Fail-closed by construction: an admission rejection (`budget-exhausted` / `depth-exceeded`) is
|
|
4500
|
-
* recorded and STOPS further admission — the loop then drains what is already live and returns.
|
|
4501
|
-
* It never retries a rejected spawn against the same pool, and it never spawns past `width`.
|
|
4502
|
-
*
|
|
4503
|
-
* ── The concurrency-cap ledger (read this before adding a fourth cap) ──────────────────────────
|
|
4504
|
-
*
|
|
4505
|
-
* Three unrelated caps bound "how much runs at once" in this stack, at three different layers.
|
|
4506
|
-
* They are NOT aware of each other, and the smallest one silently wins:
|
|
4507
|
-
*
|
|
4508
|
-
* 1. `CoordinationToolsOptions.maxLiveWorkers` (`src/mcp/tools/coordination.ts`) — supervisor
|
|
4509
|
-
* level. How many workers may be spawned-but-not-settled at once; `spawn_agent` fails closed
|
|
4510
|
-
* with `error: 'max-live-workers'` past it. Unset by default ⇒ NO cap at this layer.
|
|
4511
|
-
* 2. `SandboxLineage`'s `maxConcurrency` / `DEFAULT_FORK_CONCURRENCY = 4`
|
|
4512
|
-
* (`src/runtime/sandbox-lineage.ts`) — kernel level. How many BOXES one `runAgentRounds` fork wave
|
|
4513
|
-
* provisions at once. It bounds a single leaf's fanout, not the supervisor's worker count.
|
|
4514
|
-
* 3. A host's own live-box governor (e.g. the kernel's `ComputeGovernor`, `maxSandboxes = 4`) — fleet
|
|
4515
|
-
* level. How many sandboxes may exist across the whole host process.
|
|
4516
|
-
*
|
|
4517
|
-
* The honest effective limit on simultaneous WORKERS is the minimum of the caps that apply to the
|
|
4518
|
-
* worker layer — (1) and (3). (2) is a different unit (boxes per fork wave inside one leaf) and
|
|
4519
|
-
* must not be min'd into it, or a 4-way fork inside one worker reads as a 4-worker ceiling.
|
|
4520
|
-
* `effectiveConcurrency` computes that minimum in one place so a host derives ONE number and
|
|
4521
|
-
* passes it to BOTH `maxLiveWorkers` and this dispatcher's `width`, instead of leaving a fleet
|
|
4522
|
-
* governor of 4 and an unset worker fence as unrelated numbers.
|
|
4523
|
-
*
|
|
4524
|
-
* ── Why this is not a copy of the kernel's batch loop ──────────────────────────────────────────
|
|
4525
|
-
*
|
|
4526
|
-
* `runBatch` (`src/runtime/run-loop.ts`) runs the same hold-N-slots-full shape over the KERNEL's
|
|
4527
|
-
* substrate: bare promises in a `Set`, raced with `Promise.race`, bounded by `maxConcurrency`.
|
|
4528
|
-
* This one runs it over the SUPERVISOR's substrate — `Scope.spawn`'s atomic reservation against
|
|
4529
|
-
* the conserved pool, `Scope.next`'s journaled settlement cursor, and the blob store behind each
|
|
4530
|
-
* result. Neither can be expressed in the other's terms without dragging its whole substrate
|
|
4531
|
-
* along: the kernel loop has no budget to reserve and no cursor to order settlements by, and this
|
|
4532
|
-
* one cannot race raw promises because a settlement is only real once it is journaled. The shape
|
|
4533
|
-
* repeating across the two deliberate layers is resonance, not duplication — do not "unify" them
|
|
4534
|
-
* into a shared helper that would have to know about both.
|
|
4535
|
-
*
|
|
4536
|
-
* @experimental
|
|
4537
|
-
*/
|
|
4538
|
-
/**
|
|
4539
|
-
* Run the refilling dispatch loop over `scope` until the queue is dry (or a stop fires) and every
|
|
4540
|
-
* admitted child has settled. Returns the settlements in cursor order plus the admission ledger.
|
|
4541
|
-
*
|
|
4542
|
-
* The loop is: fill free slots from `nextUnit` → `await scope.next()` → deliver the settlement →
|
|
4543
|
-
* refill → repeat. Because the refill happens immediately after each settlement rather than after
|
|
4544
|
-
* a whole round, a slow child never idles the other slots.
|
|
4545
|
-
*/
|
|
4546
|
-
async function rollingDispatch(scope, opts) {
|
|
4547
|
-
if (!Number.isInteger(opts.width) || opts.width < 1) throw new ValidationError(`rollingDispatch: width must be a positive integer, got ${String(opts.width)}`);
|
|
4548
|
-
const settled = [];
|
|
4549
|
-
const rejected = [];
|
|
4550
|
-
const live = /* @__PURE__ */ new Set();
|
|
4551
|
-
let admitted = 0;
|
|
4552
|
-
let peakLive = 0;
|
|
4553
|
-
let queueDry = false;
|
|
4554
|
-
let stopReason = "drained";
|
|
4555
|
-
const fill = async () => {
|
|
4556
|
-
while (live.size < opts.width) {
|
|
4557
|
-
if (queueDry) return;
|
|
4558
|
-
if (scope.signal.aborted) {
|
|
4559
|
-
stopReason = "aborted";
|
|
4560
|
-
queueDry = true;
|
|
4561
|
-
return;
|
|
4562
|
-
}
|
|
4563
|
-
if (opts.shouldStop?.() === true) {
|
|
4564
|
-
stopReason = "stopped";
|
|
4565
|
-
queueDry = true;
|
|
4566
|
-
return;
|
|
4567
|
-
}
|
|
4568
|
-
const unit = await opts.nextUnit();
|
|
4569
|
-
if (unit === void 0) {
|
|
4570
|
-
queueDry = true;
|
|
4571
|
-
return;
|
|
4572
|
-
}
|
|
4573
|
-
const res = scope.spawn(unit.agent, unit.task, unit.opts);
|
|
4574
|
-
if (!res.ok) {
|
|
4575
|
-
rejected.push(`${unit.opts.label}: ${res.reason}`);
|
|
4576
|
-
stopReason = "not-admitted";
|
|
4577
|
-
queueDry = true;
|
|
4578
|
-
return;
|
|
4579
|
-
}
|
|
4580
|
-
live.add(res.handle.id);
|
|
4581
|
-
admitted += 1;
|
|
4582
|
-
if (live.size > peakLive) peakLive = live.size;
|
|
4583
|
-
}
|
|
4584
|
-
};
|
|
4585
|
-
await fill();
|
|
4586
|
-
while (live.size > 0) {
|
|
4587
|
-
const s = await scope.next();
|
|
4588
|
-
if (s === null) break;
|
|
4589
|
-
live.delete(s.handle.id);
|
|
4590
|
-
settled.push(s);
|
|
4591
|
-
await opts.onSettled?.(s);
|
|
4592
|
-
await fill();
|
|
4593
|
-
}
|
|
4594
|
-
return {
|
|
4595
|
-
settled,
|
|
4596
|
-
admitted,
|
|
4597
|
-
rejected,
|
|
4598
|
-
stopReason,
|
|
4599
|
-
peakLive
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
|
-
/**
|
|
4603
|
-
* Free worker slots under a simultaneity cap: `cap - live`, floored at 0, or `null` when there is
|
|
4604
|
-
* no cap (the conserved pool is then the only fence and "free slots" is not a finite number).
|
|
4605
|
-
* The one place the answer is computed, so the driver-facing tool payload and a dispatcher agree.
|
|
4606
|
-
*/
|
|
4607
|
-
function freeSlots(liveCount, cap) {
|
|
4608
|
-
if (cap === void 0 || cap <= 0) return null;
|
|
4609
|
-
return Math.max(0, cap - liveCount);
|
|
4610
|
-
}
|
|
4611
|
-
/**
|
|
4612
|
-
* The ONE honest effective limit on simultaneous workers: the minimum of the caps that actually
|
|
4613
|
-
* bound the worker layer. Ignores unset/non-positive caps; returns `undefined` when no cap applies
|
|
4614
|
-
* (uncapped — the conserved pool remains the only fence).
|
|
4615
|
-
*
|
|
4616
|
-
* Deliberately does NOT fold in `SandboxLineage`'s fork concurrency: that bounds boxes inside ONE
|
|
4617
|
-
* leaf's fork wave, a different unit. Folding it in would report a 4-worker ceiling for what is
|
|
4618
|
-
* really a 4-box fanout inside a single worker.
|
|
4619
|
-
*
|
|
4620
|
-
* Use it once, at the top of a run, and pass the result to BOTH `maxLiveWorkers` and a
|
|
4621
|
-
* dispatcher's `width` — that is what turns three unrelated numbers into one.
|
|
4622
|
-
*/
|
|
4623
|
-
function effectiveConcurrency(caps) {
|
|
4624
|
-
const applicable = [caps.maxLiveWorkers, caps.maxSandboxes].filter((c) => typeof c === "number" && c > 0);
|
|
4625
|
-
if (applicable.length === 0) return void 0;
|
|
4626
|
-
return Math.min(...applicable);
|
|
4627
|
-
}
|
|
4628
|
-
/** Convenience: a `DispatchUnit` factory over a fixed array of tasks, for the common case where
|
|
4629
|
-
* the queue is known up front and only the refill behavior is wanted. */
|
|
4630
|
-
function queueOf(units, budget) {
|
|
4631
|
-
let i = 0;
|
|
4632
|
-
return () => {
|
|
4633
|
-
const u = units[i];
|
|
4634
|
-
if (u === void 0) return void 0;
|
|
4635
|
-
i += 1;
|
|
4636
|
-
return {
|
|
4637
|
-
agent: u.agent,
|
|
4638
|
-
task: u.task,
|
|
4639
|
-
opts: {
|
|
4640
|
-
budget,
|
|
4641
|
-
label: u.label
|
|
4642
|
-
}
|
|
4643
|
-
};
|
|
4644
|
-
};
|
|
4645
|
-
}
|
|
4646
|
-
//#endregion
|
|
4647
5583
|
//#region src/runtime/supervise/event-bus.ts
|
|
4648
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. */
|
|
4649
5585
|
function createEventBus(now = Date.now) {
|
|
@@ -4651,7 +5587,9 @@ function createEventBus(now = Date.now) {
|
|
|
4651
5587
|
const log = [];
|
|
4652
5588
|
const subscribers = [];
|
|
4653
5589
|
const byKind = {};
|
|
5590
|
+
const staged = /* @__PURE__ */ new WeakMap();
|
|
4654
5591
|
let seq = 0;
|
|
5592
|
+
let published = 0;
|
|
4655
5593
|
let pulled = 0;
|
|
4656
5594
|
const matches = (r, kinds) => !kinds || kinds.includes(r.event.type);
|
|
4657
5595
|
const bestIndex = (kinds) => {
|
|
@@ -4669,16 +5607,19 @@ function createEventBus(now = Date.now) {
|
|
|
4669
5607
|
};
|
|
4670
5608
|
return {
|
|
4671
5609
|
async publish(event, opts) {
|
|
4672
|
-
const record = {
|
|
5610
|
+
const record = staged.get(event) ?? {
|
|
4673
5611
|
seq: seq++,
|
|
4674
5612
|
at: now(),
|
|
4675
5613
|
priority: opts?.priority ?? 0,
|
|
4676
5614
|
event
|
|
4677
5615
|
};
|
|
5616
|
+
staged.set(event, record);
|
|
5617
|
+
for (const handler of subscribers) await handler(record);
|
|
5618
|
+
staged.delete(event);
|
|
4678
5619
|
if (opts?.queue !== false) queue.push(record);
|
|
4679
5620
|
log.push(record);
|
|
5621
|
+
published += 1;
|
|
4680
5622
|
byKind[event.type] = (byKind[event.type] ?? 0) + 1;
|
|
4681
|
-
for (const handler of subscribers) await handler(record);
|
|
4682
5623
|
return record;
|
|
4683
5624
|
},
|
|
4684
5625
|
pull(kinds) {
|
|
@@ -4702,7 +5643,7 @@ function createEventBus(now = Date.now) {
|
|
|
4702
5643
|
},
|
|
4703
5644
|
stats() {
|
|
4704
5645
|
return {
|
|
4705
|
-
published
|
|
5646
|
+
published,
|
|
4706
5647
|
pulled,
|
|
4707
5648
|
byKind: { ...byKind }
|
|
4708
5649
|
};
|
|
@@ -4910,14 +5851,6 @@ function spawnProfileArg() {
|
|
|
4910
5851
|
}).properties));
|
|
4911
5852
|
return spawnProfileArgCache;
|
|
4912
5853
|
}
|
|
4913
|
-
/** Freeze a JSON-Schema tree. The derived schema is plain JSON data with no cycles. */
|
|
4914
|
-
function deepFreeze(value) {
|
|
4915
|
-
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
4916
|
-
Object.freeze(value);
|
|
4917
|
-
for (const nested of Object.values(value)) deepFreeze(nested);
|
|
4918
|
-
}
|
|
4919
|
-
return value;
|
|
4920
|
-
}
|
|
4921
5854
|
/** Build the driver's MCP tools over a live scope. */
|
|
4922
5855
|
function createCoordinationTools(opts) {
|
|
4923
5856
|
const deliverable = opts.deliverable;
|
|
@@ -4930,23 +5863,76 @@ function createCoordinationTools(opts) {
|
|
|
4930
5863
|
const questionPolicy = opts.questionPolicy ?? "auto";
|
|
4931
5864
|
const completedKeys = /* @__PURE__ */ new Set();
|
|
4932
5865
|
const keyByWorker = /* @__PURE__ */ new Map();
|
|
5866
|
+
let unkeyedAssignmentOrdinal = nextUnkeyedAssignmentOrdinal(opts.scope);
|
|
4933
5867
|
for (const [key, prior] of opts.scope.resume?.keys ?? []) if (prior.state === "completed") completedKeys.add(key);
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
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
|
+
}
|
|
4945
5910
|
const bus = createEventBus();
|
|
4946
5911
|
if (opts.onEvent) {
|
|
4947
5912
|
const cb = opts.onEvent;
|
|
4948
|
-
bus.subscribe((rec) => cb(rec.event));
|
|
4949
|
-
}
|
|
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
|
+
};
|
|
4950
5936
|
const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
|
|
4951
5937
|
const str = (v, field) => {
|
|
4952
5938
|
if (typeof v !== "string" || v.length === 0) throw new Error(`coordination tools: "${field}" must be a non-empty string`);
|
|
@@ -4969,12 +5955,14 @@ function createCoordinationTools(opts) {
|
|
|
4969
5955
|
const maxTokens = field("maxTokens");
|
|
4970
5956
|
const maxUsd = field("maxUsd");
|
|
4971
5957
|
const deadlineMs = field("deadlineMs");
|
|
4972
|
-
|
|
5958
|
+
const merged = {
|
|
4973
5959
|
maxIterations: maxIterations ?? base.maxIterations,
|
|
4974
5960
|
maxTokens: maxTokens ?? base.maxTokens,
|
|
4975
5961
|
...(maxUsd ?? base.maxUsd) === void 0 ? {} : { maxUsd: maxUsd ?? base.maxUsd },
|
|
4976
5962
|
...(deadlineMs ?? base.deadlineMs) === void 0 ? {} : { deadlineMs: deadlineMs ?? base.deadlineMs }
|
|
4977
5963
|
};
|
|
5964
|
+
assertValidBudget(merged, "coordination tools: budget");
|
|
5965
|
+
return merged;
|
|
4978
5966
|
};
|
|
4979
5967
|
const level = (v) => {
|
|
4980
5968
|
if (v === "worker" || v === "driver" || v === "loop") return v;
|
|
@@ -4984,43 +5972,27 @@ function createCoordinationTools(opts) {
|
|
|
4984
5972
|
if (v === "continue-without" || v === "blocks-step" || v === "blocks-run") return v;
|
|
4985
5973
|
throw new Error("coordination tools: \"urgency\" must be continue-without, blocks-step, or blocks-run");
|
|
4986
5974
|
};
|
|
4987
|
-
const
|
|
4988
|
-
const settledAt = Date.now();
|
|
5975
|
+
const commitSettled = (s, w) => {
|
|
4989
5976
|
const settledKey = keyByWorker.get(s.handle.id);
|
|
4990
5977
|
if (settledKey !== void 0 && s.kind === "done") completedKeys.add(settledKey);
|
|
4991
|
-
const w = s.kind === "done" ? {
|
|
4992
|
-
id: s.handle.id,
|
|
4993
|
-
status: "done",
|
|
4994
|
-
score: s.verdict?.score ?? 0,
|
|
4995
|
-
valid: s.verdict?.valid ?? false,
|
|
4996
|
-
outRef: s.outRef,
|
|
4997
|
-
settledAt
|
|
4998
|
-
} : {
|
|
4999
|
-
id: s.handle.id,
|
|
5000
|
-
status: "down",
|
|
5001
|
-
reason: s.reason,
|
|
5002
|
-
settledAt
|
|
5003
|
-
};
|
|
5004
5978
|
ledger.push(w);
|
|
5005
5979
|
unwatchWorker(w.id);
|
|
5006
|
-
return w;
|
|
5007
5980
|
};
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
await bus.publish(
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
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);
|
|
5018
5990
|
for (const analyst of opts.analyzeOnSettle) {
|
|
5019
5991
|
const findings = await opts.analysts.run(analyst, trace);
|
|
5020
5992
|
await bus.publish({
|
|
5021
5993
|
type: "finding",
|
|
5022
5994
|
finding: {
|
|
5023
|
-
fromWorker:
|
|
5995
|
+
fromWorker: pending.worker.id,
|
|
5024
5996
|
analyst,
|
|
5025
5997
|
findings
|
|
5026
5998
|
}
|
|
@@ -5029,16 +6001,41 @@ function createCoordinationTools(opts) {
|
|
|
5029
6001
|
}
|
|
5030
6002
|
return true;
|
|
5031
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
|
+
};
|
|
5032
6021
|
const drainResolved = async () => {
|
|
5033
6022
|
let drained = 0;
|
|
5034
6023
|
for (;;) {
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
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();
|
|
5042
6039
|
drained += 1;
|
|
5043
6040
|
}
|
|
5044
6041
|
};
|
|
@@ -5052,21 +6049,100 @@ function createCoordinationTools(opts) {
|
|
|
5052
6049
|
down
|
|
5053
6050
|
}, { queue: false });
|
|
5054
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
|
+
};
|
|
5055
6138
|
const projectEvent = (ev) => {
|
|
5056
6139
|
if (ev.type === "settled") {
|
|
5057
|
-
const
|
|
5058
|
-
return
|
|
5059
|
-
type: "settled",
|
|
5060
|
-
settled: w.id,
|
|
5061
|
-
status: "done",
|
|
5062
|
-
score: w.score,
|
|
5063
|
-
valid: w.valid,
|
|
5064
|
-
outRef: w.outRef
|
|
5065
|
-
} : {
|
|
6140
|
+
const { id, status, ...evidence } = ev.worker;
|
|
6141
|
+
return {
|
|
5066
6142
|
type: "settled",
|
|
5067
|
-
settled:
|
|
5068
|
-
status
|
|
5069
|
-
|
|
6143
|
+
settled: id,
|
|
6144
|
+
status,
|
|
6145
|
+
...evidence
|
|
5070
6146
|
};
|
|
5071
6147
|
}
|
|
5072
6148
|
if (ev.type === "question") return {
|
|
@@ -5082,12 +6158,25 @@ function createCoordinationTools(opts) {
|
|
|
5082
6158
|
...ev.down,
|
|
5083
6159
|
questionId: ev.questionId
|
|
5084
6160
|
};
|
|
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
|
+
};
|
|
5085
6169
|
return {
|
|
5086
6170
|
type: ev.type,
|
|
5087
6171
|
...ev.down
|
|
5088
6172
|
};
|
|
5089
6173
|
};
|
|
5090
|
-
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
|
+
};
|
|
5091
6180
|
const normalizeQuestion = (q, fallbackFrom) => {
|
|
5092
6181
|
const from = str(q.from ?? fallbackFrom, "from");
|
|
5093
6182
|
return {
|
|
@@ -5155,13 +6244,30 @@ function createCoordinationTools(opts) {
|
|
|
5155
6244
|
};
|
|
5156
6245
|
const maxLiveWorkers = opts.maxLiveWorkers;
|
|
5157
6246
|
const isLive = (status) => status !== "done" && status !== "failed" && status !== "cancelled";
|
|
5158
|
-
const
|
|
5159
|
-
const
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
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);
|
|
5165
6271
|
const readProgress = (id) => {
|
|
5166
6272
|
const scope = opts.scope;
|
|
5167
6273
|
if (typeof scope.progress !== "function") return void 0;
|
|
@@ -5262,10 +6368,22 @@ function createCoordinationTools(opts) {
|
|
|
5262
6368
|
type: "object",
|
|
5263
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.",
|
|
5264
6370
|
properties: {
|
|
5265
|
-
maxIterations: {
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
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
|
+
}
|
|
5269
6387
|
}
|
|
5270
6388
|
}
|
|
5271
6389
|
},
|
|
@@ -5274,28 +6392,47 @@ function createCoordinationTools(opts) {
|
|
|
5274
6392
|
handler: (raw) => {
|
|
5275
6393
|
const a = obj(raw);
|
|
5276
6394
|
const key = a.key === void 0 ? void 0 : str(a.key, "key");
|
|
5277
|
-
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({
|
|
5278
6396
|
error: "max-live-workers",
|
|
5279
6397
|
live: liveWorkerCount(),
|
|
5280
6398
|
freeSlots: freeWorkerSlots()
|
|
5281
6399
|
});
|
|
5282
|
-
const
|
|
5283
|
-
|
|
5284
|
-
|
|
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, {
|
|
5285
6422
|
budget,
|
|
5286
|
-
label
|
|
6423
|
+
label,
|
|
6424
|
+
assignmentId,
|
|
5287
6425
|
...key !== void 0 ? { key } : {}
|
|
5288
6426
|
});
|
|
5289
6427
|
if (res.ok && res.prior?.state === "completed") {
|
|
5290
6428
|
const s = res.prior.settled;
|
|
5291
6429
|
if (key !== void 0) completedKeys.add(key);
|
|
6430
|
+
const { id, status, resumed: _resumed, ...evidence } = projectSettled(s);
|
|
5292
6431
|
return Promise.resolve({
|
|
5293
|
-
workerId:
|
|
6432
|
+
workerId: id,
|
|
5294
6433
|
resumed: "completed",
|
|
5295
|
-
status
|
|
5296
|
-
|
|
5297
|
-
valid: s.verdict?.valid ?? false,
|
|
5298
|
-
outRef: s.outRef,
|
|
6434
|
+
status,
|
|
6435
|
+
...evidence,
|
|
5299
6436
|
live: liveWorkerCount(),
|
|
5300
6437
|
freeSlots: freeWorkerSlots()
|
|
5301
6438
|
});
|
|
@@ -5311,6 +6448,10 @@ function createCoordinationTools(opts) {
|
|
|
5311
6448
|
} : {};
|
|
5312
6449
|
return Promise.resolve(res.ok ? {
|
|
5313
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 },
|
|
5314
6455
|
live: liveWorkerCount(),
|
|
5315
6456
|
freeSlots: freeWorkerSlots(),
|
|
5316
6457
|
...priorHistory
|
|
@@ -5338,19 +6479,16 @@ function createCoordinationTools(opts) {
|
|
|
5338
6479
|
if (!resumed) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
5339
6480
|
const output = resumed.outRef ? await opts.blobs.get(resumed.outRef) : void 0;
|
|
5340
6481
|
return {
|
|
5341
|
-
|
|
5342
|
-
spent: resumed.spent,
|
|
6482
|
+
...projectNodeEvidence(resumed, true),
|
|
5343
6483
|
outRef: resumed.outRef ?? null,
|
|
5344
6484
|
output: output ?? null,
|
|
5345
|
-
progress: null
|
|
5346
|
-
resumed: true
|
|
6485
|
+
progress: null
|
|
5347
6486
|
};
|
|
5348
6487
|
}
|
|
5349
6488
|
const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
|
|
5350
6489
|
const progress = readProgress(id);
|
|
5351
6490
|
return {
|
|
5352
|
-
|
|
5353
|
-
spent: node.spent,
|
|
6491
|
+
...projectNodeEvidence(node),
|
|
5354
6492
|
outRef: node.outRef ?? null,
|
|
5355
6493
|
output: output ?? null,
|
|
5356
6494
|
progress: progress ?? null
|
|
@@ -5380,24 +6518,20 @@ function createCoordinationTools(opts) {
|
|
|
5380
6518
|
const workerId = str(a.workerId, "workerId");
|
|
5381
6519
|
const instruction = str(a.instruction, "instruction");
|
|
5382
6520
|
const interrupt = a.interrupt === true;
|
|
5383
|
-
const
|
|
5384
|
-
|
|
6521
|
+
const authorized = authorizeInstruction("steer", workerId, instruction, interrupt);
|
|
6522
|
+
await recordInstruction(authorized);
|
|
6523
|
+
const delivery = await attemptDelivery(authorized, {
|
|
6524
|
+
steer: authorized.instruction,
|
|
5385
6525
|
interrupt
|
|
5386
6526
|
});
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
instruction,
|
|
5390
|
-
delivered
|
|
5391
|
-
});
|
|
5392
|
-
if (delivered) return {
|
|
5393
|
-
delivered,
|
|
6527
|
+
if (delivery.delivered) return {
|
|
6528
|
+
delivered: true,
|
|
5394
6529
|
progress: readProgress(workerId) ?? null
|
|
5395
6530
|
};
|
|
5396
|
-
const progress = readProgress(workerId);
|
|
5397
6531
|
return {
|
|
5398
|
-
delivered,
|
|
5399
|
-
reason:
|
|
5400
|
-
progress:
|
|
6532
|
+
delivered: false,
|
|
6533
|
+
reason: delivery.outcome,
|
|
6534
|
+
progress: readProgress(workerId) ?? null
|
|
5401
6535
|
};
|
|
5402
6536
|
}
|
|
5403
6537
|
},
|
|
@@ -5479,25 +6613,24 @@ function createCoordinationTools(opts) {
|
|
|
5479
6613
|
const questionId = str(a.questionId, "questionId");
|
|
5480
6614
|
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
5481
6615
|
const answer = a.answer;
|
|
5482
|
-
const
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
const
|
|
5488
|
-
|
|
5489
|
-
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,
|
|
5490
6623
|
questionId,
|
|
5491
6624
|
interrupt
|
|
5492
6625
|
});
|
|
5493
|
-
await sendDown("answer", {
|
|
5494
|
-
toWorker: question.from,
|
|
5495
|
-
instruction: answer,
|
|
5496
|
-
delivered
|
|
5497
|
-
}, questionId);
|
|
5498
6626
|
return {
|
|
5499
|
-
question,
|
|
5500
|
-
|
|
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 }
|
|
5501
6634
|
};
|
|
5502
6635
|
}
|
|
5503
6636
|
if (typeof a.deferReason === "string" && a.deferReason.length > 0) return Promise.resolve({ question: decideQuestion(questionId, {
|
|
@@ -5663,16 +6796,29 @@ function createCoordinationTools(opts) {
|
|
|
5663
6796
|
handler: async (raw) => {
|
|
5664
6797
|
const a = obj(raw);
|
|
5665
6798
|
const id = str(a.workerId, "workerId");
|
|
5666
|
-
const node =
|
|
6799
|
+
const node = nodeForWorker(id);
|
|
5667
6800
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
5668
|
-
if (
|
|
5669
|
-
const trace =
|
|
5670
|
-
|
|
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) };
|
|
5671
6816
|
}
|
|
5672
6817
|
});
|
|
5673
6818
|
}
|
|
5674
6819
|
return {
|
|
5675
6820
|
tools,
|
|
6821
|
+
ready,
|
|
5676
6822
|
history: () => bus.history(),
|
|
5677
6823
|
raiseFinding: (finding) => bus.publish({
|
|
5678
6824
|
type: "finding",
|
|
@@ -5687,6 +6833,31 @@ function createCoordinationTools(opts) {
|
|
|
5687
6833
|
drainResolved
|
|
5688
6834
|
};
|
|
5689
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
|
+
}
|
|
5690
6861
|
//#endregion
|
|
5691
6862
|
//#region src/runtime/anytime.ts
|
|
5692
6863
|
/**
|
|
@@ -6106,10 +7277,11 @@ const runawayTripwireTurns = 2e3;
|
|
|
6106
7277
|
* overspend usd up to the turn tripwire). */
|
|
6107
7278
|
function poolStarved(scope, perWorker) {
|
|
6108
7279
|
const b = scope.budget;
|
|
6109
|
-
if (
|
|
7280
|
+
if (scope.view.inFlight > 0 || scope.view.waiting > 0) return false;
|
|
6110
7281
|
const tokenStarved = b.tokensLeft < perWorker.maxTokens;
|
|
6111
|
-
const
|
|
6112
|
-
|
|
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;
|
|
6113
7285
|
}
|
|
6114
7286
|
/** The absolute wall-clock deadline (when the root set one) has passed. */
|
|
6115
7287
|
function deadlinePassed(scope, now) {
|
|
@@ -6142,17 +7314,29 @@ function driverAgent(opts) {
|
|
|
6142
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)");
|
|
6143
7315
|
if ((opts.analyzeOnSettle?.length ?? 0) > 0 && !opts.analysts) throw new ValidationError("driverAgent: analyzeOnSettle requires analysts (the lens registry the kinds resolve against)");
|
|
6144
7316
|
const reserved = new Set(coordinationVerbNames);
|
|
6145
|
-
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
|
+
}
|
|
6146
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)");
|
|
6147
7326
|
const maxTurns = opts.maxTurns === 0 ? runawayTripwireTurns : opts.maxTurns ?? 16;
|
|
6148
7327
|
const now = opts.now ?? Date.now;
|
|
7328
|
+
const inbox = opts.inbox ?? createInbox();
|
|
6149
7329
|
return {
|
|
6150
7330
|
name: opts.name,
|
|
7331
|
+
deliver(message) {
|
|
7332
|
+
return inbox.deliver(message);
|
|
7333
|
+
},
|
|
6151
7334
|
async act(task, scope) {
|
|
6152
7335
|
const coord = createCoordinationTools({
|
|
6153
7336
|
scope,
|
|
6154
7337
|
blobs: opts.blobs,
|
|
6155
7338
|
makeWorkerAgent: opts.makeWorkerAgent,
|
|
7339
|
+
...opts.authorizeDownMessage ? { authorizeDownMessage: opts.authorizeDownMessage } : {},
|
|
6156
7340
|
perWorker: opts.perWorker,
|
|
6157
7341
|
...opts.deliverable ? { deliverable: opts.deliverable } : {},
|
|
6158
7342
|
...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
|
|
@@ -6161,28 +7345,41 @@ function driverAgent(opts) {
|
|
|
6161
7345
|
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
6162
7346
|
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
6163
7347
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
7348
|
+
...opts.replaySettlements ? { replaySettlements: true } : {},
|
|
6164
7349
|
...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {}
|
|
6165
7350
|
});
|
|
7351
|
+
await coord.ready();
|
|
6166
7352
|
for (const w of scope.resume?.waits ?? []) {
|
|
6167
7353
|
const rearmed = scope.wait(w.spec, { label: w.label });
|
|
6168
7354
|
if (!rearmed.ok) throw new RuntimeRunStateError(`driverAgent: cannot re-arm resumed wait '${w.label}' (${rearmed.reason})`);
|
|
6169
7355
|
}
|
|
6170
|
-
const byName = new Map(coord.tools.map((t) => [t.name, t]));
|
|
6171
|
-
const toolSpecs = [
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
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
|
+
];
|
|
6186
7383
|
const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
|
|
6187
7384
|
const tracker = opts.stopRule ? createProgressTracker({ now }) : void 0;
|
|
6188
7385
|
let progressStopReason;
|
|
@@ -6262,20 +7459,32 @@ function driverAgent(opts) {
|
|
|
6262
7459
|
...scope.resume ? [{
|
|
6263
7460
|
role: "user",
|
|
6264
7461
|
content: resumeBrief(scope.resume, opts.priorCoordination)
|
|
7462
|
+
}] : hasPriorCoordination(opts.priorCoordination) ? [{
|
|
7463
|
+
role: "user",
|
|
7464
|
+
content: priorCoordinationBrief(opts.priorCoordination)
|
|
6265
7465
|
}] : []
|
|
6266
7466
|
],
|
|
6267
7467
|
maxTurns,
|
|
6268
|
-
hooks: {
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
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;
|
|
6276
7486
|
}
|
|
6277
|
-
|
|
6278
|
-
} }
|
|
7487
|
+
}
|
|
6279
7488
|
});
|
|
6280
7489
|
await coord.drainResolved();
|
|
6281
7490
|
const submitted = coord.submittedResult();
|
|
@@ -6292,8 +7501,9 @@ function driverAgent(opts) {
|
|
|
6292
7501
|
/**
|
|
6293
7502
|
* The factual context a resumed driver starts from — everything the durable stores prove about
|
|
6294
7503
|
* the prior process(es): committed settlements, per-key states (completed / lost / failed),
|
|
6295
|
-
* re-armed waits, carried-over questions
|
|
6296
|
-
* 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.
|
|
6297
7507
|
*/
|
|
6298
7508
|
function resumeBrief(resume, prior) {
|
|
6299
7509
|
const lines = [
|
|
@@ -6313,13 +7523,36 @@ function resumeBrief(resume, prior) {
|
|
|
6313
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})`));
|
|
6314
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})`));
|
|
6315
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})`));
|
|
6316
|
-
|
|
6317
|
-
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}`));
|
|
6318
|
-
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);
|
|
6319
7527
|
const spent = resume.priorSpend;
|
|
6320
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}`);
|
|
6321
7529
|
return lines.join("\n");
|
|
6322
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
|
+
}
|
|
6323
7556
|
/** Run a work tool. A throw is data to the driver (it can recover next turn), not a crash — fold
|
|
6324
7557
|
* the error back as a string result. null/undefined passes through (the caller treats it as "not
|
|
6325
7558
|
* handled" and falls to the coordination dispatch). */
|
|
@@ -7217,203 +8450,6 @@ function canonicalize(value) {
|
|
|
7217
8450
|
return out;
|
|
7218
8451
|
}
|
|
7219
8452
|
//#endregion
|
|
7220
|
-
//#region src/runtime/supervise/authoring.ts
|
|
7221
|
-
/**
|
|
7222
|
-
*
|
|
7223
|
-
* The supervisor's intelligence is AUTHORING the agents it spawns — not pressing buttons.
|
|
7224
|
-
*
|
|
7225
|
-
* Every agent here is three things: instructions (system prompt), tools, and a model — its
|
|
7226
|
-
* `AgentProfile`. The supervisor's job is to WRITE those profiles: read the task, decompose it,
|
|
7227
|
-
* and for each sub-task author a tailored worker recipe. `supervisorInstructions` is the how-to the
|
|
7228
|
-
* supervisor reads (its system prompt); `authoredWorker` builds a worker AGENT from a profile the
|
|
7229
|
-
* supervisor authored — the authored systemPrompt + model shape the worker's call.
|
|
7230
|
-
*
|
|
7231
|
-
* The skill is the single OPTIMIZABLE surface: edit it → the supervisor designs better agents.
|
|
7232
|
-
* That is the self-improvement lever (the prompt/skill lever), not the execution plumbing.
|
|
7233
|
-
*
|
|
7234
|
-
* @experimental
|
|
7235
|
-
*/
|
|
7236
|
-
/** Narrow an untyped `spawn_agent` profile argument to an `AuthoredProfile`, or null if the
|
|
7237
|
-
* supervisor failed to author one (empty/placeholder profile — a skill violation worth catching). */
|
|
7238
|
-
function asAuthoredProfile(raw) {
|
|
7239
|
-
const p = raw;
|
|
7240
|
-
if (!p || typeof p.systemPrompt !== "string" || p.systemPrompt.trim().length === 0) return null;
|
|
7241
|
-
return {
|
|
7242
|
-
name: typeof p.name === "string" && p.name.length > 0 ? p.name : "worker",
|
|
7243
|
-
systemPrompt: p.systemPrompt,
|
|
7244
|
-
...typeof p.model === "string" ? { model: p.model } : {}
|
|
7245
|
-
};
|
|
7246
|
-
}
|
|
7247
|
-
/** The supervisor SKILL — the how-to the supervisor reads (its system prompt). THE optimizable
|
|
7248
|
-
* surface: editing this changes how the supervisor designs every agent it spawns. */
|
|
7249
|
-
function supervisorInstructions(opts) {
|
|
7250
|
-
return [
|
|
7251
|
-
"You are a SUPERVISOR. You do NOT do the work yourself — your job is to DESIGN and DRIVE specialist worker agents.",
|
|
7252
|
-
"",
|
|
7253
|
-
"For the task you are given:",
|
|
7254
|
-
"1. DECOMPOSE it into the smallest set of sub-tasks a single focused worker can each deliver.",
|
|
7255
|
-
"2. For EACH sub-task, AUTHOR a worker by calling spawn_agent with a COMPLETE `profile`:",
|
|
7256
|
-
" • name: a short id for the worker.",
|
|
7257
|
-
" • 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.",
|
|
7258
|
-
" • model: the model best suited to this sub-task (omit to use the default).",
|
|
7259
|
-
" NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
|
|
7260
|
-
"3. await_event (kinds:['settled']) to collect each worker. Its result says valid:true only if the deployable check passed.",
|
|
7261
|
-
"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.",
|
|
7262
|
-
"5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself — only a delivered (valid:true) worker counts.",
|
|
7263
|
-
...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
|
|
7264
|
-
].join("\n");
|
|
7265
|
-
}
|
|
7266
|
-
/** Build a worker AGENT from a profile the supervisor authored: the authored `systemPrompt` +
|
|
7267
|
-
* `model` shape the worker's one model call; the deliverable gates settlement (valid ⟺ delivered). */
|
|
7268
|
-
function authoredWorker(profile, opts) {
|
|
7269
|
-
let artifact;
|
|
7270
|
-
const model = profile.model ?? opts.cfg.model;
|
|
7271
|
-
const gated = gateOnDeliverable({
|
|
7272
|
-
runtime: "router",
|
|
7273
|
-
async execute(_t, signal) {
|
|
7274
|
-
const res = await routerChatWithUsage({
|
|
7275
|
-
...opts.cfg,
|
|
7276
|
-
model
|
|
7277
|
-
}, [{
|
|
7278
|
-
role: "system",
|
|
7279
|
-
content: profile.systemPrompt
|
|
7280
|
-
}, {
|
|
7281
|
-
role: "user",
|
|
7282
|
-
content: opts.taskPrompt
|
|
7283
|
-
}], {
|
|
7284
|
-
temperature: opts.temperature ?? .4,
|
|
7285
|
-
...signal ? { signal } : {}
|
|
7286
|
-
});
|
|
7287
|
-
artifact = {
|
|
7288
|
-
outRef: contentAddress(res.content),
|
|
7289
|
-
out: res.content,
|
|
7290
|
-
spent: {
|
|
7291
|
-
iterations: 1,
|
|
7292
|
-
tokens: res.usage ?? {
|
|
7293
|
-
input: 0,
|
|
7294
|
-
output: 0
|
|
7295
|
-
},
|
|
7296
|
-
usd: res.costUsd ?? 0,
|
|
7297
|
-
ms: 0
|
|
7298
|
-
}
|
|
7299
|
-
};
|
|
7300
|
-
return artifact;
|
|
7301
|
-
},
|
|
7302
|
-
teardown: () => Promise.resolve({ destroyed: true }),
|
|
7303
|
-
resultArtifact: () => {
|
|
7304
|
-
if (!artifact) throw new Error("authoredWorker: resultArtifact read before execute");
|
|
7305
|
-
return artifact;
|
|
7306
|
-
}
|
|
7307
|
-
}, opts.deliverable);
|
|
7308
|
-
const spec = {
|
|
7309
|
-
profile: { name: profile.name },
|
|
7310
|
-
harness: null,
|
|
7311
|
-
executor: gated
|
|
7312
|
-
};
|
|
7313
|
-
return {
|
|
7314
|
-
name: profile.name,
|
|
7315
|
-
act: async () => "",
|
|
7316
|
-
executorSpec: spec
|
|
7317
|
-
};
|
|
7318
|
-
}
|
|
7319
|
-
/** Default thresholds for `ProfileRichnessThresholds` — 600 chars / 6 lines minimum system prompt. */
|
|
7320
|
-
const defaultProfileRichnessThresholds = {
|
|
7321
|
-
minSystemPromptChars: 600,
|
|
7322
|
-
minSystemPromptLines: 6
|
|
7323
|
-
};
|
|
7324
|
-
/** Read the system prompt from any authored shape: canonical `prompt.systemPrompt`, the sandbox
|
|
7325
|
-
* `prompt.system` convention, or a bare-string `prompt`. */
|
|
7326
|
-
function resolveSystemPrompt(profile) {
|
|
7327
|
-
const pr = profile.prompt;
|
|
7328
|
-
if (typeof pr === "string") return pr;
|
|
7329
|
-
if (pr && typeof pr === "object") {
|
|
7330
|
-
const o = pr;
|
|
7331
|
-
if (typeof o.systemPrompt === "string") return o.systemPrompt;
|
|
7332
|
-
if (typeof o.system === "string") return o.system;
|
|
7333
|
-
}
|
|
7334
|
-
return "";
|
|
7335
|
-
}
|
|
7336
|
-
/** OBSERVE one authored `AgentProfile` and score its richness (no judge verdict is read). The task
|
|
7337
|
-
* context (`needsMcp`) lets a domain say "this work needs a data/tool MCP" so a missing MCP counts. */
|
|
7338
|
-
function assessAuthoredProfile(profile, opts) {
|
|
7339
|
-
const th = {
|
|
7340
|
-
...defaultProfileRichnessThresholds,
|
|
7341
|
-
...opts?.thresholds ?? {}
|
|
7342
|
-
};
|
|
7343
|
-
const systemPrompt = resolveSystemPrompt(profile);
|
|
7344
|
-
const trimmed = systemPrompt.trim();
|
|
7345
|
-
const systemPromptChars = trimmed.length;
|
|
7346
|
-
const systemPromptLines = trimmed ? trimmed.split("\n").filter((l) => l.trim().length > 0).length : 0;
|
|
7347
|
-
const sentenceCount = trimmed ? (trimmed.match(/[.!?](\s|$)/g) ?? []).length || (trimmed ? 1 : 0) : 0;
|
|
7348
|
-
const hasDescription = typeof profile.description === "string" && profile.description.trim().length > 0;
|
|
7349
|
-
const tools = profile.tools;
|
|
7350
|
-
const hasTools = !!tools && Object.keys(tools).length > 0;
|
|
7351
|
-
const skills = profile.resources?.skills;
|
|
7352
|
-
const hasSkills = Array.isArray(skills) && skills.length > 0;
|
|
7353
|
-
const mcp = profile.mcp;
|
|
7354
|
-
const hasMcp = !!mcp && Object.keys(mcp).length > 0;
|
|
7355
|
-
const subagents = profile.subagents;
|
|
7356
|
-
const hasSubagents = !!subagents && Object.keys(subagents).length > 0;
|
|
7357
|
-
const reasons = [];
|
|
7358
|
-
const promptThin = systemPromptChars < th.minSystemPromptChars || systemPromptLines < th.minSystemPromptLines;
|
|
7359
|
-
if (promptThin) reasons.push(`system prompt is thin (${systemPromptChars} chars, ${systemPromptLines} lines; need ≥${th.minSystemPromptChars} chars and ≥${th.minSystemPromptLines} lines)`);
|
|
7360
|
-
if (!hasTools) reasons.push("no tools granted (a worker can only act through the tools you grant it)");
|
|
7361
|
-
if (!hasSkills) reasons.push("no skills attached (no reusable how-to notes injected)");
|
|
7362
|
-
if (opts?.needsMcp && !hasMcp) reasons.push("no MCP server, but the task needs data/tool access");
|
|
7363
|
-
const signals = [
|
|
7364
|
-
!promptThin,
|
|
7365
|
-
hasTools,
|
|
7366
|
-
hasSkills,
|
|
7367
|
-
hasDescription,
|
|
7368
|
-
opts?.needsMcp ? hasMcp : true
|
|
7369
|
-
];
|
|
7370
|
-
const richness = signals.filter(Boolean).length / signals.length;
|
|
7371
|
-
const thin = promptThin || !hasTools && !hasSkills && !hasMcp;
|
|
7372
|
-
return {
|
|
7373
|
-
name: profile.name ?? "worker",
|
|
7374
|
-
systemPrompt,
|
|
7375
|
-
systemPromptChars,
|
|
7376
|
-
systemPromptLines,
|
|
7377
|
-
sentenceCount,
|
|
7378
|
-
hasDescription,
|
|
7379
|
-
hasTools,
|
|
7380
|
-
hasSkills,
|
|
7381
|
-
hasMcp,
|
|
7382
|
-
hasSubagents,
|
|
7383
|
-
richness,
|
|
7384
|
-
thin,
|
|
7385
|
-
reasons
|
|
7386
|
-
};
|
|
7387
|
-
}
|
|
7388
|
-
/** Turn a {@link ProfileRichness} verdict into a bus-routable `AnalystFinding` (area `profile-quality`).
|
|
7389
|
-
* Severity scales with thinness; the recommended action names the MISSING lever so the supervisor can
|
|
7390
|
-
* re-author. `subject` = the worker name so per-worker findings diff cleanly across re-authors. */
|
|
7391
|
-
function profileRichnessFinding(richness, opts) {
|
|
7392
|
-
const analyst_id = opts?.analystId ?? "profile-richness";
|
|
7393
|
-
const subject = richness.name;
|
|
7394
|
-
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)}%).`;
|
|
7395
|
-
return makeFinding({
|
|
7396
|
-
analyst_id,
|
|
7397
|
-
severity: richness.thin ? richness.richness < .25 ? "high" : "medium" : "info",
|
|
7398
|
-
area: "profile-quality",
|
|
7399
|
-
claim,
|
|
7400
|
-
subject,
|
|
7401
|
-
confidence: .9,
|
|
7402
|
-
evidence_refs: [{
|
|
7403
|
-
kind: "metric",
|
|
7404
|
-
uri: `profile:${subject}`,
|
|
7405
|
-
excerpt: `chars=${richness.systemPromptChars} lines=${richness.systemPromptLines} tools=${richness.hasTools} skills=${richness.hasSkills} mcp=${richness.hasMcp} richness=${richness.richness.toFixed(2)}`
|
|
7406
|
-
}],
|
|
7407
|
-
...richness.thin ? { recommended_action: `Re-author "${richness.name}" with: ${richness.reasons.join("; ")}.` } : {},
|
|
7408
|
-
id_basis: computeFindingId({
|
|
7409
|
-
analyst_id,
|
|
7410
|
-
area: "profile-quality",
|
|
7411
|
-
subject,
|
|
7412
|
-
claim: `richness:${richness.thin ? "thin" : "rich"}`
|
|
7413
|
-
})
|
|
7414
|
-
});
|
|
7415
|
-
}
|
|
7416
|
-
//#endregion
|
|
7417
8453
|
//#region src/runtime/supervise/delegate.ts
|
|
7418
8454
|
/**
|
|
7419
8455
|
*
|
|
@@ -7442,15 +8478,15 @@ const defaultDelegateBudget = {
|
|
|
7442
8478
|
maxIterations: 50,
|
|
7443
8479
|
maxTokens: 2e5
|
|
7444
8480
|
};
|
|
7445
|
-
/** 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`)
|
|
7446
8482
|
* whose standing instruction IS the authoring-agent-profiles skill, so it decomposes the intent and
|
|
7447
8483
|
* AUTHORS a worker profile per sub-task. No worker profile is baked in here. */
|
|
7448
8484
|
function authoringSupervisorProfile(model, override) {
|
|
7449
8485
|
return {
|
|
7450
8486
|
name: override?.name ?? "delegate-supervisor",
|
|
7451
|
-
harness:
|
|
7452
|
-
...model ? { model } : {},
|
|
7453
|
-
systemPrompt: override?.systemPrompt ?? supervisorInstructions()
|
|
8487
|
+
harness: "cli-base",
|
|
8488
|
+
...model ? { model: { default: model } } : {},
|
|
8489
|
+
prompt: { systemPrompt: override?.systemPrompt ?? supervisorInstructions() }
|
|
7454
8490
|
};
|
|
7455
8491
|
}
|
|
7456
8492
|
/**
|
|
@@ -7535,11 +8571,19 @@ function validateDelegateArgs(raw) {
|
|
|
7535
8571
|
* conserved `spentTotal`, so the agent always learns the cost — even on a no-winner, never a faked
|
|
7536
8572
|
* output and never a fabricated zero spend. */
|
|
7537
8573
|
function toDelegateResult(result) {
|
|
7538
|
-
if (result.kind === "no-winner")
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
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
|
+
}
|
|
7543
8587
|
return {
|
|
7544
8588
|
status: "winner",
|
|
7545
8589
|
out: result.out,
|
|
@@ -8299,6 +9343,7 @@ async function serveCoordinationMcp(opts) {
|
|
|
8299
9343
|
scope: opts.scope,
|
|
8300
9344
|
blobs: opts.blobs,
|
|
8301
9345
|
makeWorkerAgent: opts.makeWorkerAgent,
|
|
9346
|
+
...opts.authorizeDownMessage ? { authorizeDownMessage: opts.authorizeDownMessage } : {},
|
|
8302
9347
|
perWorker: opts.perWorker,
|
|
8303
9348
|
...opts.deliverable ? { deliverable: opts.deliverable } : {},
|
|
8304
9349
|
...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
|
|
@@ -8308,11 +9353,13 @@ async function serveCoordinationMcp(opts) {
|
|
|
8308
9353
|
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
8309
9354
|
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
8310
9355
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
9356
|
+
...opts.replaySettlements ? { replaySettlements: true } : {},
|
|
8311
9357
|
...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {},
|
|
8312
9358
|
...opts.priorQuestions?.length ? { priorQuestions: opts.priorQuestions } : {}
|
|
8313
9359
|
});
|
|
9360
|
+
await coord.ready();
|
|
8314
9361
|
const mcp = createMcpServer({
|
|
8315
|
-
extraTools: coord.tools,
|
|
9362
|
+
extraTools: [...coord.tools, ...opts.nodeTools ?? []],
|
|
8316
9363
|
serverName: "coordination"
|
|
8317
9364
|
});
|
|
8318
9365
|
const server = createServer((req, res) => {
|
|
@@ -8484,45 +9531,81 @@ function assertCoordinationBinding(binding) {
|
|
|
8484
9531
|
}
|
|
8485
9532
|
/** Build a supervisor `Agent` from its profile: the brain resolves from `profile.harness` (backend-as-data), the same resolution rule as every worker. */
|
|
8486
9533
|
function supervisorAgent(profile, deps) {
|
|
8487
|
-
const
|
|
8488
|
-
const
|
|
8489
|
-
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);
|
|
8490
9542
|
const coordination = deps.coordination ? { ...deps.coordination } : void 0;
|
|
8491
9543
|
assertCoordinationBinding(coordination);
|
|
8492
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.");
|
|
8493
|
-
if (harness !== null && deps.compaction) throw new ValidationError("supervisorAgent: compaction is only supported for router-brained supervisors (profile.harness
|
|
8494
|
-
if (harness === null)
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
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
|
+
}
|
|
8517
9591
|
const driveHarness = deps.driveHarness;
|
|
8518
9592
|
if (!driveHarness) throw new ValidationError(`supervisorAgent: profile.harness="${harness}" needs deps.driveHarness (how to run the harness with the coordination MCP mounted)`);
|
|
8519
|
-
|
|
9593
|
+
const deliver = driveHarness.deliver?.bind(driveHarness);
|
|
9594
|
+
const externalAgent = {
|
|
8520
9595
|
name,
|
|
9596
|
+
...deliver ? { deliver(message) {
|
|
9597
|
+
return deliver(message);
|
|
9598
|
+
} } : {},
|
|
8521
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);
|
|
8522
9604
|
const mcp = await serveCoordinationMcp({
|
|
8523
9605
|
scope,
|
|
8524
9606
|
blobs: deps.blobs,
|
|
8525
9607
|
makeWorkerAgent: deps.makeWorkerAgent,
|
|
9608
|
+
...deps.authorizeDownMessage ? { authorizeDownMessage: deps.authorizeDownMessage } : {},
|
|
8526
9609
|
perWorker: deps.perWorker,
|
|
8527
9610
|
...coordination?.host !== void 0 ? { host: coordination.host } : {},
|
|
8528
9611
|
...coordination?.port !== void 0 ? { port: coordination.port } : {},
|
|
@@ -8533,17 +9616,24 @@ function supervisorAgent(profile, deps) {
|
|
|
8533
9616
|
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
|
|
8534
9617
|
...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
|
|
8535
9618
|
...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {},
|
|
8536
|
-
...
|
|
8537
|
-
...deps.
|
|
9619
|
+
...onEvent ? { onEvent } : {},
|
|
9620
|
+
...deps.replaySettlements ? { replaySettlements: true } : {},
|
|
9621
|
+
...priorCoordination?.questions.length ? { priorQuestions: priorCoordination.questions } : {},
|
|
9622
|
+
...nodeTools?.length ? { nodeTools } : {}
|
|
8538
9623
|
});
|
|
8539
9624
|
try {
|
|
8540
9625
|
try {
|
|
8541
9626
|
await driveHarness({
|
|
8542
|
-
profile,
|
|
9627
|
+
profile: stableProfile,
|
|
8543
9628
|
...profilePrompt !== void 0 ? { systemPrompt: profilePrompt } : {},
|
|
8544
9629
|
task,
|
|
8545
9630
|
scope,
|
|
8546
|
-
coordinationMcpUrl: mcp.url
|
|
9631
|
+
coordinationMcpUrl: mcp.url,
|
|
9632
|
+
coordinationTools: (nodeTools ?? []).map(({ name, description, inputSchema }) => ({
|
|
9633
|
+
name,
|
|
9634
|
+
description,
|
|
9635
|
+
inputSchema
|
|
9636
|
+
}))
|
|
8547
9637
|
});
|
|
8548
9638
|
} catch (error) {
|
|
8549
9639
|
if (!mcp.submittedResult()) throw error;
|
|
@@ -8562,9 +9652,57 @@ function supervisorAgent(profile, deps) {
|
|
|
8562
9652
|
}
|
|
8563
9653
|
}
|
|
8564
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
|
+
};
|
|
8565
9703
|
}
|
|
8566
9704
|
function routerBrainFromProfile(profile, deps) {
|
|
8567
|
-
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)");
|
|
8568
9706
|
const modelId = resolveSupervisorModelId(profile);
|
|
8569
9707
|
return routerBrain({
|
|
8570
9708
|
...deps.router,
|
|
@@ -8573,32 +9711,314 @@ function routerBrainFromProfile(profile, deps) {
|
|
|
8573
9711
|
}
|
|
8574
9712
|
//#endregion
|
|
8575
9713
|
//#region src/runtime/supervise/supervise.ts
|
|
8576
|
-
/**
|
|
8577
|
-
*
|
|
8578
|
-
*
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
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
|
+
/**
|
|
9724
|
+
* Build the worker seam from a backend (WHERE workers run) + an optional completion oracle (the
|
|
9725
|
+
* deliverable check that makes "settled ⟺ delivered" true — the guard against "ran but didn't
|
|
9726
|
+
* deliver"). The ONE place a backend becomes a spawnable worker.
|
|
9727
|
+
*
|
|
9728
|
+
* `seams` exists because this path builds the leaf executor EAGERLY and hands it back as a BYO
|
|
9729
|
+
* `executorSpec.executor`. The registry resolves a BYO executor without ever consulting the
|
|
9730
|
+
* per-child `ExecutorContext` the `Scope` seeds, so anything the scope would have supplied is
|
|
9731
|
+
* invisible here and has to be passed in. It is a FUNCTION because it is resolved once per worker
|
|
9732
|
+
* construction, so a caller may hand back something the run only learns later — which is exactly how
|
|
9733
|
+
* `supervise()` gives a traced run's workers their trace context without ordering the span recorder
|
|
9734
|
+
* ahead of the worker seam.
|
|
9735
|
+
*/
|
|
9736
|
+
function workerFromBackend(backend, deliverable, seams) {
|
|
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;
|
|
8590
9758
|
};
|
|
8591
|
-
const built = createExecutor(backend)(spec, ctx);
|
|
8592
|
-
const executor = deliverable ? gateOnDeliverable(built, deliverable) : built;
|
|
8593
9759
|
return {
|
|
8594
9760
|
name,
|
|
8595
9761
|
act: async () => "",
|
|
8596
9762
|
executorSpec: {
|
|
8597
|
-
|
|
8598
|
-
|
|
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
|
+
}
|
|
8599
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);
|
|
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");
|
|
8600
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;
|
|
8601
10012
|
};
|
|
10013
|
+
drive.deliver = (message) => {
|
|
10014
|
+
const deliver = activeExecutor?.deliver;
|
|
10015
|
+
if (!deliver) return false;
|
|
10016
|
+
return deliver.call(activeExecutor, message) !== false;
|
|
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";
|
|
8602
10022
|
}
|
|
8603
10023
|
/** Resolve one option that may be given as a value OR as a name into `opts.registry`. Both failure
|
|
8604
10024
|
* modes name the option, the requested name, and the table it was looked up in — a typo must not
|
|
@@ -8612,93 +10032,489 @@ function resolveNamed(option, table, value, registry) {
|
|
|
8612
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`);
|
|
8613
10033
|
return entry;
|
|
8614
10034
|
}
|
|
8615
|
-
|
|
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. */
|
|
8616
10112
|
function defaultPerWorker(budget) {
|
|
8617
10113
|
return {
|
|
8618
|
-
maxIterations: budget.maxIterations,
|
|
8619
|
-
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 } : {}
|
|
8620
10117
|
};
|
|
8621
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
|
+
}
|
|
8622
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. */
|
|
8623
10228
|
function supervise(profile, task, opts) {
|
|
8624
|
-
const
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
const
|
|
8629
|
-
const
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
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);
|
|
10268
|
+
const log = ctx.coordinationLog;
|
|
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;
|
|
10316
|
+
let spans;
|
|
10317
|
+
let makeWorkerAgent = options.makeWorkerAgent;
|
|
8637
10318
|
if (!makeWorkerAgent) {
|
|
8638
|
-
if (!
|
|
8639
|
-
|
|
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);
|
|
8640
10433
|
}
|
|
8641
10434
|
const workerFactory = makeWorkerAgent;
|
|
8642
|
-
const runId = opts.runId ?? "supervise";
|
|
8643
|
-
const log = ctx.coordinationLog;
|
|
8644
|
-
const now = opts.now ?? Date.now;
|
|
8645
10435
|
const start = async () => {
|
|
8646
|
-
const priorCoordination = log ? await log.load(runId) : void 0;
|
|
8647
|
-
const
|
|
10436
|
+
const priorCoordination = log ? await log.load(runId, rootOwnerId) : void 0;
|
|
10437
|
+
const authorizeRootMessage = authorizeDownFor(canonicalProfile, 1);
|
|
10438
|
+
const agent = supervisorAgent(canonicalProfile, {
|
|
8648
10439
|
blobs,
|
|
8649
10440
|
makeWorkerAgent: workerFactory,
|
|
10441
|
+
...authorizeRootMessage ? { authorizeDownMessage: authorizeRootMessage } : {},
|
|
8650
10442
|
perWorker,
|
|
10443
|
+
...log ? { onEvent: (_event, record) => log.append(runId, record, rootOwnerId) } : {},
|
|
8651
10444
|
...deliverable ? { deliverable } : {},
|
|
8652
|
-
...
|
|
8653
|
-
...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 } : {},
|
|
8654
10446
|
...finalizer ? { finalizer } : {},
|
|
8655
|
-
...
|
|
8656
|
-
...
|
|
8657
|
-
...
|
|
8658
|
-
...
|
|
8659
|
-
...
|
|
8660
|
-
|
|
8661
|
-
|
|
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 } : {},
|
|
8662
10466
|
...analysts ? { analysts } : {},
|
|
8663
|
-
...
|
|
8664
|
-
...
|
|
8665
|
-
...
|
|
8666
|
-
...
|
|
8667
|
-
...
|
|
8668
|
-
...
|
|
8669
|
-
...
|
|
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 } : {}
|
|
8670
10474
|
});
|
|
8671
|
-
|
|
10475
|
+
spans = options.otel ? createSupervisorSpanRecorder({
|
|
8672
10476
|
runId,
|
|
8673
|
-
...
|
|
10477
|
+
...options.otel,
|
|
8674
10478
|
now
|
|
8675
10479
|
}) : void 0;
|
|
8676
|
-
const
|
|
8677
|
-
const
|
|
8678
|
-
|
|
10480
|
+
const recorder = spans;
|
|
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,
|
|
8679
10486
|
runId,
|
|
8680
|
-
journal
|
|
10487
|
+
journal,
|
|
8681
10488
|
blobs,
|
|
8682
10489
|
executors: ctx.executors,
|
|
8683
|
-
|
|
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 } : {},
|
|
8684
10498
|
...probes ? { probes } : {},
|
|
8685
10499
|
...ctx.resume === true ? { resume: true } : {},
|
|
8686
|
-
...
|
|
8687
|
-
...
|
|
10500
|
+
...options.now ? { now: options.now } : {},
|
|
10501
|
+
...options.signal ? { signal: options.signal } : {},
|
|
10502
|
+
...hooks ? { hooks } : {},
|
|
10503
|
+
...recorder ? { workerTrace: recorder.workerTrace } : {}
|
|
8688
10504
|
});
|
|
8689
|
-
if (!
|
|
10505
|
+
if (!recorder) return run;
|
|
8690
10506
|
try {
|
|
8691
10507
|
const result = await run;
|
|
8692
|
-
await
|
|
10508
|
+
await recorder.finish({ result });
|
|
8693
10509
|
return result;
|
|
8694
10510
|
} catch (error) {
|
|
8695
|
-
await
|
|
10511
|
+
await recorder.finish({ error });
|
|
8696
10512
|
throw error;
|
|
8697
10513
|
}
|
|
8698
10514
|
};
|
|
8699
10515
|
return start();
|
|
8700
10516
|
}
|
|
8701
10517
|
//#endregion
|
|
8702
|
-
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 };
|
|
8703
10519
|
|
|
8704
|
-
//# sourceMappingURL=supervise-
|
|
10520
|
+
//# sourceMappingURL=supervise-U7_K5yuV.js.map
|