@tangle-network/agent-runtime 0.113.0 → 0.114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{activation-DGozpA_7.js → activation-DlKl7-1o.js} +3 -3
  2. package/dist/{activation-DGozpA_7.js.map → activation-DlKl7-1o.js.map} +1 -1
  3. package/dist/agent.d.ts +18 -7
  4. package/dist/agent.js +123 -64
  5. package/dist/agent.js.map +1 -1
  6. package/dist/candidate-execution/index.js +4 -4
  7. package/dist/{candidate-execution-Co3sRC_7.js → candidate-execution-CfpJrd3o.js} +4 -4
  8. package/dist/{candidate-execution-Co3sRC_7.js.map → candidate-execution-CfpJrd3o.js.map} +1 -1
  9. package/dist/{environment-provider-BsHpW4Dp.js → environment-provider-CCaEhA-l.js} +38 -4
  10. package/dist/environment-provider-CCaEhA-l.js.map +1 -0
  11. package/dist/{environment-provider-DG-cSx89.d.ts → environment-provider-Cygthiy3.d.ts} +40 -1
  12. package/dist/environment-provider.d.ts +1 -1
  13. package/dist/environment-provider.js +1 -1
  14. package/dist/{improvement-cycle-DxBZsG33.js → improvement-cycle-vLye8pYb.js} +4 -4
  15. package/dist/{improvement-cycle-DxBZsG33.js.map → improvement-cycle-vLye8pYb.js.map} +1 -1
  16. package/dist/{index-DmXPCQ2X.d.ts → index-BLf-SZ2b.d.ts} +6 -6
  17. package/dist/{index-BdnhmGUH.d.ts → index-D-lNUfea.d.ts} +147 -9
  18. package/dist/{index-B-DkjCqQ.d.ts → index-YpvLKzIX.d.ts} +3 -3
  19. package/dist/index.d.ts +7 -7
  20. package/dist/index.js +11 -11
  21. package/dist/intelligence.d.ts +3 -3
  22. package/dist/intelligence.js +5 -5
  23. package/dist/kernel.d.ts +4 -4
  24. package/dist/kernel.js +7 -7
  25. package/dist/{knowledge-h7sDHsnd.js → knowledge-BTsA9n6L.js} +5 -5
  26. package/dist/{knowledge-h7sDHsnd.js.map → knowledge-BTsA9n6L.js.map} +1 -1
  27. package/dist/knowledge.d.ts +1 -1
  28. package/dist/knowledge.js +1 -1
  29. package/dist/{local-harness-Dh8PJ0ot.d.ts → local-harness-BnB1E9nM.d.ts} +92 -2
  30. package/dist/{loop-runner-bin-DN8491Y_.d.ts → loop-runner-bin-Cg_8UKZR.d.ts} +3 -3
  31. package/dist/{loop-runner-bin-D1TTr39X.js → loop-runner-bin-CuBMmlMJ.js} +3 -3
  32. package/dist/{loop-runner-bin-D1TTr39X.js.map → loop-runner-bin-CuBMmlMJ.js.map} +1 -1
  33. package/dist/loop-runner-bin.d.ts +1 -1
  34. package/dist/loop-runner-bin.js +1 -1
  35. package/dist/mcp/bin.js +1 -1
  36. package/dist/mcp/index.d.ts +2 -2
  37. package/dist/mcp/index.js +4 -4
  38. package/dist/{openai-tools-B1lRpTZr.js → openai-tools-VAbzps5G.js} +2 -2
  39. package/dist/{openai-tools-B1lRpTZr.js.map → openai-tools-VAbzps5G.js.map} +1 -1
  40. package/dist/{prepare-BKVdOn0w.js → prepare-BHQBb02e.js} +2 -2
  41. package/dist/{prepare-BKVdOn0w.js.map → prepare-BHQBb02e.js.map} +1 -1
  42. package/dist/primeintellect/index.d.ts +1 -1
  43. package/dist/{protected-model-port-BtldAE9j.js → protected-model-port-DqAH1Z2M.js} +2 -2
  44. package/dist/{protected-model-port-BtldAE9j.js.map → protected-model-port-DqAH1Z2M.js.map} +1 -1
  45. package/dist/{redact-BkasKlyd.d.ts → redact-BRaHUFMf.d.ts} +2 -2
  46. package/dist/{runtime-Cs2e63LL.js → runtime-DQ5seHEu.js} +8 -5
  47. package/dist/runtime-DQ5seHEu.js.map +1 -0
  48. package/dist/{spawn-journal-DLhnom3e.js → spawn-journal-DCPbicXB.js} +2 -1
  49. package/dist/{spawn-journal-DLhnom3e.js.map → spawn-journal-DCPbicXB.js.map} +1 -1
  50. package/dist/{structural-rollout-DXmSoA9q.js → structural-rollout-C7vF83fV.js} +3 -3
  51. package/dist/{structural-rollout-DXmSoA9q.js.map → structural-rollout-C7vF83fV.js.map} +1 -1
  52. package/dist/{supervise-S7glOjJH.js → supervise-tdxMJYem.js} +476 -31
  53. package/dist/supervise-tdxMJYem.js.map +1 -0
  54. package/dist/{supervisor-zebR4ICs.js → supervisor-DKqolBGI.js} +234 -23
  55. package/dist/supervisor-DKqolBGI.js.map +1 -0
  56. package/dist/testing.js +9 -9
  57. package/dist/{workspace-archive-CYYxZyA7.js → workspace-archive-DXzJq7WP.js} +2 -2
  58. package/dist/{workspace-archive-CYYxZyA7.js.map → workspace-archive-DXzJq7WP.js.map} +1 -1
  59. package/package.json +1 -1
  60. package/dist/environment-provider-BsHpW4Dp.js.map +0 -1
  61. package/dist/runtime-Cs2e63LL.js.map +0 -1
  62. package/dist/supervise-S7glOjJH.js.map +0 -1
  63. package/dist/supervisor-zebR4ICs.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { c as RuntimeRunStateError, u as ValidationError } from "./errors-DEAvWQPy.js";
