@tangle-network/agent-runtime 0.91.0 → 0.92.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 +4 -2
- package/dist/agent.d.ts +3 -3
- package/dist/agent.js +88 -9
- package/dist/agent.js.map +1 -1
- package/dist/{mcp-serve-verifier-XsX8rkB9.d.ts → agentic-generator-B8oeE2Yv.d.ts} +6 -33
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/candidate-execution/index.d.ts +104 -0
- package/dist/candidate-execution/index.js +34 -0
- package/dist/candidate-execution/index.js.map +1 -0
- package/dist/chunk-3BE7KTMU.js +1229 -0
- package/dist/chunk-3BE7KTMU.js.map +1 -0
- package/dist/chunk-3D2RHC4K.js +73 -0
- package/dist/chunk-3D2RHC4K.js.map +1 -0
- package/dist/chunk-3MDZX7YU.js +125 -0
- package/dist/chunk-3MDZX7YU.js.map +1 -0
- package/dist/{chunk-NC66AM3S.js → chunk-6O5USWVH.js} +32 -1413
- package/dist/chunk-6O5USWVH.js.map +1 -0
- package/dist/chunk-6O73TRHW.js +142 -0
- package/dist/chunk-6O73TRHW.js.map +1 -0
- package/dist/{chunk-JRS3YSRZ.js → chunk-7VJJJ2T2.js} +2 -2
- package/dist/chunk-A62TP7SK.js +4784 -0
- package/dist/chunk-A62TP7SK.js.map +1 -0
- package/dist/chunk-APVPRF4Y.js +2166 -0
- package/dist/chunk-APVPRF4Y.js.map +1 -0
- package/dist/{chunk-DWWII6N2.js → chunk-AUEIDTR3.js} +2 -2
- package/dist/{chunk-FF77IBQM.js → chunk-FRBHUNQ7.js} +2 -141
- package/dist/chunk-FRBHUNQ7.js.map +1 -0
- package/dist/{chunk-7ON74BQO.js → chunk-GDAQUFG6.js} +2 -2
- package/dist/{chunk-AD7JW4QG.js → chunk-I7WVPJBZ.js} +23 -1231
- package/dist/chunk-I7WVPJBZ.js.map +1 -0
- package/dist/{chunk-IOUUITQA.js → chunk-IGGZGKJD.js} +3 -3
- package/dist/chunk-PH65PR4F.js +860 -0
- package/dist/chunk-PH65PR4F.js.map +1 -0
- package/dist/chunk-RSWM2ZKM.js +659 -0
- package/dist/chunk-RSWM2ZKM.js.map +1 -0
- package/dist/{chunk-BZF3KQ6G.js → chunk-VSWBYWFK.js} +4 -122
- package/dist/chunk-VSWBYWFK.js.map +1 -0
- package/dist/{completion-gate-DkAnUmpb.d.ts → completion-gate-BLaiN0-X.d.ts} +1 -1
- package/dist/{coordination-rRj5hjJK.d.ts → coordination-DxJ83oZA.d.ts} +12 -5
- package/dist/environment-provider.d.ts +2 -2
- package/dist/environment-provider.js +2 -1
- package/dist/improve-CUVCq7xg.d.ts +152 -0
- package/dist/{improvement-adapter-CDR8QNVM.d.ts → improvement-adapter-BieWeK5J.d.ts} +16 -0
- package/dist/index.d.ts +27 -1092
- package/dist/index.js +108 -6491
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +160 -13
- package/dist/intelligence.js +535 -59
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +6 -6
- package/dist/knowledge.js +6 -4
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.js +5 -3
- package/dist/lifecycle.js.map +1 -1
- package/dist/{loop-runner-bin-DTbZVGfM.d.ts → loop-runner-bin-kKUNGLyV.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +8 -5
- package/dist/loops.d.ts +16 -16
- package/dist/loops.js +47 -41
- package/dist/mcp/bin.js +6 -4
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -8
- package/dist/mcp/index.js +9 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp-serve-verifier-Bg4C3p5S.d.ts +34 -0
- package/dist/{openai-tools-C4ZfUD4L.d.ts → openai-tools-E3woykz9.d.ts} +1 -1
- package/dist/prepare-Z08a4heC.d.ts +713 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/{router-client-DJImUDlm.d.ts → sanitize-C9go6tXj.d.ts} +113 -1
- package/dist/{structural-rollout-MwlpgQ-6.d.ts → structural-rollout-DHGDbhvR.d.ts} +3 -3
- package/dist/{supervise-DPmYPk0j.d.ts → supervise-T2pazU3G.d.ts} +4 -4
- package/dist/{types-SyuwunY_.d.ts → types-B00NtbCs.d.ts} +1 -1
- package/dist/{types-eMNgWgFi.d.ts → types-DAdIm4AC.d.ts} +1 -1
- package/dist/{worktree-fanout-BDFQIO-Y.d.ts → worktree-fanout-BUb2Ag02.d.ts} +3 -3
- package/package.json +26 -35
- package/skills/build-with-agent-runtime/SKILL.md +1 -1
- package/dist/chunk-AD7JW4QG.js.map +0 -1
- package/dist/chunk-BZF3KQ6G.js.map +0 -1
- package/dist/chunk-FF77IBQM.js.map +0 -1
- package/dist/chunk-IVGYLCFH.js +0 -381
- package/dist/chunk-IVGYLCFH.js.map +0 -1
- package/dist/chunk-NC66AM3S.js.map +0 -1
- /package/dist/{chunk-JRS3YSRZ.js.map → chunk-7VJJJ2T2.js.map} +0 -0
- /package/dist/{chunk-DWWII6N2.js.map → chunk-AUEIDTR3.js.map} +0 -0
- /package/dist/{chunk-7ON74BQO.js.map → chunk-GDAQUFG6.js.map} +0 -0
- /package/dist/{chunk-IOUUITQA.js.map → chunk-IGGZGKJD.js.map} +0 -0
|
@@ -2,23 +2,37 @@ import {
|
|
|
2
2
|
UI_LENSES
|
|
3
3
|
} from "./chunk-DPEUKJRO.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
assertModelAllowed,
|
|
6
|
+
buildLoopSpanNodes,
|
|
7
|
+
createSupervisor,
|
|
8
|
+
notifyRuntimeHookEvent,
|
|
9
|
+
routerBrain,
|
|
10
|
+
routerChatWithUsage,
|
|
11
|
+
runBrainLoop,
|
|
12
|
+
withDriverExecutor
|
|
13
|
+
} from "./chunk-APVPRF4Y.js";
|
|
14
|
+
import {
|
|
15
|
+
InMemoryResultBlobStore,
|
|
16
|
+
InMemorySpawnJournal,
|
|
17
|
+
contentAddress
|
|
18
|
+
} from "./chunk-3D2RHC4K.js";
|
|
19
|
+
import {
|
|
20
|
+
providerAsExecutor,
|
|
21
|
+
resolveAgentEnvironmentProvider
|
|
22
|
+
} from "./chunk-VSWBYWFK.js";
|
|
7
23
|
import {
|
|
8
24
|
addTokenUsage,
|
|
9
25
|
deleteBoxSafe,
|
|
10
26
|
mapWithConcurrency,
|
|
11
|
-
providerAsExecutor,
|
|
12
27
|
randomSuffix,
|
|
13
28
|
randomUuid,
|
|
14
|
-
resolveAgentEnvironmentProvider,
|
|
15
29
|
sleep,
|
|
16
30
|
stringifySafe,
|
|
17
31
|
throwAbort,
|
|
18
32
|
throwIfAborted,
|
|
19
33
|
withTimeout,
|
|
20
34
|
zeroTokenUsage
|
|
21
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-3MDZX7YU.js";
|
|
22
36
|
import {
|
|
23
37
|
extractLlmCallEvent
|
|
24
38
|
} from "./chunk-FVJ7M3DA.js";
|
|
@@ -30,1213 +44,9 @@ import {
|
|
|
30
44
|
AgentEvalError,
|
|
31
45
|
ConfigError,
|
|
32
46
|
NotFoundError,
|
|
33
|
-
RuntimeRunStateError,
|
|
34
47
|
ValidationError
|
|
35
48
|
} from "./chunk-YEJR7IXO.js";
|
|
36
49
|
|
|
37
|
-
// src/durable/spawn-journal.ts
|
|
38
|
-
import { createHash } from "crypto";
|
|
39
|
-
function contentAddress(artifact) {
|
|
40
|
-
const hex = createHash("sha256").update(stableStringify(artifact), "utf-8").digest("hex");
|
|
41
|
-
return `sha256:${hex}`;
|
|
42
|
-
}
|
|
43
|
-
function stableStringify(value) {
|
|
44
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
45
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
46
|
-
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
47
|
-
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
48
|
-
}
|
|
49
|
-
var InMemoryResultBlobStore = class {
|
|
50
|
-
blobs = /* @__PURE__ */ new Map();
|
|
51
|
-
async put(outRef, artifact) {
|
|
52
|
-
assertContentAddress(outRef, artifact);
|
|
53
|
-
this.blobs.set(outRef, artifact);
|
|
54
|
-
}
|
|
55
|
-
async get(outRef) {
|
|
56
|
-
return this.blobs.has(outRef) ? this.blobs.get(outRef) : void 0;
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
function assertContentAddress(outRef, artifact) {
|
|
60
|
-
const expected = contentAddress(artifact);
|
|
61
|
-
if (outRef !== expected) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
var InMemorySpawnJournal = class {
|
|
68
|
-
trees = /* @__PURE__ */ new Map();
|
|
69
|
-
async loadTree(root) {
|
|
70
|
-
const tree = this.trees.get(root);
|
|
71
|
-
if (!tree) return void 0;
|
|
72
|
-
return tree.events.map((ev) => ({ ...ev }));
|
|
73
|
-
}
|
|
74
|
-
async beginTree(root, at) {
|
|
75
|
-
const existing = this.trees.get(root);
|
|
76
|
-
if (existing) {
|
|
77
|
-
if (existing.begunAt !== at) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`spawn tree '${root}' already begun at ${existing.begunAt}; refusing to overwrite with ${at}`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
this.trees.set(root, { begunAt: at, events: [] });
|
|
85
|
-
}
|
|
86
|
-
async appendEvent(root, ev) {
|
|
87
|
-
const tree = this.trees.get(root);
|
|
88
|
-
if (!tree) {
|
|
89
|
-
throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
|
|
90
|
-
}
|
|
91
|
-
assertSeqUnique(root, tree.events, ev);
|
|
92
|
-
tree.events.push({ ...ev });
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
function assertSeqUnique(root, events, ev) {
|
|
96
|
-
if (ev.kind === "spawned" || ev.kind === "metered") return;
|
|
97
|
-
if (events.some((e) => e.kind !== "spawned" && e.kind !== "metered" && e.seq === ev.seq)) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; the cursor order replay relies on is not unique`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/runtime/supervise/model-policy.ts
|
|
105
|
-
function assertModelAllowed(model, allowed) {
|
|
106
|
-
if (!allowed || model === void 0) return;
|
|
107
|
-
if (!allowed.includes(model)) {
|
|
108
|
-
throw new ConfigError(
|
|
109
|
-
`model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/runtime/router-client.ts
|
|
115
|
-
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
116
|
-
|
|
117
|
-
// src/runtime/tool-loop.ts
|
|
118
|
-
function estimateConversationTokens(messages) {
|
|
119
|
-
let chars = 0;
|
|
120
|
-
for (const m of messages) {
|
|
121
|
-
const content = m.content;
|
|
122
|
-
chars += typeof content === "string" ? content.length : safeJsonLength(content);
|
|
123
|
-
const calls = m.tool_calls;
|
|
124
|
-
if (calls) for (const c of calls) chars += c.function?.arguments?.length ?? 0;
|
|
125
|
-
}
|
|
126
|
-
return Math.ceil(chars / 4);
|
|
127
|
-
}
|
|
128
|
-
function safeJsonLength(value) {
|
|
129
|
-
if (value === void 0 || value === null) return 0;
|
|
130
|
-
try {
|
|
131
|
-
return JSON.stringify(value)?.length ?? 0;
|
|
132
|
-
} catch {
|
|
133
|
-
return String(value).length;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async function maybeCompact(messages, c, turn) {
|
|
137
|
-
const head = c.preserveHead !== void 0 && c.preserveHead > 0 ? c.preserveHead : 2;
|
|
138
|
-
if (messages.length <= head + 1) return false;
|
|
139
|
-
const estimate = c.estimateTokens ?? estimateConversationTokens;
|
|
140
|
-
const before = estimate(messages);
|
|
141
|
-
if (before <= c.thresholdTokens) return false;
|
|
142
|
-
const digest = await c.distill([...messages]);
|
|
143
|
-
messages.splice(head, messages.length - head, {
|
|
144
|
-
role: "user",
|
|
145
|
-
content: `[earlier work compacted to save context \u2014 progress so far]
|
|
146
|
-
${digest}`
|
|
147
|
-
});
|
|
148
|
-
c.onCompact?.({ turn, beforeTokens: before, afterTokens: estimate(messages) });
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
async function runBrainLoop(opts) {
|
|
152
|
-
const maxTurns = opts.maxTurns ?? 4;
|
|
153
|
-
const messages = [...opts.initialMessages];
|
|
154
|
-
let toolCalls = 0;
|
|
155
|
-
let lastText = "";
|
|
156
|
-
const usage = { input: 0, output: 0 };
|
|
157
|
-
const toolTrace = [];
|
|
158
|
-
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
159
|
-
if (opts.hooks?.stopBefore?.(turn)) break;
|
|
160
|
-
await opts.hooks?.beforeTurn?.(turn, messages);
|
|
161
|
-
if (opts.compaction) await maybeCompact(messages, opts.compaction, turn);
|
|
162
|
-
const r = await opts.chat(messages, opts.tools);
|
|
163
|
-
if (r.usage) {
|
|
164
|
-
usage.input += r.usage.input;
|
|
165
|
-
usage.output += r.usage.output;
|
|
166
|
-
opts.hooks?.onUsage?.(r.usage);
|
|
167
|
-
}
|
|
168
|
-
if (r.content) lastText = r.content;
|
|
169
|
-
if (r.toolCalls.length === 0) {
|
|
170
|
-
if (r.content) messages.push({ role: "assistant", content: r.content });
|
|
171
|
-
return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
|
|
172
|
-
}
|
|
173
|
-
messages.push({
|
|
174
|
-
role: "assistant",
|
|
175
|
-
content: r.content ?? "",
|
|
176
|
-
tool_calls: r.toolCalls.map((tc) => ({
|
|
177
|
-
id: tc.id,
|
|
178
|
-
type: "function",
|
|
179
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
180
|
-
}))
|
|
181
|
-
});
|
|
182
|
-
for (const tc of r.toolCalls) {
|
|
183
|
-
toolCalls += 1;
|
|
184
|
-
let args = {};
|
|
185
|
-
try {
|
|
186
|
-
args = JSON.parse(tc.arguments);
|
|
187
|
-
} catch {
|
|
188
|
-
messages.push({
|
|
189
|
-
role: "tool",
|
|
190
|
-
tool_call_id: tc.id,
|
|
191
|
-
content: `error: arguments were not valid JSON: ${tc.arguments.slice(0, 200)}`
|
|
192
|
-
});
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const out = await opts.execute(tc.name, args);
|
|
196
|
-
messages.push({ role: "tool", tool_call_id: tc.id, content: out });
|
|
197
|
-
toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/runtime/router-client.ts
|
|
204
|
-
async function routerChatWithUsage(cfg, messages, opts) {
|
|
205
|
-
const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
206
|
-
const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
|
|
207
|
-
let temperature = opts?.temperature ?? 0.2;
|
|
208
|
-
const body = () => ({
|
|
209
|
-
model: cfg.model,
|
|
210
|
-
messages,
|
|
211
|
-
temperature,
|
|
212
|
-
max_tokens: opts?.maxTokens ?? 8192,
|
|
213
|
-
...opts?.reasoningEffort ? { reasoning_effort: opts.reasoningEffort } : {}
|
|
214
|
-
});
|
|
215
|
-
if (cfg.complete) return parseChatResult(await cfg.complete(body()), cfg.model);
|
|
216
|
-
let lastErr = "";
|
|
217
|
-
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
218
|
-
const res = await fetch(url, {
|
|
219
|
-
method: "POST",
|
|
220
|
-
headers,
|
|
221
|
-
body: JSON.stringify(body()),
|
|
222
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
223
|
-
});
|
|
224
|
-
if (res.ok) return parseChatResult(await res.json(), cfg.model);
|
|
225
|
-
const status = res.status;
|
|
226
|
-
const text = (await res.text()).slice(0, 200);
|
|
227
|
-
lastErr = `router ${status}: ${text}`;
|
|
228
|
-
if (status === 400 && /temperature/i.test(text) && temperature !== 1) {
|
|
229
|
-
temperature = 1;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (![408, 425, 429, 500, 502, 503, 504, 520, 522, 524].includes(status))
|
|
233
|
-
throw new Error(lastErr);
|
|
234
|
-
if (attempt < 4) await new Promise((r) => setTimeout(r, 800 * 2 ** attempt));
|
|
235
|
-
}
|
|
236
|
-
throw new Error(`${lastErr} (exhausted retries)`);
|
|
237
|
-
}
|
|
238
|
-
function parseChatResult(json, model) {
|
|
239
|
-
const data = json;
|
|
240
|
-
const u = data.usage;
|
|
241
|
-
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
242
|
-
const costUsd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
|
|
243
|
-
const msg = data.choices?.[0]?.message;
|
|
244
|
-
const { content, reasoning } = splitReasoning(
|
|
245
|
-
msg?.content ?? "",
|
|
246
|
-
msg?.reasoning ?? msg?.reasoning_content
|
|
247
|
-
);
|
|
248
|
-
return {
|
|
249
|
-
content,
|
|
250
|
-
...reasoning ? { reasoning } : {},
|
|
251
|
-
...usage ? { usage } : {},
|
|
252
|
-
...costUsd !== void 0 ? { costUsd } : {}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function splitReasoning(rawContent, fieldReasoning) {
|
|
256
|
-
const open = rawContent.indexOf("<think>");
|
|
257
|
-
if (open !== -1) {
|
|
258
|
-
const close = rawContent.indexOf("</think>", open);
|
|
259
|
-
const inline = close !== -1 ? rawContent.slice(open + 7, close) : rawContent.slice(open + 7);
|
|
260
|
-
const rest = close !== -1 ? rawContent.slice(0, open) + rawContent.slice(close + 8) : rawContent.slice(0, open);
|
|
261
|
-
const reasoning = [fieldReasoning, inline.trim()].filter(Boolean).join("\n");
|
|
262
|
-
return { content: rest.trim(), ...reasoning ? { reasoning } : {} };
|
|
263
|
-
}
|
|
264
|
-
return { content: rawContent, ...fieldReasoning ? { reasoning: fieldReasoning } : {} };
|
|
265
|
-
}
|
|
266
|
-
async function routerChatWithTools(cfg, messages, tools, opts) {
|
|
267
|
-
const body = {
|
|
268
|
-
model: cfg.model,
|
|
269
|
-
messages,
|
|
270
|
-
tools,
|
|
271
|
-
tool_choice: opts?.toolChoice ?? "auto",
|
|
272
|
-
temperature: opts?.temperature ?? 0.3,
|
|
273
|
-
...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
|
|
274
|
-
};
|
|
275
|
-
const raw = cfg.complete ? await cfg.complete(body) : await (async () => {
|
|
276
|
-
const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
277
|
-
method: "POST",
|
|
278
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
|
|
279
|
-
body: JSON.stringify(body),
|
|
280
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
281
|
-
});
|
|
282
|
-
if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
283
|
-
return res.json();
|
|
284
|
-
})();
|
|
285
|
-
const data = raw;
|
|
286
|
-
const msg = data.choices?.[0]?.message;
|
|
287
|
-
const toolCalls = (msg?.tool_calls ?? []).map((tc, i) => ({
|
|
288
|
-
id: tc.id ?? `call_${i}`,
|
|
289
|
-
name: tc.function?.name ?? "",
|
|
290
|
-
arguments: tc.function?.arguments ?? "{}"
|
|
291
|
-
}));
|
|
292
|
-
const u = data.usage;
|
|
293
|
-
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
294
|
-
const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
|
|
295
|
-
return {
|
|
296
|
-
content: msg?.content ?? null,
|
|
297
|
-
toolCalls,
|
|
298
|
-
...usage ? { usage } : {},
|
|
299
|
-
...costUsd !== void 0 ? { costUsd } : {}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
303
|
-
const initialMessages = opts?.initialMessages ?? [
|
|
304
|
-
{ role: "system", content: system },
|
|
305
|
-
{ role: "user", content: user }
|
|
306
|
-
];
|
|
307
|
-
return runBrainLoop({
|
|
308
|
-
chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
|
|
309
|
-
...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
310
|
-
...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
|
|
311
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
312
|
-
}),
|
|
313
|
-
tools,
|
|
314
|
-
execute,
|
|
315
|
-
initialMessages,
|
|
316
|
-
maxTurns: opts?.maxTurns ?? 4
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
function routerBrain(cfg, opts = {}) {
|
|
320
|
-
const temperature = opts.temperature ?? 0.4;
|
|
321
|
-
return (messages, tools) => routerChatWithTools(cfg, messages, tools, { temperature, toolChoice: "auto" });
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// src/runtime-hooks.ts
|
|
325
|
-
function defineRuntimeHooks(hooks) {
|
|
326
|
-
return hooks;
|
|
327
|
-
}
|
|
328
|
-
function composeRuntimeHooks(...entries) {
|
|
329
|
-
const hooks = entries.filter((entry) => !!entry);
|
|
330
|
-
return {
|
|
331
|
-
onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
|
|
332
|
-
const pending = [];
|
|
333
|
-
for (const hook of hooks) {
|
|
334
|
-
const result = hook.onEvent?.(event, context);
|
|
335
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
336
|
-
}
|
|
337
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
338
|
-
return void 0;
|
|
339
|
-
} : void 0,
|
|
340
|
-
onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
|
|
341
|
-
const pending = [];
|
|
342
|
-
for (const hook of hooks) {
|
|
343
|
-
const result = hook.onDecisionPoint?.(point, context);
|
|
344
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
345
|
-
}
|
|
346
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
347
|
-
return void 0;
|
|
348
|
-
} : void 0,
|
|
349
|
-
onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
|
|
350
|
-
const pending = [];
|
|
351
|
-
for (const hook of hooks) {
|
|
352
|
-
const result = hook.onHookError?.(error, context);
|
|
353
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
354
|
-
}
|
|
355
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
356
|
-
return void 0;
|
|
357
|
-
} : void 0
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
function notifyRuntimeHookEvent(hooks, event, context = {}) {
|
|
361
|
-
const onEvent = hooks?.onEvent;
|
|
362
|
-
if (!onEvent) return;
|
|
363
|
-
try {
|
|
364
|
-
const result = onEvent(event, context);
|
|
365
|
-
if (isThenable(result)) {
|
|
366
|
-
void result.catch((error) => {
|
|
367
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
368
|
-
hook: "onEvent",
|
|
369
|
-
eventId: event.id,
|
|
370
|
-
target: event.target,
|
|
371
|
-
phase: event.phase
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
377
|
-
hook: "onEvent",
|
|
378
|
-
eventId: event.id,
|
|
379
|
-
target: event.target,
|
|
380
|
-
phase: event.phase
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
|
|
385
|
-
const onDecisionPoint = hooks?.onDecisionPoint;
|
|
386
|
-
if (!onDecisionPoint) return;
|
|
387
|
-
try {
|
|
388
|
-
const result = onDecisionPoint(point, context);
|
|
389
|
-
if (isThenable(result)) {
|
|
390
|
-
void result.catch((error) => {
|
|
391
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
392
|
-
hook: "onDecisionPoint",
|
|
393
|
-
decisionId: point.id,
|
|
394
|
-
decisionKind: point.kind
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
} catch (error) {
|
|
399
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
400
|
-
hook: "onDecisionPoint",
|
|
401
|
-
decisionId: point.id,
|
|
402
|
-
decisionKind: point.kind
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
function notifyRuntimeHookError(hooks, error, context) {
|
|
407
|
-
try {
|
|
408
|
-
const result = hooks?.onHookError?.(error, context);
|
|
409
|
-
if (isThenable(result)) void result.catch(() => void 0);
|
|
410
|
-
} catch {
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function isThenable(value) {
|
|
414
|
-
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
415
|
-
}
|
|
416
|
-
function toError(error) {
|
|
417
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// src/runtime/supervise/scope.ts
|
|
421
|
-
var nestedScopeSeamKey = "nested-scope";
|
|
422
|
-
function makeNestedScopeSeam(args, childNodeId) {
|
|
423
|
-
return {
|
|
424
|
-
depth: args.depth,
|
|
425
|
-
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
426
|
-
journalRoot: args.root,
|
|
427
|
-
mount(nestedRoot, signal) {
|
|
428
|
-
return createScope({
|
|
429
|
-
parentId: childNodeId,
|
|
430
|
-
root: nestedRoot,
|
|
431
|
-
pool: args.pool,
|
|
432
|
-
journal: args.journal,
|
|
433
|
-
blobs: args.blobs,
|
|
434
|
-
executors: args.executors,
|
|
435
|
-
// Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
|
|
436
|
-
// the nested scope adds its OWN nested-scope seam per child in `spawn`.
|
|
437
|
-
seams: args.seams,
|
|
438
|
-
depth: args.depth + 1,
|
|
439
|
-
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
440
|
-
signal,
|
|
441
|
-
...args.now ? { now: args.now } : {},
|
|
442
|
-
...args.hooks ? { hooks: args.hooks } : {}
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
function createScope(args) {
|
|
448
|
-
const children = /* @__PURE__ */ new Map();
|
|
449
|
-
let spawnOrdinal = 0;
|
|
450
|
-
let cursorSeq = 0;
|
|
451
|
-
let meterSeq = 0;
|
|
452
|
-
const now = args.now ?? Date.now;
|
|
453
|
-
function spawn4(agent, task, opts) {
|
|
454
|
-
if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
|
|
455
|
-
return { ok: false, reason: "depth-exceeded" };
|
|
456
|
-
}
|
|
457
|
-
const spec = agent.executorSpec;
|
|
458
|
-
if (!isAgentSpec(spec)) {
|
|
459
|
-
throw new ValidationError(
|
|
460
|
-
`scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
const resolved = args.executors.resolve(spec);
|
|
464
|
-
if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
|
|
465
|
-
const reservation = args.pool.reserve(opts.budget);
|
|
466
|
-
if (!reservation.ok) return { ok: false, reason: reservation.reason };
|
|
467
|
-
try {
|
|
468
|
-
const ordinal = spawnOrdinal++;
|
|
469
|
-
const id = `${args.parentId}:s${ordinal}`;
|
|
470
|
-
const childAbort = new AbortController();
|
|
471
|
-
const cascadeAbort = () => childAbort.abort();
|
|
472
|
-
if (args.signal.aborted) childAbort.abort();
|
|
473
|
-
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
474
|
-
const ctx = {
|
|
475
|
-
signal: childAbort.signal,
|
|
476
|
-
seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
|
|
477
|
-
};
|
|
478
|
-
const executor = resolved.value(spec, ctx);
|
|
479
|
-
const handle = {
|
|
480
|
-
id,
|
|
481
|
-
label: opts.label,
|
|
482
|
-
get status() {
|
|
483
|
-
return children.get(id)?.status ?? "cancelled";
|
|
484
|
-
},
|
|
485
|
-
abort(reason) {
|
|
486
|
-
childAbort.abort(reason);
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
const live = {
|
|
490
|
-
id,
|
|
491
|
-
status: "acquiring",
|
|
492
|
-
runtime: executor.runtime,
|
|
493
|
-
budget: opts.budget,
|
|
494
|
-
label: opts.label,
|
|
495
|
-
spent: zeroSpend(),
|
|
496
|
-
settled: void 0,
|
|
497
|
-
delivered: false,
|
|
498
|
-
executorDone: false,
|
|
499
|
-
...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
|
|
500
|
-
};
|
|
501
|
-
children.set(id, live);
|
|
502
|
-
void args.journal.appendEvent(args.root, {
|
|
503
|
-
kind: "spawned",
|
|
504
|
-
id,
|
|
505
|
-
parent: args.parentId,
|
|
506
|
-
label: opts.label,
|
|
507
|
-
budget: opts.budget,
|
|
508
|
-
runtime: executor.runtime,
|
|
509
|
-
seq: ordinal,
|
|
510
|
-
at: new Date(now()).toISOString()
|
|
511
|
-
});
|
|
512
|
-
notifyRuntimeHookEvent(
|
|
513
|
-
args.hooks,
|
|
514
|
-
{
|
|
515
|
-
id: `${id}:spawn`,
|
|
516
|
-
runId: args.root,
|
|
517
|
-
target: "agent.spawn",
|
|
518
|
-
phase: "after",
|
|
519
|
-
timestamp: now(),
|
|
520
|
-
stepIndex: ordinal,
|
|
521
|
-
parentId: args.parentId,
|
|
522
|
-
payload: {
|
|
523
|
-
childId: id,
|
|
524
|
-
label: opts.label,
|
|
525
|
-
runtime: executor.runtime,
|
|
526
|
-
budget: opts.budget,
|
|
527
|
-
depth: args.depth
|
|
528
|
-
}
|
|
529
|
-
},
|
|
530
|
-
{ signal: args.signal }
|
|
531
|
-
);
|
|
532
|
-
const settled = runChild(
|
|
533
|
-
live,
|
|
534
|
-
executor,
|
|
535
|
-
childAbort,
|
|
536
|
-
task,
|
|
537
|
-
opts,
|
|
538
|
-
args.pool,
|
|
539
|
-
reservation.ticket,
|
|
540
|
-
args.blobs
|
|
541
|
-
).then((s) => {
|
|
542
|
-
live.resolved = s;
|
|
543
|
-
return s;
|
|
544
|
-
}).finally(() => {
|
|
545
|
-
args.signal.removeEventListener("abort", cascadeAbort);
|
|
546
|
-
});
|
|
547
|
-
live.settled = settled;
|
|
548
|
-
return { ok: true, handle };
|
|
549
|
-
} catch (err) {
|
|
550
|
-
args.pool.reconcile(reservation.ticket, zeroSpend());
|
|
551
|
-
throw err;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
async function next() {
|
|
555
|
-
const undelivered = () => [...children.values()].filter((c) => !c.delivered);
|
|
556
|
-
if (undelivered().length === 0) return null;
|
|
557
|
-
for (; ; ) {
|
|
558
|
-
const pending = undelivered();
|
|
559
|
-
if (pending.length === 0) return null;
|
|
560
|
-
const ready = pending.find((c) => c.resolved !== void 0);
|
|
561
|
-
const chosen = ready ?? await raceFirstSettled(pending);
|
|
562
|
-
if (chosen.delivered) continue;
|
|
563
|
-
chosen.delivered = true;
|
|
564
|
-
const seq = cursorSeq++;
|
|
565
|
-
const settlement = chosen.resolved;
|
|
566
|
-
if (!settlement) {
|
|
567
|
-
throw new ValidationError(
|
|
568
|
-
`scope.next: child '${chosen.id}' won the settle race without a resolved value`
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
return finalizeSettlement(chosen, settlement, seq, args, now);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
async function nextResolved() {
|
|
575
|
-
for (; ; ) {
|
|
576
|
-
const candidates = [...children.values()].filter((c) => !c.delivered);
|
|
577
|
-
const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
|
|
578
|
-
if (!pick) return null;
|
|
579
|
-
const settlement = await pick.settled;
|
|
580
|
-
if (pick.delivered) continue;
|
|
581
|
-
pick.delivered = true;
|
|
582
|
-
const seq = cursorSeq++;
|
|
583
|
-
return finalizeSettlement(pick, settlement, seq, args, now);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
function send(nodeId, msg) {
|
|
587
|
-
const child = children.get(nodeId);
|
|
588
|
-
if (!child || child.delivered || !child.deliver) return false;
|
|
589
|
-
child.deliver(msg);
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
async function meter(spend, detail) {
|
|
593
|
-
const seq = meterSeq++;
|
|
594
|
-
args.pool.observe(spend);
|
|
595
|
-
await args.journal.appendEvent(args.root, {
|
|
596
|
-
kind: "metered",
|
|
597
|
-
id: args.parentId,
|
|
598
|
-
spend,
|
|
599
|
-
seq,
|
|
600
|
-
at: new Date(now()).toISOString()
|
|
601
|
-
});
|
|
602
|
-
notifyRuntimeHookEvent(
|
|
603
|
-
args.hooks,
|
|
604
|
-
{
|
|
605
|
-
id: `${args.parentId}:meter:${seq}`,
|
|
606
|
-
runId: args.root,
|
|
607
|
-
target: "agent.turn",
|
|
608
|
-
phase: "after",
|
|
609
|
-
timestamp: now(),
|
|
610
|
-
parentId: args.parentId,
|
|
611
|
-
payload: { spend, ...detail ?? {} }
|
|
612
|
-
},
|
|
613
|
-
{ signal: args.signal }
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
return {
|
|
617
|
-
spawn: spawn4,
|
|
618
|
-
next,
|
|
619
|
-
nextResolved,
|
|
620
|
-
send,
|
|
621
|
-
signal: args.signal,
|
|
622
|
-
meter,
|
|
623
|
-
get view() {
|
|
624
|
-
return makeTreeView(args.parentId, children);
|
|
625
|
-
},
|
|
626
|
-
get budget() {
|
|
627
|
-
return args.pool.readout();
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
async function raceFirstSettled(pending) {
|
|
632
|
-
return Promise.race(pending.map((c) => c.settled.then(() => c)));
|
|
633
|
-
}
|
|
634
|
-
async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
635
|
-
const handle = frozenHandle(child);
|
|
636
|
-
if (settlement.kind === "down") {
|
|
637
|
-
child.status = "failed";
|
|
638
|
-
await args.journal.appendEvent(args.root, {
|
|
639
|
-
kind: "settled",
|
|
640
|
-
id: child.id,
|
|
641
|
-
status: "down",
|
|
642
|
-
spent: child.spent,
|
|
643
|
-
infra: settlement.infra,
|
|
644
|
-
seq,
|
|
645
|
-
at: new Date(now()).toISOString()
|
|
646
|
-
});
|
|
647
|
-
if (settlement.metered) {
|
|
648
|
-
await args.journal.appendEvent(args.root, {
|
|
649
|
-
kind: "metered",
|
|
650
|
-
id: child.id,
|
|
651
|
-
spend: settlement.metered,
|
|
652
|
-
seq,
|
|
653
|
-
at: new Date(now()).toISOString()
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
notifyRuntimeHookEvent(
|
|
657
|
-
args.hooks,
|
|
658
|
-
{
|
|
659
|
-
id: `${child.id}:settled`,
|
|
660
|
-
runId: args.root,
|
|
661
|
-
target: "agent.child",
|
|
662
|
-
phase: "after",
|
|
663
|
-
timestamp: now(),
|
|
664
|
-
stepIndex: seq,
|
|
665
|
-
parentId: args.parentId,
|
|
666
|
-
payload: {
|
|
667
|
-
childId: child.id,
|
|
668
|
-
status: "down",
|
|
669
|
-
reason: settlement.reason,
|
|
670
|
-
infra: settlement.infra,
|
|
671
|
-
spent: child.spent
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
|
-
{ signal: args.signal }
|
|
675
|
-
);
|
|
676
|
-
return {
|
|
677
|
-
kind: "down",
|
|
678
|
-
handle,
|
|
679
|
-
reason: settlement.reason,
|
|
680
|
-
infra: settlement.infra,
|
|
681
|
-
restartCount: settlement.restartCount,
|
|
682
|
-
seq
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
child.status = "done";
|
|
686
|
-
child.outRef = settlement.outRef;
|
|
687
|
-
child.spent = settlement.spent;
|
|
688
|
-
await args.journal.appendEvent(args.root, {
|
|
689
|
-
kind: "settled",
|
|
690
|
-
id: child.id,
|
|
691
|
-
status: "done",
|
|
692
|
-
outRef: settlement.outRef,
|
|
693
|
-
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
694
|
-
spent: settlement.spent,
|
|
695
|
-
seq,
|
|
696
|
-
at: new Date(now()).toISOString()
|
|
697
|
-
});
|
|
698
|
-
if (settlement.metered) {
|
|
699
|
-
await args.journal.appendEvent(args.root, {
|
|
700
|
-
kind: "metered",
|
|
701
|
-
id: child.id,
|
|
702
|
-
spend: settlement.metered,
|
|
703
|
-
seq,
|
|
704
|
-
at: new Date(now()).toISOString()
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
notifyRuntimeHookEvent(
|
|
708
|
-
args.hooks,
|
|
709
|
-
{
|
|
710
|
-
id: `${child.id}:settled`,
|
|
711
|
-
runId: args.root,
|
|
712
|
-
target: "agent.child",
|
|
713
|
-
phase: "after",
|
|
714
|
-
timestamp: now(),
|
|
715
|
-
stepIndex: seq,
|
|
716
|
-
parentId: args.parentId,
|
|
717
|
-
payload: {
|
|
718
|
-
childId: child.id,
|
|
719
|
-
status: "done",
|
|
720
|
-
outRef: settlement.outRef,
|
|
721
|
-
score: settlement.verdict?.score,
|
|
722
|
-
valid: settlement.verdict?.valid,
|
|
723
|
-
spent: settlement.spent
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
{ signal: args.signal }
|
|
727
|
-
);
|
|
728
|
-
return {
|
|
729
|
-
kind: "done",
|
|
730
|
-
handle,
|
|
731
|
-
out: settlement.out,
|
|
732
|
-
outRef: settlement.outRef,
|
|
733
|
-
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
734
|
-
spent: settlement.spent,
|
|
735
|
-
seq
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
|
|
739
|
-
let reconciled = false;
|
|
740
|
-
const reconcileOnce = (spend) => {
|
|
741
|
-
if (reconciled) return;
|
|
742
|
-
reconciled = true;
|
|
743
|
-
pool.reconcile(ticket, clampSpend(spend, opts.budget));
|
|
744
|
-
};
|
|
745
|
-
try {
|
|
746
|
-
live.status = "running";
|
|
747
|
-
const ran = executor.execute(task, childAbort.signal);
|
|
748
|
-
let artifact;
|
|
749
|
-
if (isAsyncIterable(ran)) {
|
|
750
|
-
const spend = await foldStream(ran);
|
|
751
|
-
live.spent = spend;
|
|
752
|
-
artifact = executor.resultArtifact();
|
|
753
|
-
reconcileOnce(spend);
|
|
754
|
-
} else {
|
|
755
|
-
const terminal = await ran;
|
|
756
|
-
live.spent = terminal.spent;
|
|
757
|
-
artifact = terminal;
|
|
758
|
-
reconcileOnce(terminal.spent);
|
|
759
|
-
}
|
|
760
|
-
live.executorDone = true;
|
|
761
|
-
const ownMetered = executor.metered?.();
|
|
762
|
-
if (childAbort.signal.aborted) {
|
|
763
|
-
await teardownSafe(executor, opts.shutdown ?? "brutalKill");
|
|
764
|
-
return downRecord("aborted before settle", true, ownMetered);
|
|
765
|
-
}
|
|
766
|
-
const outRef = contentAddress(artifact.out);
|
|
767
|
-
await blobs.put(outRef, artifact.out);
|
|
768
|
-
await teardownSafe(executor, opts.shutdown ?? "infinity");
|
|
769
|
-
return {
|
|
770
|
-
kind: "done",
|
|
771
|
-
out: artifact.out,
|
|
772
|
-
outRef,
|
|
773
|
-
...artifact.verdict ? { verdict: artifact.verdict } : {},
|
|
774
|
-
spent: live.spent,
|
|
775
|
-
...ownMetered ? { metered: ownMetered } : {}
|
|
776
|
-
};
|
|
777
|
-
} catch (err) {
|
|
778
|
-
live.executorDone = true;
|
|
779
|
-
reconcileOnce(live.spent);
|
|
780
|
-
await teardownSafe(executor, "brutalKill");
|
|
781
|
-
const aborted = childAbort.signal.aborted || isAbortError(err);
|
|
782
|
-
return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
function settledToIteration(settled) {
|
|
786
|
-
if (settled.kind === "down") {
|
|
787
|
-
throw new ValidationError(
|
|
788
|
-
`settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
return {
|
|
792
|
-
index: settled.seq,
|
|
793
|
-
task: void 0,
|
|
794
|
-
agentRunName: settled.handle.label,
|
|
795
|
-
output: settled.out,
|
|
796
|
-
...settled.verdict ? { verdict: settled.verdict } : {},
|
|
797
|
-
events: [],
|
|
798
|
-
startedAt: 0,
|
|
799
|
-
endedAt: settled.spent.ms,
|
|
800
|
-
costUsd: settled.spent.usd,
|
|
801
|
-
tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
function makeTreeView(root, children) {
|
|
805
|
-
const nodes = [...children.values()].map((c) => ({
|
|
806
|
-
id: c.id,
|
|
807
|
-
parent: root,
|
|
808
|
-
label: c.label,
|
|
809
|
-
status: c.status,
|
|
810
|
-
runtime: c.runtime,
|
|
811
|
-
budget: c.budget,
|
|
812
|
-
spent: c.spent,
|
|
813
|
-
...c.outRef ? { outRef: c.outRef } : {}
|
|
814
|
-
}));
|
|
815
|
-
return {
|
|
816
|
-
root,
|
|
817
|
-
nodes,
|
|
818
|
-
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function frozenHandle(child) {
|
|
822
|
-
return {
|
|
823
|
-
id: child.id,
|
|
824
|
-
label: child.label,
|
|
825
|
-
status: child.status,
|
|
826
|
-
abort() {
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
async function foldStream(stream) {
|
|
831
|
-
const tokens = { input: 0, output: 0 };
|
|
832
|
-
let usd = 0;
|
|
833
|
-
let iterations = 0;
|
|
834
|
-
for await (const ev of stream) {
|
|
835
|
-
if (ev.kind === "tokens") {
|
|
836
|
-
tokens.input += ev.input;
|
|
837
|
-
tokens.output += ev.output;
|
|
838
|
-
} else if (ev.kind === "cost") {
|
|
839
|
-
usd += ev.usd;
|
|
840
|
-
} else {
|
|
841
|
-
iterations += 1;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
845
|
-
}
|
|
846
|
-
function clampSpend(spend, budget) {
|
|
847
|
-
const totalTokens2 = spend.tokens.input + spend.tokens.output;
|
|
848
|
-
const tokensOk = totalTokens2 <= budget.maxTokens;
|
|
849
|
-
const itersOk = spend.iterations <= budget.maxIterations;
|
|
850
|
-
const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
|
|
851
|
-
if (tokensOk && itersOk && usdOk) return spend;
|
|
852
|
-
const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
|
|
853
|
-
return {
|
|
854
|
-
iterations: Math.min(spend.iterations, budget.maxIterations),
|
|
855
|
-
tokens: ratio < 1 ? {
|
|
856
|
-
input: Math.floor(spend.tokens.input * ratio),
|
|
857
|
-
output: Math.floor(spend.tokens.output * ratio)
|
|
858
|
-
} : spend.tokens,
|
|
859
|
-
usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
|
|
860
|
-
ms: spend.ms
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
async function teardownSafe(executor, grace) {
|
|
864
|
-
try {
|
|
865
|
-
await executor.teardown(grace);
|
|
866
|
-
} catch {
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
function downRecord(reason, infra, metered) {
|
|
870
|
-
return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
|
|
871
|
-
}
|
|
872
|
-
function zeroSpend() {
|
|
873
|
-
return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
874
|
-
}
|
|
875
|
-
function isAsyncIterable(value) {
|
|
876
|
-
return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
877
|
-
}
|
|
878
|
-
function isAgentSpec(value) {
|
|
879
|
-
if (typeof value !== "object" || value === null) return false;
|
|
880
|
-
const v = value;
|
|
881
|
-
return "profile" in v && "harness" in v;
|
|
882
|
-
}
|
|
883
|
-
function isAbortError(err) {
|
|
884
|
-
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
885
|
-
}
|
|
886
|
-
function isInfraError(err) {
|
|
887
|
-
return err instanceof ValidationError;
|
|
888
|
-
}
|
|
889
|
-
function errMessage(err) {
|
|
890
|
-
if (err instanceof Error) return err.message;
|
|
891
|
-
return String(err);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// src/runtime/supervise/budget.ts
|
|
895
|
-
function spendFromUsageEvents(events) {
|
|
896
|
-
const tokens = zeroTokenUsage();
|
|
897
|
-
let usd = 0;
|
|
898
|
-
let iterations = 0;
|
|
899
|
-
for (const ev of events) {
|
|
900
|
-
if (ev.kind === "tokens") {
|
|
901
|
-
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
902
|
-
} else if (ev.kind === "cost") {
|
|
903
|
-
usd += ev.usd;
|
|
904
|
-
} else {
|
|
905
|
-
iterations += 1;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
909
|
-
}
|
|
910
|
-
async function foldUsage(events) {
|
|
911
|
-
if (Array.isArray(events)) return spendFromUsageEvents(events);
|
|
912
|
-
const tokens = zeroTokenUsage();
|
|
913
|
-
let usd = 0;
|
|
914
|
-
let iterations = 0;
|
|
915
|
-
for await (const ev of events) {
|
|
916
|
-
if (ev.kind === "tokens") {
|
|
917
|
-
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
918
|
-
} else if (ev.kind === "cost") {
|
|
919
|
-
usd += ev.usd;
|
|
920
|
-
} else {
|
|
921
|
-
iterations += 1;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
925
|
-
}
|
|
926
|
-
function totalTokens(usage) {
|
|
927
|
-
return usage.input + usage.output;
|
|
928
|
-
}
|
|
929
|
-
function createBudgetPool(root, now = Date.now) {
|
|
930
|
-
let freeTokens = root.maxTokens;
|
|
931
|
-
let reservedTokens = 0;
|
|
932
|
-
let committedTokens = 0;
|
|
933
|
-
const usdCapped = root.maxUsd !== void 0;
|
|
934
|
-
let freeUsd = root.maxUsd ?? 0;
|
|
935
|
-
let reservedUsd = 0;
|
|
936
|
-
let committedUsd = 0;
|
|
937
|
-
let freeIterations = root.maxIterations;
|
|
938
|
-
let reservedIterations = 0;
|
|
939
|
-
let committedIterations = 0;
|
|
940
|
-
const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
|
|
941
|
-
let nextTicketId = 0;
|
|
942
|
-
const open = /* @__PURE__ */ new Set();
|
|
943
|
-
function reserve(b) {
|
|
944
|
-
const wantTokens = b.maxTokens;
|
|
945
|
-
const wantUsd = b.maxUsd ?? 0;
|
|
946
|
-
const wantIterations = b.maxIterations;
|
|
947
|
-
if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
|
|
948
|
-
if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
|
|
949
|
-
if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
|
|
950
|
-
return { ok: false, reason: "budget-exhausted" };
|
|
951
|
-
}
|
|
952
|
-
freeTokens -= wantTokens;
|
|
953
|
-
reservedTokens += wantTokens;
|
|
954
|
-
freeIterations -= wantIterations;
|
|
955
|
-
reservedIterations += wantIterations;
|
|
956
|
-
if (wantUsd > 0) {
|
|
957
|
-
freeUsd -= wantUsd;
|
|
958
|
-
reservedUsd += wantUsd;
|
|
959
|
-
}
|
|
960
|
-
const id = nextTicketId++;
|
|
961
|
-
open.add(id);
|
|
962
|
-
return {
|
|
963
|
-
ok: true,
|
|
964
|
-
ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
function reconcile(ticket, spent) {
|
|
968
|
-
if (!open.has(ticket.id)) {
|
|
969
|
-
throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
|
|
970
|
-
}
|
|
971
|
-
open.delete(ticket.id);
|
|
972
|
-
const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
|
|
973
|
-
const spentTokens = totalTokens(spent.tokens);
|
|
974
|
-
if (spentTokens > rTokens) {
|
|
975
|
-
throw new Error(
|
|
976
|
-
`budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
|
|
977
|
-
);
|
|
978
|
-
}
|
|
979
|
-
if (spent.iterations > rIterations) {
|
|
980
|
-
throw new Error(
|
|
981
|
-
`budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
if (usdCapped && spent.usd > rUsd) {
|
|
985
|
-
throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
|
|
986
|
-
}
|
|
987
|
-
reservedTokens -= rTokens;
|
|
988
|
-
committedTokens += spentTokens;
|
|
989
|
-
freeTokens += rTokens - spentTokens;
|
|
990
|
-
reservedIterations -= rIterations;
|
|
991
|
-
committedIterations += spent.iterations;
|
|
992
|
-
freeIterations += rIterations - spent.iterations;
|
|
993
|
-
if (usdCapped && rUsd > 0) {
|
|
994
|
-
reservedUsd -= rUsd;
|
|
995
|
-
committedUsd += spent.usd;
|
|
996
|
-
freeUsd += rUsd - spent.usd;
|
|
997
|
-
} else {
|
|
998
|
-
committedUsd += spent.usd;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
function observe(spend) {
|
|
1002
|
-
const tokens = totalTokens(spend.tokens);
|
|
1003
|
-
freeTokens -= tokens;
|
|
1004
|
-
committedTokens += tokens;
|
|
1005
|
-
freeIterations -= spend.iterations;
|
|
1006
|
-
committedIterations += spend.iterations;
|
|
1007
|
-
committedUsd += spend.usd;
|
|
1008
|
-
if (usdCapped) freeUsd -= spend.usd;
|
|
1009
|
-
}
|
|
1010
|
-
function readout() {
|
|
1011
|
-
return {
|
|
1012
|
-
tokensLeft: freeTokens,
|
|
1013
|
-
usdLeft: usdCapped ? freeUsd : 0,
|
|
1014
|
-
usdCapped,
|
|
1015
|
-
deadlineMs: absoluteDeadlineMs,
|
|
1016
|
-
reservedTokens
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
function assertNoOpenTickets() {
|
|
1020
|
-
if (open.size > 0) {
|
|
1021
|
-
throw new Error(
|
|
1022
|
-
`budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
return {
|
|
1027
|
-
reserve,
|
|
1028
|
-
reconcile,
|
|
1029
|
-
spendFrom: foldUsage,
|
|
1030
|
-
readout,
|
|
1031
|
-
observe,
|
|
1032
|
-
assertNoOpenTickets
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// src/runtime/supervise/supervisor.ts
|
|
1037
|
-
var defaultMaxDepth = 4;
|
|
1038
|
-
function createSupervisor() {
|
|
1039
|
-
let attached;
|
|
1040
|
-
async function run(root, task, opts) {
|
|
1041
|
-
const now = opts.now ?? Date.now;
|
|
1042
|
-
const pool = createBudgetPool(opts.budget, now);
|
|
1043
|
-
await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
|
|
1044
|
-
await opts.journal.appendEvent(opts.runId, {
|
|
1045
|
-
kind: "spawned",
|
|
1046
|
-
id: opts.runId,
|
|
1047
|
-
label: "root",
|
|
1048
|
-
budget: opts.budget,
|
|
1049
|
-
runtime: "inline",
|
|
1050
|
-
seq: 0,
|
|
1051
|
-
at: new Date(now()).toISOString()
|
|
1052
|
-
});
|
|
1053
|
-
const controller = new AbortController();
|
|
1054
|
-
const cascadeAbort = (reason) => {
|
|
1055
|
-
if (controller.signal.aborted) return;
|
|
1056
|
-
controller.abort(reason);
|
|
1057
|
-
};
|
|
1058
|
-
const onCallerAbort = () => cascadeAbort("caller signal aborted");
|
|
1059
|
-
if (opts.signal) {
|
|
1060
|
-
if (opts.signal.aborted) cascadeAbort("caller signal aborted");
|
|
1061
|
-
else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
1062
|
-
}
|
|
1063
|
-
const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
|
|
1064
|
-
const journal = wrapJournalForBreaker(opts.journal, breaker);
|
|
1065
|
-
const scope = createScope({
|
|
1066
|
-
parentId: opts.runId,
|
|
1067
|
-
root: opts.runId,
|
|
1068
|
-
pool,
|
|
1069
|
-
journal,
|
|
1070
|
-
blobs: opts.blobs,
|
|
1071
|
-
executors: opts.executors,
|
|
1072
|
-
seams: {},
|
|
1073
|
-
depth: 0,
|
|
1074
|
-
maxDepth: opts.maxDepth ?? defaultMaxDepth,
|
|
1075
|
-
signal: controller.signal,
|
|
1076
|
-
now,
|
|
1077
|
-
hooks: opts.hooks
|
|
1078
|
-
});
|
|
1079
|
-
const openScope = scope;
|
|
1080
|
-
if (attached) {
|
|
1081
|
-
attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
|
|
1082
|
-
}
|
|
1083
|
-
let actOutcome;
|
|
1084
|
-
try {
|
|
1085
|
-
const out = await root.act(task, scope);
|
|
1086
|
-
actOutcome = { ok: true, out };
|
|
1087
|
-
} catch (error) {
|
|
1088
|
-
actOutcome = { ok: false, error };
|
|
1089
|
-
} finally {
|
|
1090
|
-
await drainLiveChildren(openScope, controller);
|
|
1091
|
-
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
1092
|
-
if (attached) attached.unbind();
|
|
1093
|
-
}
|
|
1094
|
-
const tree = scope.view;
|
|
1095
|
-
if (actOutcome.ok) {
|
|
1096
|
-
pool.assertNoOpenTickets();
|
|
1097
|
-
const out = actOutcome.out;
|
|
1098
|
-
if (out !== void 0) {
|
|
1099
|
-
const outRef = contentAddress(out);
|
|
1100
|
-
await opts.blobs.put(outRef, out);
|
|
1101
|
-
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
1102
|
-
return {
|
|
1103
|
-
kind: "winner",
|
|
1104
|
-
out,
|
|
1105
|
-
outRef,
|
|
1106
|
-
tree,
|
|
1107
|
-
spentTotal: addSpend(childWork, driverInference),
|
|
1108
|
-
...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
return noWinner();
|
|
1112
|
-
}
|
|
1113
|
-
return noWinner();
|
|
1114
|
-
async function noWinner() {
|
|
1115
|
-
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
1116
|
-
return {
|
|
1117
|
-
kind: "no-winner",
|
|
1118
|
-
reason: classifyNoWinner(controller, pool, opts, breaker),
|
|
1119
|
-
tree,
|
|
1120
|
-
downCount: breaker.downCount(),
|
|
1121
|
-
spentTotal: addSpend(childWork, driverInference)
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
function attach(h) {
|
|
1126
|
-
const control = rootControls.get(h);
|
|
1127
|
-
if (!control) {
|
|
1128
|
-
throw new RuntimeRunStateError(
|
|
1129
|
-
"supervisor.attach: handle was not minted by createRootHandle (no control channel)"
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
attached = control;
|
|
1133
|
-
}
|
|
1134
|
-
return { run, attach };
|
|
1135
|
-
}
|
|
1136
|
-
var rootControls = /* @__PURE__ */ new WeakMap();
|
|
1137
|
-
function pushRootSignal(cascadeAbort) {
|
|
1138
|
-
return (msg) => {
|
|
1139
|
-
if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
function createIntensityBreaker(opts, trip) {
|
|
1143
|
-
const max = opts.maxRestarts;
|
|
1144
|
-
const within = opts.withinMs;
|
|
1145
|
-
const armed = max !== void 0 && within !== void 0;
|
|
1146
|
-
const recent = [];
|
|
1147
|
-
let total = 0;
|
|
1148
|
-
let isTripped = false;
|
|
1149
|
-
return {
|
|
1150
|
-
recordDown(at) {
|
|
1151
|
-
total += 1;
|
|
1152
|
-
if (!armed || isTripped) return;
|
|
1153
|
-
recent.push(at);
|
|
1154
|
-
const cutoff = at - within;
|
|
1155
|
-
while (recent.length > 0 && recent[0] < cutoff) recent.shift();
|
|
1156
|
-
if (recent.length > max) {
|
|
1157
|
-
isTripped = true;
|
|
1158
|
-
trip();
|
|
1159
|
-
}
|
|
1160
|
-
},
|
|
1161
|
-
tripped() {
|
|
1162
|
-
return isTripped;
|
|
1163
|
-
},
|
|
1164
|
-
downCount() {
|
|
1165
|
-
return total;
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
function wrapJournalForBreaker(journal, breaker) {
|
|
1170
|
-
return {
|
|
1171
|
-
loadTree: (root) => journal.loadTree(root),
|
|
1172
|
-
beginTree: (root, at) => journal.beginTree(root, at),
|
|
1173
|
-
appendEvent: (root, ev) => {
|
|
1174
|
-
if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
|
|
1175
|
-
return journal.appendEvent(root, ev);
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
async function drainLiveChildren(scope, controller) {
|
|
1180
|
-
const hasLive = scope.view.inFlight > 0;
|
|
1181
|
-
if (!hasLive) return;
|
|
1182
|
-
if (!controller.signal.aborted) controller.abort();
|
|
1183
|
-
await Promise.allSettled([drainCursor(scope)]);
|
|
1184
|
-
}
|
|
1185
|
-
async function drainCursor(scope) {
|
|
1186
|
-
for (; ; ) {
|
|
1187
|
-
const settled = await scope.next();
|
|
1188
|
-
if (settled === null) return;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
function classifyNoWinner(controller, pool, opts, breaker) {
|
|
1192
|
-
if (breaker.tripped()) return "all-children-down";
|
|
1193
|
-
if (controller.signal.aborted) return "aborted";
|
|
1194
|
-
if (poolExhausted(pool, opts)) return "budget-exhausted";
|
|
1195
|
-
return "all-children-down";
|
|
1196
|
-
}
|
|
1197
|
-
function poolExhausted(pool, opts) {
|
|
1198
|
-
const r = pool.readout();
|
|
1199
|
-
if (r.tokensLeft <= 0) return true;
|
|
1200
|
-
if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
|
|
1201
|
-
if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
|
|
1202
|
-
return true;
|
|
1203
|
-
}
|
|
1204
|
-
return false;
|
|
1205
|
-
}
|
|
1206
|
-
async function spentFromJournal(journal, root) {
|
|
1207
|
-
const events = await journal.loadTree(root);
|
|
1208
|
-
if (events === void 0) {
|
|
1209
|
-
throw new RuntimeRunStateError(
|
|
1210
|
-
`supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
1214
|
-
const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
1215
|
-
for (const ev of events) {
|
|
1216
|
-
if (ev.kind === "settled") accumulate(childWork, ev.spent);
|
|
1217
|
-
else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
|
|
1218
|
-
}
|
|
1219
|
-
return { childWork, driverInference };
|
|
1220
|
-
}
|
|
1221
|
-
function accumulate(a, b) {
|
|
1222
|
-
a.iterations += b.iterations;
|
|
1223
|
-
a.tokens.input += b.tokens.input;
|
|
1224
|
-
a.tokens.output += b.tokens.output;
|
|
1225
|
-
a.usd += b.usd;
|
|
1226
|
-
a.ms += b.ms;
|
|
1227
|
-
}
|
|
1228
|
-
function addSpend(a, b) {
|
|
1229
|
-
return {
|
|
1230
|
-
iterations: a.iterations + b.iterations,
|
|
1231
|
-
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
1232
|
-
usd: a.usd + b.usd,
|
|
1233
|
-
ms: a.ms + b.ms
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
function isNonEmptySpend(s) {
|
|
1237
|
-
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
50
|
// src/runtime/supervise/completion-gate.ts
|
|
1241
51
|
function gateOnDeliverable(inner, deliverable) {
|
|
1242
52
|
let gated;
|
|
@@ -1255,7 +65,7 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
1255
65
|
...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
|
|
1256
66
|
execute(task, signal) {
|
|
1257
67
|
const r = inner.execute(task, signal);
|
|
1258
|
-
if (
|
|
68
|
+
if (isAsyncIterable(r)) {
|
|
1259
69
|
return (async function* () {
|
|
1260
70
|
for await (const ev of r) yield ev;
|
|
1261
71
|
const art = inner.resultArtifact();
|
|
@@ -1275,7 +85,7 @@ function gateOnDeliverable(inner, deliverable) {
|
|
|
1275
85
|
}
|
|
1276
86
|
};
|
|
1277
87
|
}
|
|
1278
|
-
function
|
|
88
|
+
function isAsyncIterable(v) {
|
|
1279
89
|
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
1280
90
|
}
|
|
1281
91
|
|
|
@@ -2055,13 +865,13 @@ async function executeIteration(args) {
|
|
|
2055
865
|
await destroySandboxSafe(box, args.ctx.traceEmitter, args.runId, args.now);
|
|
2056
866
|
}
|
|
2057
867
|
}
|
|
2058
|
-
if (
|
|
868
|
+
if (isAbortError(slot.error) || args.signal.aborted) {
|
|
2059
869
|
if (slot.error) throw slot.error;
|
|
2060
870
|
throwAbort();
|
|
2061
871
|
}
|
|
2062
872
|
if (slot.error instanceof ValidationError) throw slot.error;
|
|
2063
873
|
}
|
|
2064
|
-
function
|
|
874
|
+
function isAbortError(err) {
|
|
2065
875
|
return err instanceof Error && err.name === "AbortError";
|
|
2066
876
|
}
|
|
2067
877
|
var TEARDOWN_TIMEOUT_MS2 = 15e3;
|
|
@@ -2575,7 +1385,7 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
2575
1385
|
import { request as httpRequest } from "http";
|
|
2576
1386
|
import { request as httpsRequest } from "https";
|
|
2577
1387
|
import { Readable } from "stream";
|
|
2578
|
-
import { estimateCost
|
|
1388
|
+
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
2579
1389
|
var routerSeamKey = "router";
|
|
2580
1390
|
var sandboxSeamKey = "sandbox";
|
|
2581
1391
|
var cliSeamKey = "cli";
|
|
@@ -2596,7 +1406,7 @@ function contentRef(prefix, value) {
|
|
|
2596
1406
|
}
|
|
2597
1407
|
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
2598
1408
|
}
|
|
2599
|
-
function
|
|
1409
|
+
function zeroSpend() {
|
|
2600
1410
|
return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
|
|
2601
1411
|
}
|
|
2602
1412
|
var routerInlineExecutor = (spec, ctx) => {
|
|
@@ -2790,7 +1600,7 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
2790
1600
|
}
|
|
2791
1601
|
}
|
|
2792
1602
|
}
|
|
2793
|
-
const usd =
|
|
1603
|
+
const usd = isModelPriced(model) ? estimateCost(tokens.input, tokens.output, model) : 0;
|
|
2794
1604
|
const spent = { iterations: turns, tokens, usd, ms: Date.now() - started };
|
|
2795
1605
|
const out = { content: lastText };
|
|
2796
1606
|
artifact = { outRef: contentRef("router-tools", { model, content: lastText }), out, spent };
|
|
@@ -3002,7 +1812,7 @@ async function* streamCliLeaf(args) {
|
|
|
3002
1812
|
);
|
|
3003
1813
|
}
|
|
3004
1814
|
const out = { content: chunks.join("") };
|
|
3005
|
-
args.onArtifact({ outRef: contentRef("cli", out), out, spent:
|
|
1815
|
+
args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend() });
|
|
3006
1816
|
yield { kind: "iteration" };
|
|
3007
1817
|
}
|
|
3008
1818
|
function killWithGrace(proc, grace) {
|
|
@@ -3390,7 +2200,7 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
3390
2200
|
inner = bridgeExecutor(spec, bridgeCtx);
|
|
3391
2201
|
for (const msg of pending.splice(0)) inner.deliver?.(msg);
|
|
3392
2202
|
const run = inner.execute(seam.taskPrompt, linked);
|
|
3393
|
-
if (
|
|
2203
|
+
if (isAsyncIterable2(run)) {
|
|
3394
2204
|
for await (const event of run) yield event;
|
|
3395
2205
|
bridgeArtifact = inner.resultArtifact();
|
|
3396
2206
|
} else {
|
|
@@ -3483,7 +2293,7 @@ function bridgeOutputText(out) {
|
|
|
3483
2293
|
return String(out);
|
|
3484
2294
|
}
|
|
3485
2295
|
}
|
|
3486
|
-
function
|
|
2296
|
+
function isAsyncIterable2(value) {
|
|
3487
2297
|
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
3488
2298
|
}
|
|
3489
2299
|
var cliWorktreeExecutor = (spec, ctx) => {
|
|
@@ -3641,179 +2451,6 @@ function mergeAbortSignals(...signals) {
|
|
|
3641
2451
|
return c.signal;
|
|
3642
2452
|
}
|
|
3643
2453
|
|
|
3644
|
-
// src/runtime/supervise/driver-executor.ts
|
|
3645
|
-
var driverRuntime = "driver";
|
|
3646
|
-
var driverRole = "driver";
|
|
3647
|
-
function isDriverSpec(spec) {
|
|
3648
|
-
const role = spec.profile.metadata?.role;
|
|
3649
|
-
if (role !== driverRole) return false;
|
|
3650
|
-
const driver = spec.driver;
|
|
3651
|
-
if (!isAgent(driver)) {
|
|
3652
|
-
throw new ValidationError(
|
|
3653
|
-
"driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
|
|
3654
|
-
);
|
|
3655
|
-
}
|
|
3656
|
-
return true;
|
|
3657
|
-
}
|
|
3658
|
-
var driverExecutorFactory = (spec, ctx) => {
|
|
3659
|
-
if (!isDriverSpec(spec)) {
|
|
3660
|
-
throw new ValidationError(
|
|
3661
|
-
'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
|
|
3662
|
-
);
|
|
3663
|
-
}
|
|
3664
|
-
const driver = spec.driver;
|
|
3665
|
-
const journal = spec.journal;
|
|
3666
|
-
const seam = readNestedScopeSeam(ctx);
|
|
3667
|
-
let artifact;
|
|
3668
|
-
let meteredSpend;
|
|
3669
|
-
return {
|
|
3670
|
-
runtime: driverRuntime,
|
|
3671
|
-
async execute(task, signal) {
|
|
3672
|
-
const nestedRoot = nestedTreeKey(seam, journal);
|
|
3673
|
-
await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3674
|
-
const nestedScope = seam.mount(nestedRoot, signal);
|
|
3675
|
-
try {
|
|
3676
|
-
const out = await driver.act(task, nestedScope);
|
|
3677
|
-
const events = await loadTreeEvents(journal, nestedRoot);
|
|
3678
|
-
const settled = events.filter(isSettled);
|
|
3679
|
-
meteredSpend = nonZeroOrUndef(sumMetered(events));
|
|
3680
|
-
const verdict = deriveDeliveryVerdict(settled);
|
|
3681
|
-
artifact = {
|
|
3682
|
-
outRef: `${driverRuntime}:${nestedRoot}`,
|
|
3683
|
-
out,
|
|
3684
|
-
spent: sumSpend(settled),
|
|
3685
|
-
...verdict ? { verdict } : {}
|
|
3686
|
-
};
|
|
3687
|
-
return artifact;
|
|
3688
|
-
} catch (err) {
|
|
3689
|
-
meteredSpend = await safeSumMetered(journal, nestedRoot);
|
|
3690
|
-
throw err;
|
|
3691
|
-
}
|
|
3692
|
-
},
|
|
3693
|
-
metered() {
|
|
3694
|
-
return meteredSpend;
|
|
3695
|
-
},
|
|
3696
|
-
teardown() {
|
|
3697
|
-
return Promise.resolve({ destroyed: true });
|
|
3698
|
-
},
|
|
3699
|
-
resultArtifact() {
|
|
3700
|
-
if (!artifact) {
|
|
3701
|
-
throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
|
|
3702
|
-
}
|
|
3703
|
-
return artifact;
|
|
3704
|
-
}
|
|
3705
|
-
};
|
|
3706
|
-
};
|
|
3707
|
-
function withDriverExecutor(base) {
|
|
3708
|
-
return {
|
|
3709
|
-
register: base.register.bind(base),
|
|
3710
|
-
resolve(spec) {
|
|
3711
|
-
const role = spec.profile.metadata?.role;
|
|
3712
|
-
if (role === driverRole && !spec.executor) {
|
|
3713
|
-
return { succeeded: true, value: driverExecutorFactory };
|
|
3714
|
-
}
|
|
3715
|
-
return base.resolve(spec);
|
|
3716
|
-
}
|
|
3717
|
-
};
|
|
3718
|
-
}
|
|
3719
|
-
function nestedTreeKey(seam, journal) {
|
|
3720
|
-
return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
|
|
3721
|
-
}
|
|
3722
|
-
var nestCounters = /* @__PURE__ */ new WeakMap();
|
|
3723
|
-
function nextNestOrdinal(journal) {
|
|
3724
|
-
let c = nestCounters.get(journal);
|
|
3725
|
-
if (!c) {
|
|
3726
|
-
c = { n: 0 };
|
|
3727
|
-
nestCounters.set(journal, c);
|
|
3728
|
-
}
|
|
3729
|
-
return c.n++;
|
|
3730
|
-
}
|
|
3731
|
-
async function loadTreeEvents(journal, nestedRoot) {
|
|
3732
|
-
const events = await journal.loadTree(nestedRoot);
|
|
3733
|
-
if (events === void 0) {
|
|
3734
|
-
throw new ValidationError(
|
|
3735
|
-
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3736
|
-
);
|
|
3737
|
-
}
|
|
3738
|
-
return events;
|
|
3739
|
-
}
|
|
3740
|
-
function isSettled(ev) {
|
|
3741
|
-
return ev.kind === "settled";
|
|
3742
|
-
}
|
|
3743
|
-
function sumSpend(settled) {
|
|
3744
|
-
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3745
|
-
for (const ev of settled) {
|
|
3746
|
-
total.iterations += ev.spent.iterations;
|
|
3747
|
-
total.tokens.input += ev.spent.tokens.input;
|
|
3748
|
-
total.tokens.output += ev.spent.tokens.output;
|
|
3749
|
-
total.usd += ev.spent.usd;
|
|
3750
|
-
total.ms += ev.spent.ms;
|
|
3751
|
-
}
|
|
3752
|
-
return total;
|
|
3753
|
-
}
|
|
3754
|
-
function sumMetered(events) {
|
|
3755
|
-
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3756
|
-
for (const ev of events) {
|
|
3757
|
-
if (ev.kind !== "metered") continue;
|
|
3758
|
-
total.iterations += ev.spend.iterations;
|
|
3759
|
-
total.tokens.input += ev.spend.tokens.input;
|
|
3760
|
-
total.tokens.output += ev.spend.tokens.output;
|
|
3761
|
-
total.usd += ev.spend.usd;
|
|
3762
|
-
total.ms += ev.spend.ms;
|
|
3763
|
-
}
|
|
3764
|
-
return total;
|
|
3765
|
-
}
|
|
3766
|
-
function isNonZeroSpend(s) {
|
|
3767
|
-
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
3768
|
-
}
|
|
3769
|
-
function nonZeroOrUndef(s) {
|
|
3770
|
-
return isNonZeroSpend(s) ? s : void 0;
|
|
3771
|
-
}
|
|
3772
|
-
async function safeSumMetered(journal, nestedRoot) {
|
|
3773
|
-
try {
|
|
3774
|
-
return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
|
|
3775
|
-
} catch {
|
|
3776
|
-
return void 0;
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
function deriveDeliveryVerdict(settled) {
|
|
3780
|
-
let sawChild = false;
|
|
3781
|
-
let anyValid = false;
|
|
3782
|
-
let bestValidScore;
|
|
3783
|
-
let bestDoneScore;
|
|
3784
|
-
for (const ev of settled) {
|
|
3785
|
-
sawChild = true;
|
|
3786
|
-
if (ev.status !== "done") continue;
|
|
3787
|
-
const score = ev.verdict?.score;
|
|
3788
|
-
if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
|
|
3789
|
-
bestDoneScore = score;
|
|
3790
|
-
}
|
|
3791
|
-
if (ev.verdict?.valid === true) {
|
|
3792
|
-
anyValid = true;
|
|
3793
|
-
if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
|
|
3794
|
-
bestValidScore = score;
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
if (!sawChild) return void 0;
|
|
3799
|
-
return {
|
|
3800
|
-
valid: anyValid,
|
|
3801
|
-
score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
|
|
3802
|
-
};
|
|
3803
|
-
}
|
|
3804
|
-
function readNestedScopeSeam(ctx) {
|
|
3805
|
-
const seam = ctx.seams[nestedScopeSeamKey];
|
|
3806
|
-
if (!seam || typeof seam.mount !== "function") {
|
|
3807
|
-
throw new ValidationError(
|
|
3808
|
-
`driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
|
|
3809
|
-
);
|
|
3810
|
-
}
|
|
3811
|
-
return seam;
|
|
3812
|
-
}
|
|
3813
|
-
function isAgent(value) {
|
|
3814
|
-
return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
|
|
3815
|
-
}
|
|
3816
|
-
|
|
3817
2454
|
// src/runtime/supervise/run-context.ts
|
|
3818
2455
|
function createInMemoryRunContext(opts = {}) {
|
|
3819
2456
|
const base = createExecutorRegistry();
|
|
@@ -4480,7 +3117,7 @@ ${narrative}` : roster;
|
|
|
4480
3117
|
return `${roster}
|
|
4481
3118
|
|
|
4482
3119
|
## Progress notes
|
|
4483
|
-
Summary unavailable: ${
|
|
3120
|
+
Summary unavailable: ${errMessage(e)}`;
|
|
4484
3121
|
}
|
|
4485
3122
|
}),
|
|
4486
3123
|
...opts.compaction.onCompact ? { onCompact: opts.compaction.onCompact } : {},
|
|
@@ -4551,7 +3188,7 @@ function safeJson(v) {
|
|
|
4551
3188
|
return String(v);
|
|
4552
3189
|
}
|
|
4553
3190
|
}
|
|
4554
|
-
function
|
|
3191
|
+
function errMessage(e) {
|
|
4555
3192
|
return e instanceof Error ? e.message : String(e);
|
|
4556
3193
|
}
|
|
4557
3194
|
|
|
@@ -6551,24 +5188,6 @@ async function delegate(intent, opts = {}) {
|
|
|
6551
5188
|
}
|
|
6552
5189
|
|
|
6553
5190
|
export {
|
|
6554
|
-
contentAddress,
|
|
6555
|
-
InMemoryResultBlobStore,
|
|
6556
|
-
InMemorySpawnJournal,
|
|
6557
|
-
assertModelAllowed,
|
|
6558
|
-
routerChatWithUsage,
|
|
6559
|
-
routerChatWithTools,
|
|
6560
|
-
routerToolLoop,
|
|
6561
|
-
routerBrain,
|
|
6562
|
-
defineRuntimeHooks,
|
|
6563
|
-
composeRuntimeHooks,
|
|
6564
|
-
notifyRuntimeHookEvent,
|
|
6565
|
-
notifyRuntimeDecisionPoint,
|
|
6566
|
-
createScope,
|
|
6567
|
-
settledToIteration,
|
|
6568
|
-
withDriverExecutor,
|
|
6569
|
-
spendFromUsageEvents,
|
|
6570
|
-
createBudgetPool,
|
|
6571
|
-
createSupervisor,
|
|
6572
5191
|
gateOnDeliverable,
|
|
6573
5192
|
createWorktree,
|
|
6574
5193
|
captureWorktreeDiff,
|
|
@@ -6644,4 +5263,4 @@ export {
|
|
|
6644
5263
|
workerFromBackend,
|
|
6645
5264
|
supervise
|
|
6646
5265
|
};
|
|
6647
|
-
//# sourceMappingURL=chunk-
|
|
5266
|
+
//# sourceMappingURL=chunk-6O5USWVH.js.map
|