2
2
  import { n as addTokenUsage, p as zeroTokenUsage } from "./util-Cc9g9Y-o.js";
3
- import { a as contentAddress, c as replaySpawnTree, o as materializeTreeView, s as pendingWaits } from "./spawn-journal-DLhnom3e.js";
3
+ import { a as contentAddress, c as replaySpawnTree, o as materializeTreeView, s as pendingWaits } from "./spawn-journal-DCPbicXB.js";
4
4
  import { i as notifyRuntimeHookEvent } from "./runtime-hooks-C7iJOWm3.js";
5
5
  import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
6
6
  import { createHash, randomUUID } from "node:crypto";
@@ -1917,14 +1917,7 @@ function splitReasoning(rawContent, fieldReasoning) {
1917
1917
  * `routerChatWithUsage`. `tool_choice: 'auto'` lets the model decide; the driver loops on the result.
1918
1918
  */
1919
1919
  async function routerChatWithTools(cfg, messages, tools, opts) {
1920
- const body = {
1921
- model: cfg.model,
1922
- messages,
1923
- tools,
1924
- tool_choice: opts?.toolChoice ?? "auto",
1925
- temperature: opts?.temperature ?? .3,
1926
- ...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
1927
- };
1920
+ const body = toolCompletionBody(cfg, messages, tools, opts);
1928
1921
  const data = cfg.complete ? await cfg.complete(body) : await (async () => {
1929
1922
  const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
1930
1923
  method: "POST",
@@ -1944,12 +1937,7 @@ async function routerChatWithTools(cfg, messages, tools, opts) {
1944
1937
  name: tc.function?.name ?? "",
1945
1938
  arguments: tc.function?.arguments ?? "{}"
1946
1939
  }));
1947
- const u = data.usage;
1948
- const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? {
1949
- input: u.prompt_tokens,
1950
- output: u.completion_tokens
1951
- } : void 0;
1952
- const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
1940
+ const { usage, costUsd } = meterTurn(data.usage, cfg.model);
1953
1941
  return {
1954
1942
  content: msg?.content ?? null,
1955
1943
  toolCalls,
@@ -1957,6 +1945,204 @@ async function routerChatWithTools(cfg, messages, tools, opts) {
1957
1945
  ...costUsd !== void 0 ? { costUsd } : {}
1958
1946
  };
1959
1947
  }
1948
+ /** The OpenAI-shape request body both tool-calling transports send. One builder so the streamed
1949
+ * turn cannot drift from the buffered one on model, temperature, tool_choice, or the ceiling. */
1950
+ function toolCompletionBody(cfg, messages, tools, opts) {
1951
+ return {
1952
+ model: cfg.model,
1953
+ messages,
1954
+ tools,
1955
+ tool_choice: opts?.toolChoice ?? "auto",
1956
+ temperature: opts?.temperature ?? .3,
1957
+ ...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
1958
+ };
1959
+ }
1960
+ /**
1961
+ * REAL usage → the metered pair, or `undefined` when the provider reported none. Never a
1962
+ * fabricated 0: a phantom 0 reads as a free call to the conserved budget pool, which would then
1963
+ * over-spend. Shared by the buffered and streamed transports so both meter identically.
1964
+ */
1965
+ function meterTurn(raw, model) {
1966
+ const usage = raw && typeof raw.prompt_tokens === "number" && typeof raw.completion_tokens === "number" ? {
1967
+ input: raw.prompt_tokens,
1968
+ output: raw.completion_tokens
1969
+ } : void 0;
1970
+ if (!usage) return {};
1971
+ const costUsd = isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
1972
+ return {
1973
+ usage,
1974
+ ...costUsd !== void 0 ? { costUsd } : {}
1975
+ };
1976
+ }
1977
+ /**
1978
+ * The SAME completion as `routerChatWithTools`, taken over SSE (`stream: true`) and reassembled
1979
+ * into the identical `RouterChatToolsResult`. Opt in with `RouterConfig.stream` — the buffered
1980
+ * function is untouched and stays the default for every existing caller.
1981
+ *
1982
+ * What it buys: a buffered POST holds one connection idle for the whole completion, and that idle
1983
+ * window is what an intermediary gateway kills (524/503). Streaming puts bytes on the wire from the
1984
+ * first generated token, so the connection is only idle through prefill.
1985
+ *
1986
+ * Usage accounting is preserved exactly: `stream_options.include_usage` asks the provider for a
1987
+ * terminal usage chunk, and those tokens run through the same `meterTurn` the buffered path uses.
1988
+ *
1989
+ * When NO chunk reported usage, `usage`/`costUsd` stay undefined (never a fabricated 0) AND
1990
+ * `usageUnknown: true` is set. A stream that finishes with no usage chunk means the
1991
+ * `include_usage` request was not honored upstream, and returning a quiet `undefined` for it is
1992
+ * indistinguishable from a free turn — the marker is what lets a metering caller record an UNKNOWN
1993
+ * turn instead. Streaming raises the odds of this (one dropped terminal frame is enough), which is
1994
+ * why the streamed transport says so explicitly and the buffered one has no equivalent claim to make.
1995
+ */
1996
+ async function streamRouterChatWithTools(cfg, messages, tools, opts) {
1997
+ if (cfg.complete) throw new ValidationError("streamRouterChatWithTools: RouterConfig.complete is a BUFFERED transport (it returns one parsed completion body) and cannot serve a stream. Drop `stream` to use the injected transport, or drop `complete` to stream from the router.");
1998
+ const body = {
1999
+ ...toolCompletionBody(cfg, messages, tools, opts),
2000
+ stream: true,
2001
+ stream_options: { include_usage: true }
2002
+ };
2003
+ const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
2004
+ method: "POST",
2005
+ headers: {
2006
+ "content-type": "application/json",
2007
+ authorization: `Bearer ${cfg.routerKey}`,
2008
+ accept: "text/event-stream"
2009
+ },
2010
+ body: JSON.stringify(body),
2011
+ ...opts?.signal ? { signal: opts.signal } : {}
2012
+ });
2013
+ if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
2014
+ if (!res.body) throw new ValidationError(`router ${res.status}: streamed completion returned no response body to read`);
2015
+ let content = "";
2016
+ let sawContent = false;
2017
+ let fieldReasoning = "";
2018
+ let finishReason;
2019
+ let rawUsage;
2020
+ const calls = /* @__PURE__ */ new Map();
2021
+ let lastCallIndex = -1;
2022
+ for await (const chunk of readChatCompletionChunks(res.body)) {
2023
+ if (chunk.error) throw new ValidationError(`router stream error: ${chunk.error.message ?? chunk.error.type ?? "unknown"}`);
2024
+ if (chunk.usage) rawUsage = chunk.usage;
2025
+ const choice = chunk.choices?.[0];
2026
+ if (!choice) continue;
2027
+ if (typeof choice.finish_reason === "string") finishReason = choice.finish_reason;
2028
+ const delta = choice.delta;
2029
+ if (!delta) continue;
2030
+ if (typeof delta.content === "string") {
2031
+ content += delta.content;
2032
+ sawContent = true;
2033
+ }
2034
+ const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
2035
+ if (typeof reasoningDelta === "string") fieldReasoning += reasoningDelta;
2036
+ for (const tc of delta.tool_calls ?? []) {
2037
+ const index = typeof tc.index === "number" ? tc.index : tc.id && !idAlreadyOpen(calls, tc.id) ? lastCallIndex + 1 : Math.max(lastCallIndex, 0);
2038
+ lastCallIndex = Math.max(lastCallIndex, index);
2039
+ const acc = calls.get(index) ?? { arguments: "" };
2040
+ if (tc.id) acc.id = tc.id;
2041
+ if (tc.function?.name) acc.name = tc.function.name;
2042
+ if (tc.function?.arguments) acc.arguments += tc.function.arguments;
2043
+ calls.set(index, acc);
2044
+ }
2045
+ }
2046
+ const split = splitReasoning(content, fieldReasoning.length > 0 ? fieldReasoning : void 0);
2047
+ const toolCalls = [...calls.entries()].sort((a, b) => a[0] - b[0]).map(([index, call]) => ({
2048
+ id: call.id ?? `call_${index}`,
2049
+ name: call.name ?? "",
2050
+ arguments: call.arguments || "{}"
2051
+ }));
2052
+ const { usage, costUsd } = meterTurn(rawUsage, cfg.model);
2053
+ return {
2054
+ content: sawContent ? split.content : null,
2055
+ toolCalls,
2056
+ ...split.reasoning ? { reasoning: split.reasoning } : {},
2057
+ ...finishReason !== void 0 ? { finishReason } : {},
2058
+ ...usage ? { usage } : {},
2059
+ ...costUsd !== void 0 ? { costUsd } : {},
2060
+ ...usage ? {} : { usageUnknown: true }
2061
+ };
2062
+ }
2063
+ function idAlreadyOpen(calls, id) {
2064
+ for (const call of calls.values()) if (call.id === id) return true;
2065
+ return false;
2066
+ }
2067
+ /**
2068
+ * Decode an SSE body into chat-completion chunks. Frames are blank-line separated; `data:` lines
2069
+ * concatenate; `:` comments are keepalives; `[DONE]` ends the stream. The unterminated tail is
2070
+ * parsed too — an upstream that dies mid-response routinely sends a last frame with no trailing
2071
+ * blank line, and dropping it would turn a real upstream error into a silent empty turn.
2072
+ *
2073
+ * All three SSE line terminators (CRLF, LF, lone CR) are normalized, and the boundary scan runs
2074
+ * over the ACCUMULATED buffer — never over a single read in isolation, because a read may split a
2075
+ * CRLF. See `normalizeSseLineEndings`; the split is exercised byte-by-byte by the tests.
2076
+ *
2077
+ * The generator OWNS the body for its lifetime and releases it on every exit. Stopping early —
2078
+ * at `[DONE]`, or because the consumer threw — leaves the HTTP response only partly read, so the
2079
+ * body is CANCELLED rather than merely unlocked: releasing the lock alone leaves the connection
2080
+ * held open until the socket times out, which under a connection pool starves later turns.
2081
+ */
2082
+ async function* readChatCompletionChunks(body) {
2083
+ const reader = body.getReader();
2084
+ const decoder = new TextDecoder();
2085
+ let buf = "";
2086
+ let drained = false;
2087
+ try {
2088
+ for (;;) {
2089
+ const { done, value } = await reader.read();
2090
+ buf = normalizeSseLineEndings(done ? buf : buf + decoder.decode(value, { stream: true }), !done);
2091
+ let sep = buf.indexOf("\n\n");
2092
+ while (sep !== -1) {
2093
+ const frame = parseChunkFrame(buf.slice(0, sep));
2094
+ buf = buf.slice(sep + 2);
2095
+ if (frame === "done") return;
2096
+ if (frame) yield frame;
2097
+ sep = buf.indexOf("\n\n");
2098
+ }
2099
+ if (done) break;
2100
+ }
2101
+ drained = true;
2102
+ const tail = parseChunkFrame(buf);
2103
+ if (tail && tail !== "done") yield tail;
2104
+ } finally {
2105
+ if (!drained) await reader.cancel().catch(() => void 0);
2106
+ reader.releaseLock();
2107
+ }
2108
+ }
2109
+ /**
2110
+ * Normalize an SSE body's line terminators to `\n`. The SSE grammar accepts CRLF, LF, AND a lone
2111
+ * CR, so a `\r`-separated body is legal — and left unnormalized it decodes to zero frames, which
2112
+ * this transport would report as a turn with no content, no tool calls, and `usageUnknown: true`
2113
+ * and no error at all. Losing a whole turn in silence is the one outcome it may not produce.
2114
+ *
2115
+ * `holdTrailingCr` keeps an ambiguous FINAL `\r` out of the conversion while more bytes may still
2116
+ * arrive: rewriting it to `\n` before the next byte is seen turns a read-split CRLF into a FALSE
2117
+ * frame boundary, splitting one frame into two unparseable halves. At EOF nothing can follow it,
2118
+ * so it is converted. Re-running this over the accumulated buffer is idempotent.
2119
+ */
2120
+ function normalizeSseLineEndings(buf, holdTrailingCr) {
2121
+ if (holdTrailingCr && buf.endsWith("\r")) return `${buf.slice(0, -1).replace(/\r\n?/g, "\n")}\r`;
2122
+ return buf.replace(/\r\n?/g, "\n");
2123
+ }
2124
+ /** One SSE frame → a chunk, `'done'`, or undefined for a comment/keepalive/unparseable payload. */
2125
+ function parseChunkFrame(frame) {
2126
+ const dataLines = [];
2127
+ for (const line of frame.split("\n")) {
2128
+ if (!line || line.startsWith(":")) continue;
2129
+ if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
2130
+ }
2131
+ if (dataLines.length === 0) return void 0;
2132
+ const data = dataLines.join("\n");
2133
+ if (data === "[DONE]") return "done";
2134
+ try {
2135
+ return JSON.parse(data);
2136
+ } catch {
2137
+ return;
2138
+ }
2139
+ }
2140
+ /** Pick the tool-calling transport the config asked for. `routerChatWithTools` itself never
2141
+ * streams; this is the one place `RouterConfig.stream` is honored, so every router-client entry
2142
+ * point (`routerBrain`, `routerToolLoop`) obeys the same switch. */
2143
+ function chatWithTools(cfg, messages, tools, opts) {
2144
+ return cfg.stream === true ? streamRouterChatWithTools(cfg, messages, tools, opts) : routerChatWithTools(cfg, messages, tools, opts);
2145
+ }
1960
2146
  /**
1961
2147
  * The tool-using router backend: a real agentic loop OVER the Tangle router (which
1962
2148
  * supports tool-calling), off-box — no sandbox. Each turn is one router completion
@@ -1970,7 +2156,7 @@ async function routerChatWithTools(cfg, messages, tools, opts) {
1970
2156
  */
1971
2157
  async function routerToolLoop(cfg, system, user, tools, execute, opts) {
1972
2158
  return runBrainLoop({
1973
- chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
2159
+ chat: (messages, toolSpecs) => chatWithTools(cfg, messages, toolSpecs, {
1974
2160
  ...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
1975
2161
  ...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
1976
2162
  ...opts?.signal ? { signal: opts.signal } : {}
@@ -1992,12 +2178,16 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
1992
2178
  * tool-calling. The driver's spawn/observe/steer/await/stop turns become real router tool-calls.
1993
2179
  * The turnkey production brain — tests script a mock `ToolLoopChat`; production passes
1994
2180
  * `routerBrain(cfg)`. No message translation: the loop already speaks the router's OpenAI shape.
2181
+ *
2182
+ * Transport follows `cfg.stream`: buffered by default, SSE when the caller opts in. A supervisor
2183
+ * turn is the longest completion in the system, so it is the call site streaming exists for.
1995
2184
  */
1996
2185
  function routerBrain(cfg, opts = {}) {
1997
2186
  const temperature = opts.temperature ?? .4;
1998
- return (messages, tools) => routerChatWithTools(cfg, messages, tools, {
2187
+ return (messages, tools) => chatWithTools(cfg, messages, tools, {
1999
2188
  temperature,
2000
- toolChoice: "auto"
2189
+ toolChoice: "auto",
2190
+ ...cfg.maxTokens !== void 0 ? { maxTokens: cfg.maxTokens } : {}
2001
2191
  });
2002
2192
  }
2003
2193
  //#endregion
@@ -2040,10 +2230,12 @@ function readWorkerProgress(scope, executor, now, stallAfterMs = DEFAULT_STALL_A
2040
2230
  stallAfterMs,
2041
2231
  turns: executor?.turns ?? scope.turns,
2042
2232
  tokens: scope.tokens,
2233
+ ...scope.tokensKnown === false ? { tokensKnown: false } : {},
2043
2234
  usd: scope.usd,
2044
2235
  ...scope.usdKnown === false ? { usdKnown: false } : {},
2045
2236
  pendingMessages: executor?.pendingMessages ?? 0,
2046
2237
  recentActivity: executor?.recentActivity ?? [],
2238
+ ...executor?.derived?.length ? { derived: executor.derived } : {},
2047
2239
  ...note ? { note } : {}
2048
2240
  };
2049
2241
  }
@@ -2644,6 +2836,7 @@ function createScope(args) {
2644
2836
  lastActivityAt: child.lastActivityAt,
2645
2837
  turns: child.spent.iterations,
2646
2838
  tokens: child.spent.tokens,
2839
+ ...child.spent.tokensKnown === false ? { tokensKnown: false } : {},
2647
2840
  usd: child.spent.usd,
2648
2841
  ...child.spent.usdKnown === false ? { usdKnown: false } : {}
2649
2842
  }, fromExecutor, opts.now ?? now(), opts.stallAfterMs ?? 18e4);
@@ -3039,6 +3232,7 @@ function clampSpend(spend, budget) {
3039
3232
  output: Math.floor(spend.tokens.output * ratio)
3040
3233
  } : spend.tokens,
3041
3234
  usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
3235
+ ...spend.tokensKnown === false ? { tokensKnown: false } : {},
3042
3236
  ...spend.usdKnown === false ? { usdKnown: false } : {},
3043
3237
  ms: spend.ms
3044
3238
  };
@@ -3262,6 +3456,7 @@ function sumSpend(settled) {
3262
3456
  total.tokens.input += ev.spent.tokens.input;
3263
3457
  total.tokens.output += ev.spent.tokens.output;
3264
3458
  total.usd += ev.spent.usd;
3459
+ if (ev.spent.tokensKnown === false) total.tokensKnown = false;
3265
3460
  if (ev.spent.usdKnown === false) total.usdKnown = false;
3266
3461
  total.ms += ev.spent.ms;
3267
3462
  }
@@ -3286,13 +3481,17 @@ function sumMetered(events) {
3286
3481
  total.tokens.input += ev.spend.tokens.input;
3287
3482
  total.tokens.output += ev.spend.tokens.output;
3288
3483
  total.usd += ev.spend.usd;
3484
+ if (ev.spend.tokensKnown === false) total.tokensKnown = false;
3289
3485
  if (ev.spend.usdKnown === false) total.usdKnown = false;
3290
3486
  total.ms += ev.spend.ms;
3291
3487
  }
3292
3488
  return total;
3293
3489
  }
3490
+ /** An all-zero spend that carries an UNKNOWN marker counts as non-zero: a sub-driver whose turns
3491
+ * went unmeasured did real work, and dropping its `metered` event here would re-hide upstream the
3492
+ * very turn the driver refused to skip. */
3294
3493
  function isNonZeroSpend(s) {
3295
- return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
3494
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
3296
3495
  }
3297
3496
  /** A spend, or `undefined` when it is all-zero — so `metered()` returns undefined for a driver
3298
3497
  * whose sub-tree did no inference (and the parent journals no empty `metered` event). */
@@ -3364,6 +3563,10 @@ function isAgent(value) {
3364
3563
  * unknown reconcile) so a child can never refund twice. If dollar cost is unknowable under a
3365
3564
  * dollar limit, reconciliation closes the known token/iteration work, marks the dollar channel
3366
3565
  * unusable, and refuses later reservations; inventing a numeric dollar total would be worse.
3566
+ * If a TOKEN count is unknowable (a provider that reported no usage for work that ran), the pool
3567
+ * records the work, debits what it knows, and marks `readout().tokensKnown` false — the balance is
3568
+ * then a ceiling, not a measurement. It does not close admission the way the dollar channel does:
3569
+ * tokens are always capped, so one unreported turn must not end the run.
3367
3570
  *
3368
3571
  * @experimental
3369
3572
  */
@@ -3426,6 +3629,7 @@ function createBudgetPool(root, now = Date.now) {
3426
3629
  let freeTokens = root.maxTokens;
3427
3630
  let reservedTokens = 0;
3428
3631
  let committedTokens = 0;
3632
+ let tokensTainted = false;
3429
3633
  const usdCapped = root.maxUsd !== void 0;
3430
3634
  let freeUsd = root.maxUsd ?? 0;
3431
3635
  let reservedUsd = 0;
@@ -3492,6 +3696,7 @@ function createBudgetPool(root, now = Date.now) {
3492
3696
  if (spent.iterations > rIterations) throw new Error(`budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`);
3493
3697
  if (usdCapped && spent.usd > rUsd) throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
3494
3698
  open.delete(ticket.id);
3699
+ if (spent.tokensKnown === false) tokensTainted = true;
3495
3700
  reservedTokens -= rTokens;
3496
3701
  committedTokens += spentTokens;
3497
3702
  freeTokens += rTokens - spentTokens;
@@ -3510,6 +3715,7 @@ function createBudgetPool(root, now = Date.now) {
3510
3715
  }
3511
3716
  function observe(spend) {
3512
3717
  if (usdCapped && spend.usdKnown === false) throw new Error("budget pool: cannot observe unknown dollar cost under a dollar-capped budget");
3718
+ if (spend.tokensKnown === false) tokensTainted = true;
3513
3719
  const tokens = totalTokens(spend.tokens);
3514
3720
  freeTokens -= tokens;
3515
3721
  committedTokens += tokens;
@@ -3521,6 +3727,7 @@ function createBudgetPool(root, now = Date.now) {
3521
3727
  function readout() {
3522
3728
  return {
3523
3729
  tokensLeft: freeTokens,
3730
+ tokensKnown: !tokensTainted,
3524
3731
  usdLeft: usdCapped ? usdTainted ? 0 : freeUsd : 0,
3525
3732
  usdCapped,
3526
3733
  deadlineMs: absoluteDeadlineMs,
@@ -4011,6 +4218,7 @@ function accumulate(a, b) {
4011
4218
  a.tokens.input += b.tokens.input;
4012
4219
  a.tokens.output += b.tokens.output;
4013
4220
  a.usd += b.usd;
4221
+ if (b.tokensKnown === false) a.tokensKnown = false;
4014
4222
  if (b.usdKnown === false) a.usdKnown = false;
4015
4223
  a.ms += b.ms;
4016
4224
  }
@@ -4024,17 +4232,20 @@ function addSpend(a, b) {
4024
4232
  output: a.tokens.output + b.tokens.output
4025
4233
  },
4026
4234
  usd: a.usd + b.usd,
4235
+ ...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
4027
4236
  ...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
4028
4237
  ms: a.ms + b.ms
4029
4238
  };
4030
4239
  }
4031
4240
  /** True when any driver metered inference this run (so the winner carries a `spentBreakdown`).
4032
4241
  * Checks every channel `addSpend` sums — including `ms` — so the gate stays consistent with the
4033
- * total even though the coordination driver currently stamps `ms: 0`. */
4242
+ * total even though the coordination driver currently stamps `ms: 0`. An all-zero spend that
4243
+ * carries an UNKNOWN marker is non-empty: work happened and went unmeasured, which is exactly the
4244
+ * fact a breakdown must not hide by looking like no work at all. */
4034
4245
  function isNonEmptySpend(s) {
4035
- return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
4246
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
4036
4247
  }
4037
4248
  //#endregion
4038
- export { createWorktree as A, routerChatWithUsage as C, runWorktreeChecks as D, runSettledCommand as E, CodexExecutionDiagnosticError as F, harnessInvocation as M, parseCodexTokenUsage as N, runWorktreeHarness as O, runLocalHarness as P, routerChatWithTools as S, runBrainLoop as T, waitUntil as _, runFinalizer as a, readWorkerProgress as b, spendFromUsageEvents as c, settledToIteration as d, createWaitProbes as f, validateWaitSpec as g, timerAt as h, pickBestDelivered as i, removeWorktree as j, captureWorktreeDiff as k, withDriverExecutor as l, pollFor as m, bestDelivered as n, runTree as o, isWaitOutcome as p, collectDelivered as r, createBudgetPool as s, createSupervisor as t, createScope as u, DEFAULT_STALL_AFTER_MS as v, routerToolLoop as w, routerBrain as x, createActivityLog as y };
4249
+ export { captureWorktreeDiff as A, routerChatWithUsage as C, runSettledCommand as D, runBrainLoop as E, runLocalHarness as F, CodexExecutionDiagnosticError as I, removeWorktree as M, harnessInvocation as N, runWorktreeChecks as O, parseCodexTokenUsage as P, routerChatWithTools as S, streamRouterChatWithTools as T, waitUntil as _, runFinalizer as a, readWorkerProgress as b, spendFromUsageEvents as c, settledToIteration as d, createWaitProbes as f, validateWaitSpec as g, timerAt as h, pickBestDelivered as i, createWorktree as j, runWorktreeHarness as k, withDriverExecutor as l, pollFor as m, bestDelivered as n, runTree as o, isWaitOutcome as p, collectDelivered as r, createBudgetPool as s, createSupervisor as t, createScope as u, DEFAULT_STALL_AFTER_MS as v, routerToolLoop as w, routerBrain as x, createActivityLog as y };
4039
4250
 
4040
- //# sourceMappingURL=supervisor-zebR4ICs.js.map
4251
+ //# sourceMappingURL=supervisor-DKqolBGI.js.map