@tangle-network/agent-runtime 0.143.0 → 0.153.1

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 (113) hide show
  1. package/README.md +32 -0
  2. package/dist/{activation-mi8TgSLM.js → activation-DKxQsHiO.js} +3 -3
  3. package/dist/{activation-mi8TgSLM.js.map → activation-DKxQsHiO.js.map} +1 -1
  4. package/dist/agent.d.ts +1 -1
  5. package/dist/agent.js +3 -3
  6. package/dist/{analyst-loop-CvkmfhUB.js → analyst-loop-B2kTHKNh.js} +2 -2
  7. package/dist/{analyst-loop-CvkmfhUB.js.map → analyst-loop-B2kTHKNh.js.map} +1 -1
  8. package/dist/analyst-loop.js +1 -1
  9. package/dist/{authoring-DuyShLdw.js → authoring-Tk2xFys0.js} +2 -2
  10. package/dist/{authoring-DuyShLdw.js.map → authoring-Tk2xFys0.js.map} +1 -1
  11. package/dist/candidate-execution/index.js +4 -4
  12. package/dist/{candidate-execution-CD5PoNmC.js → candidate-execution-CB9RJbbJ.js} +4 -4
  13. package/dist/{candidate-execution-CD5PoNmC.js.map → candidate-execution-CB9RJbbJ.js.map} +1 -1
  14. package/dist/{conversation-1hNtDeMM.js → conversation-D64WyWrK.js} +4 -4
  15. package/dist/{conversation-1hNtDeMM.js.map → conversation-D64WyWrK.js.map} +1 -1
  16. package/dist/conversation.d.ts +1 -1
  17. package/dist/conversation.js +1 -1
  18. package/dist/durable.d.ts +102 -4
  19. package/dist/durable.js +259 -10
  20. package/dist/durable.js.map +1 -1
  21. package/dist/{environment-provider-B8eZ85Ga.d.ts → environment-provider-DJG0yIYl.d.ts} +2 -2
  22. package/dist/{environment-provider-DCBJ13VP.js → environment-provider-D_MXw3jK.js} +88 -9
  23. package/dist/environment-provider-D_MXw3jK.js.map +1 -0
  24. package/dist/environment-provider.d.ts +1 -1
  25. package/dist/environment-provider.js +1 -1
  26. package/dist/executable-spec-BUcUcEPk.js +73 -0
  27. package/dist/executable-spec-BUcUcEPk.js.map +1 -0
  28. package/dist/{graph-De_XEsfH.js → graph-M0Qr4863.js} +4 -4
  29. package/dist/{graph-De_XEsfH.js.map → graph-M0Qr4863.js.map} +1 -1
  30. package/dist/{improvement-cycle-Bi-Tf4QF.js → improvement-cycle-CDxlGQGE.js} +7 -7
  31. package/dist/{improvement-cycle-Bi-Tf4QF.js.map → improvement-cycle-CDxlGQGE.js.map} +1 -1
  32. package/dist/{index-B8rKtH3U.d.ts → index-BihHgUAI.d.ts} +4 -4
  33. package/dist/{index-C2caGomG.d.ts → index-Dlz7ejRx.d.ts} +339 -503
  34. package/dist/{index-D4sLkOPY.d.ts → index-LQfgFJoX.d.ts} +2 -2
  35. package/dist/index.d.ts +7 -7
  36. package/dist/index.js +13 -13
  37. package/dist/intelligence.d.ts +4 -4
  38. package/dist/intelligence.js +6 -6
  39. package/dist/kernel.d.ts +6 -6
  40. package/dist/kernel.js +13 -13
  41. package/dist/{knowledge-CVE6Wk1i.js → knowledge-DM-i40b9.js} +10 -7
  42. package/dist/{knowledge-CVE6Wk1i.js.map → knowledge-DM-i40b9.js.map} +1 -1
  43. package/dist/knowledge.d.ts +1 -1
  44. package/dist/knowledge.js +1 -1
  45. package/dist/{loop-runner-bin-BoHdQqLm.d.ts → loop-runner-bin-D-BW51yr.d.ts} +3 -3
  46. package/dist/{loop-runner-bin-DdaI1nLL.js → loop-runner-bin-pD5WJuqJ.js} +4 -4
  47. package/dist/{loop-runner-bin-DdaI1nLL.js.map → loop-runner-bin-pD5WJuqJ.js.map} +1 -1
  48. package/dist/loop-runner-bin.d.ts +1 -1
  49. package/dist/loop-runner-bin.js +1 -1
  50. package/dist/{materialization-P86EciBw.js → materialization-Bo6j3Hg9.js} +257 -61
  51. package/dist/materialization-Bo6j3Hg9.js.map +1 -0
  52. package/dist/mcp/bin.js +3 -3
  53. package/dist/mcp/index.d.ts +3 -3
  54. package/dist/mcp/index.js +6 -6
  55. package/dist/{model-policy-DgRJIJse.js → model-policy-Sw4ywhtL.js} +68 -6
  56. package/dist/model-policy-Sw4ywhtL.js.map +1 -0
  57. package/dist/{openai-tools-BiAWKjh6.js → openai-tools-CWBywy-e.js} +2 -2
  58. package/dist/{openai-tools-BiAWKjh6.js.map → openai-tools-CWBywy-e.js.map} +1 -1
  59. package/dist/{prepare-D3n7cA39.js → prepare-C3eFd3UA.js} +2 -2
  60. package/dist/{prepare-D3n7cA39.js.map → prepare-C3eFd3UA.js.map} +1 -1
  61. package/dist/primeintellect/index.d.ts +1 -1
  62. package/dist/profiles.js +1 -1
  63. package/dist/{protected-model-port-bq6bD9YX.js → protected-model-port-Dn2b6peS.js} +2 -2
  64. package/dist/{protected-model-port-bq6bD9YX.js.map → protected-model-port-Dn2b6peS.js.map} +1 -1
  65. package/dist/{redact-DexTuAiX.d.ts → redact-NAptmqKw.d.ts} +3 -3
  66. package/dist/{researcher-CiSY6Wzb.js → researcher-BMFFPwKb.js} +2 -2
  67. package/dist/{researcher-CiSY6Wzb.js.map → researcher-BMFFPwKb.js.map} +1 -1
  68. package/dist/{run-layout-Be_Z0dF8.js → run-layout-C2jgwsCq.js} +111 -6
  69. package/dist/run-layout-C2jgwsCq.js.map +1 -0
  70. package/dist/{runtime-J6Toky8K.d.ts → runtime-B-5dp5Iz.d.ts} +214 -14
  71. package/dist/{runtime-BnqjbOGX.js → runtime-RY_rvv7t.js} +85 -82
  72. package/dist/runtime-RY_rvv7t.js.map +1 -0
  73. package/dist/{sandbox-events-COwCWUVB.js → sandbox-events-DMijDq2p.js} +49 -2
  74. package/dist/sandbox-events-DMijDq2p.js.map +1 -0
  75. package/dist/{spawn-journal-D2HKUImC.js → spawn-journal-CLguaA2A.js} +8 -43
  76. package/dist/spawn-journal-CLguaA2A.js.map +1 -0
  77. package/dist/{stream-agent-turn-C1Vs_3B9.d.ts → stream-agent-turn-BGyszuPH.d.ts} +2 -2
  78. package/dist/{stream-agent-turn-CXRugSGO.js → stream-agent-turn-D6HoaRPV.js} +90 -14
  79. package/dist/stream-agent-turn-D6HoaRPV.js.map +1 -0
  80. package/dist/{structural-rollout-CJgj9YXH.js → structural-rollout-P1YxFRfl.js} +11 -8
  81. package/dist/structural-rollout-P1YxFRfl.js.map +1 -0
  82. package/dist/{supervise-hCgG6ROJ.js → supervise-BezbzKiJ.js} +1550 -1186
  83. package/dist/supervise-BezbzKiJ.js.map +1 -0
  84. package/dist/{supervisor-s8SfZYbW.js → supervisor-DBVlPONC.js} +597 -194
  85. package/dist/supervisor-DBVlPONC.js.map +1 -0
  86. package/dist/testing.d.ts +2 -2
  87. package/dist/testing.js +12 -12
  88. package/dist/{top-app-BMOSKK06.js → top-app-tKo9OrON.js} +189 -86
  89. package/dist/top-app-tKo9OrON.js.map +1 -0
  90. package/dist/tui/bin.js +1 -1
  91. package/dist/tui/index.d.ts +32 -4
  92. package/dist/tui/index.js +1 -1
  93. package/dist/{types-Df9vulGc.d.ts → types-BPQhQRrC.d.ts} +507 -10
  94. package/dist/{util-C1bv6yog.js → util-Bncw0o_F.js} +41 -2
  95. package/dist/util-Bncw0o_F.js.map +1 -0
  96. package/dist/{workspace-archive-C52LjkeN.js → workspace-archive-Dm73lTfB.js} +2 -2
  97. package/dist/{workspace-archive-C52LjkeN.js.map → workspace-archive-Dm73lTfB.js.map} +1 -1
  98. package/package.json +4 -4
  99. package/dist/environment-provider-DCBJ13VP.js.map +0 -1
  100. package/dist/materialization-P86EciBw.js.map +0 -1
  101. package/dist/model-policy-DgRJIJse.js.map +0 -1
  102. package/dist/retained-run-binding-Dp2e8xne.js +0 -268
  103. package/dist/retained-run-binding-Dp2e8xne.js.map +0 -1
  104. package/dist/run-layout-Be_Z0dF8.js.map +0 -1
  105. package/dist/runtime-BnqjbOGX.js.map +0 -1
  106. package/dist/sandbox-events-COwCWUVB.js.map +0 -1
  107. package/dist/spawn-journal-D2HKUImC.js.map +0 -1
  108. package/dist/stream-agent-turn-CXRugSGO.js.map +0 -1
  109. package/dist/structural-rollout-CJgj9YXH.js.map +0 -1
  110. package/dist/supervise-hCgG6ROJ.js.map +0 -1
  111. package/dist/supervisor-s8SfZYbW.js.map +0 -1
  112. package/dist/top-app-BMOSKK06.js.map +0 -1
  113. package/dist/util-C1bv6yog.js.map +0 -1
package/dist/durable.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { _o as supervise, jo as SupervisorProfile, po as SuperviseOptions } from "./index-C2caGomG.js";
1
+ import { ao as SuperviseOptions, lo as supervise, wo as SupervisorProfile } from "./index-Dlz7ejRx.js";
2
2
  import { l as RuntimeHooks, o as RuntimeHookEvent, r as RuntimeDecisionPoint } from "./runtime-hooks-Bj6wJHlH.js";
3
- import { x as NodeId } from "./types-Df9vulGc.js";
3
+ import { A as ProviderModelExecutionEvidence, O as ProfileMaterializationReceipt, T as NodeId, Y as SpendGap, ct as WorkerTraceEvidence, o as ExecutionBindingReceipt, q as Spend } from "./types-BPQhQRrC.js";
4
4
  //#region src/durable/chat-engine.d.ts
5
5
  /**
6
6
  * `handleChatTurn` is a framework-neutral chat-turn HTTP orchestrator.
@@ -198,6 +198,66 @@ declare function createFileObserverHooks(path: string, pursuitId: string): {
198
198
  //#endregion
199
199
  //#region src/durable/observer-projection.d.ts
200
200
  type PursuitRunStatus = 'running' | 'done' | 'failed';
201
+ /**
202
+ * Where a node's dollar figure came from. `reported` = a provider billed all of it; `estimated` =
203
+ * a model catalog priced all of it; `partial` = a provider billed part and a catalog priced the
204
+ * rest; `unknown` = nothing priced it, so `usd` is a floor and never the cost.
205
+ */
206
+ type PursuitCostProvenance = 'reported' | 'estimated' | 'partial' | 'unknown';
207
+ /**
208
+ * One node's token usage by class. Cache and reasoning classes are absent when the provider did
209
+ * not report them — absence is not zero. `tokensKnown` is `false` when work happened whose token
210
+ * count no provider reported, which makes `input`/`output` a floor.
211
+ */
212
+ interface PursuitNodeUsage {
213
+ readonly input: number;
214
+ readonly output: number;
215
+ readonly cacheRead?: number;
216
+ readonly cacheWrite?: number;
217
+ readonly reasoning?: number;
218
+ readonly tokensKnown: boolean;
219
+ }
220
+ /** One node's dollar cost with the provenance that decides whether it may be compared or summed. */
221
+ interface PursuitNodeCost {
222
+ readonly usd: number;
223
+ readonly usdKnown: boolean;
224
+ /** The part of `usd` a model catalog priced because no provider receipt covered it. */
225
+ readonly usdEstimated?: number;
226
+ readonly provenance: PursuitCostProvenance;
227
+ }
228
+ /**
229
+ * One node's clock. `wallMs` is `settledAt - startedAt` and is deliberately distinct from the
230
+ * executor-reported `spent.ms` sums, which under-report and overlap across parallel children.
231
+ * `firstTokenAt` stays absent unless a provider reports that instant; it is never inferred from
232
+ * `firstOutputAt`, which is when the node first reported usage for a turn.
233
+ */
234
+ interface PursuitNodeTiming {
235
+ readonly startedAt: number;
236
+ readonly firstOutputAt?: number;
237
+ readonly firstTokenAt?: number;
238
+ readonly settledAt?: number;
239
+ readonly wallMs?: number;
240
+ }
241
+ /** Where and how a node's execution was placed, read off its execution-binding receipt. */
242
+ type PursuitNodePlacement = Readonly<Record<string, string | number | boolean | null>>;
243
+ /**
244
+ * One run's spend counted once, and each node's own share of it. `inclusive` and the entries of
245
+ * `exclusiveByNode` are the two views a client needs to show a tree without double counting.
246
+ */
247
+ interface PursuitRunTotals {
248
+ /**
249
+ * The whole run counted once. A node's settled `spent` already contains the child work its own
250
+ * nested tree reported, so summing only the run's top-level nodes plus every node's own
251
+ * inference counts each model call exactly once.
252
+ */
253
+ readonly inclusive: Spend;
254
+ /**
255
+ * Each node's own share: its reported spend and own inference minus what its direct children
256
+ * reported. Keyed by node id, plus the run root when the root itself metered inference. The
257
+ * entries sum to `inclusive` by construction.
258
+ */
259
+ readonly exclusiveByNode: Readonly<Record<string, Spend>>;
260
+ }
201
261
  interface PursuitRunProjection {
202
262
  readonly runId: string;
203
263
  readonly status: PursuitRunStatus;
@@ -211,6 +271,9 @@ interface PursuitRunProjection {
211
271
  readonly decisionCount: number;
212
272
  readonly targets: Readonly<Record<string, number>>;
213
273
  readonly decisions: Readonly<Record<string, number>>;
274
+ readonly totals: PursuitRunTotals;
275
+ /** The nodes whose accounting is incomplete. Present exactly when non-empty. */
276
+ readonly spendGaps?: ReadonlyArray<SpendGap>;
214
277
  }
215
278
  type PursuitNodeStatus = 'running' | 'done' | 'down';
216
279
  interface PursuitNodeProjection {
@@ -219,6 +282,7 @@ interface PursuitNodeProjection {
219
282
  /** Node ids are scoped to this concrete Runtime tree; `(runId,id)` is identity. */
220
283
  readonly runId: string;
221
284
  readonly label?: string;
285
+ /** The runner that executed this node — the executor's own name, not a harness guess. */
222
286
  readonly runtime?: string;
223
287
  readonly depth?: number;
224
288
  readonly assignmentId?: string;
@@ -226,18 +290,48 @@ interface PursuitNodeProjection {
226
290
  readonly budget?: unknown;
227
291
  readonly status: PursuitNodeStatus;
228
292
  readonly settledAt?: number;
229
- readonly spent?: unknown;
293
+ /** The child work this node reported at settlement. Absent until a terminal record lands. */
294
+ readonly spent?: Spend;
295
+ /** This node's OWN inference, re-homed from its nested tree. Absent when it drove no turns. */
296
+ readonly ownInference?: Spend;
297
+ /** Absent until a spend record lands; the run's `spendGaps` then names the node. */
298
+ readonly usage?: PursuitNodeUsage;
299
+ /** Absent until a spend record lands; the run's `spendGaps` then names the node. */
300
+ readonly cost?: PursuitNodeCost;
301
+ readonly timing?: PursuitNodeTiming;
302
+ /** The kernel-minted attempt this node's execution binding is keyed on. */
303
+ readonly attemptId?: string;
304
+ /** The runner-native execution the node bound to: a request, session, run, process, or tree. */
305
+ readonly execution?: {
306
+ readonly kind: string;
307
+ readonly id: string;
308
+ };
309
+ /** The model the materialization receipt names, when the runner reported one. */
310
+ readonly model?: string;
311
+ /** The concrete backend the profile materialized onto. */
312
+ readonly backend?: string;
313
+ readonly placement?: PursuitNodePlacement;
314
+ /** Model-call identifiers this node's own turns reported, in order, deduplicated. */
315
+ readonly modelCalls?: ReadonlyArray<string>;
316
+ readonly materialization?: ProfileMaterializationReceipt;
317
+ readonly executionBindings?: ReadonlyArray<ExecutionBindingReceipt>;
318
+ /** What the provider itself reported serving, and why it is unknown when it is. */
319
+ readonly providerModel?: ProviderModelExecutionEvidence;
320
+ /** Content-addressed pointer to this node's persisted tool trace, or why there is none. */
321
+ readonly trace?: WorkerTraceEvidence;
230
322
  readonly outRef?: string;
231
323
  readonly score?: number;
232
324
  readonly valid?: boolean;
233
325
  readonly reason?: string;
234
326
  readonly infra?: boolean;
327
+ readonly restartCount?: number;
235
328
  readonly wait?: unknown;
236
329
  readonly firstSequence: number;
237
330
  readonly lastSequence: number;
238
331
  readonly firstObservedAt: number;
239
332
  readonly lastObservedAt: number;
240
333
  readonly eventCount: number;
334
+ readonly turnCount: number;
241
335
  }
242
336
  interface PursuitProjection {
243
337
  readonly pursuitId: string;
@@ -264,6 +358,10 @@ interface PursuitProjection {
264
358
  * state comes only from `agent.child`; concrete run state comes only from the root
265
359
  * `agent.run` lifecycle emitted by `supervisePursuit`. Node identity is scoped to the
266
360
  * concrete Runtime run so independent trees may both contain `root:s0` without aliasing.
361
+ *
362
+ * Usage, cost and timing are reported at the class the runtime measured them at. A missing
363
+ * class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
364
+ * unmeasured channel into a zero, because a fabricated zero is indistinguishable from free work.
267
365
  */
268
366
  declare function projectPursuit(records: readonly ObserverRecord[]): PursuitProjection;
269
367
  //#endregion
@@ -335,5 +433,5 @@ interface DurableSupervisionDiscovery {
335
433
  */
336
434
  declare function discoverDurableSupervisionRun(runDir: string): Promise<DurableSupervisionDiscovery>;
337
435
  //#endregion
338
- export { type ChatStreamEvent, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnProducer, type ChatTurnResult, type DurableCoordinationStreamIdentity, type DurableSupervisionDiscovery, FileObserverJournal, type ObserverJournal, type ObserverRecord, type ObserverRecordKind, type PursuitNodeProjection, type PursuitNodeStatus, type PursuitProjection, type PursuitRunProjection, type PursuitRunStatus, type RunChatTurnInput, SupervisePursuitError, type SupervisePursuitOptions, type SupervisedPursuitResult, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, supervisePursuit, verifyObserverRecords };
436
+ export { type ChatStreamEvent, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnProducer, type ChatTurnResult, type DurableCoordinationStreamIdentity, type DurableSupervisionDiscovery, FileObserverJournal, type ObserverJournal, type ObserverRecord, type ObserverRecordKind, type PursuitCostProvenance, type PursuitNodeCost, type PursuitNodePlacement, type PursuitNodeProjection, type PursuitNodeStatus, type PursuitNodeTiming, type PursuitNodeUsage, type PursuitProjection, type PursuitRunProjection, type PursuitRunStatus, type PursuitRunTotals, type RunChatTurnInput, SupervisePursuitError, type SupervisePursuitOptions, type SupervisedPursuitResult, createFileObserverHooks, deriveExecutionId, discoverDurableSupervisionRun, handleChatTurn, observerRecordDigest, projectPursuit, supervisePursuit, verifyObserverRecords };
339
437
  //# sourceMappingURL=durable.d.ts.map
package/dist/durable.js CHANGED
@@ -1,6 +1,7 @@
1
- import { d as prepareJsonlAppend, f as writeAllBytes, u as parseCommittedJsonLines } from "./spawn-journal-D2HKUImC.js";
1
+ import { d as prepareJsonlAppend, f as writeAllBytes, u as parseCommittedJsonLines } from "./spawn-journal-CLguaA2A.js";
2
+ import { i as cloneSpend, t as addSpend, v as zeroSpend } from "./util-Bncw0o_F.js";
2
3
  import { a as withPursuitContext, t as composeRuntimeHooks } from "./runtime-hooks-tXpAarhW.js";
3
- import { n as supervise } from "./supervise-hCgG6ROJ.js";
4
+ import { n as supervise } from "./supervise-BezbzKiJ.js";
4
5
  import { createHash } from "node:crypto";
5
6
  import { resolve } from "node:path";
6
7
  import { readFile } from "node:fs/promises";
@@ -301,6 +302,10 @@ function toError(error) {
301
302
  * state comes only from `agent.child`; concrete run state comes only from the root
302
303
  * `agent.run` lifecycle emitted by `supervisePursuit`. Node identity is scoped to the
303
304
  * concrete Runtime run so independent trees may both contain `root:s0` without aliasing.
305
+ *
306
+ * Usage, cost and timing are reported at the class the runtime measured them at. A missing
307
+ * class stays ABSENT and the run names the node in `spendGaps`; nothing here converts an
308
+ * unmeasured channel into a zero, because a fabricated zero is indistinguishable from free work.
304
309
  */
305
310
  function projectPursuit(records) {
306
311
  if (records.length === 0) throw new TypeError("projectPursuit: at least one observer record is required");
@@ -323,12 +328,19 @@ function projectPursuit(records) {
323
328
  projectRunActivity(run, record);
324
329
  projectSpawnNode(nodes, record);
325
330
  projectNodeActivity(nodes, record);
331
+ projectTurn(runs, nodes, record);
326
332
  } else if (record.decision) {
327
333
  decisionCount += 1;
328
334
  run.decisionCount += 1;
329
335
  increment(run.decisions, record.decision.kind);
330
336
  }
331
337
  }
338
+ const byRun = /* @__PURE__ */ new Map();
339
+ for (const node of nodes.values()) {
340
+ const list = byRun.get(node.runId);
341
+ if (list) list.push(node);
342
+ else byRun.set(node.runId, [node]);
343
+ }
332
344
  const first = verified[0];
333
345
  const last = verified.at(-1);
334
346
  return Object.freeze({
@@ -337,7 +349,7 @@ function projectPursuit(records) {
337
349
  chainTip: last.digest,
338
350
  firstObservedAt: first.observedAt,
339
351
  lastObservedAt: last.observedAt,
340
- runs: Object.freeze([...runs.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId)).map(freezeRun)),
352
+ runs: Object.freeze([...runs.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId)).map((run) => freezeRun(run, byRun.get(run.runId) ?? []))),
341
353
  nodes: Object.freeze([...nodes.values()].sort((a, b) => a.firstSequence - b.firstSequence || a.runId.localeCompare(b.runId) || a.id.localeCompare(b.id)).map(freezeNode)),
342
354
  eventCount,
343
355
  decisionCount
@@ -398,6 +410,8 @@ function projectSpawnNode(nodes, record) {
398
410
  const runtime = stringField(payload, "runtime");
399
411
  const depth = numberField(payload, "depth");
400
412
  const assignmentId = stringField(payload, "assignmentId");
413
+ const attemptId = stringField(payload, "attemptId");
414
+ const startedAt = numberField(payload, "startedAt");
401
415
  nodes.set(key, {
402
416
  id: childId,
403
417
  ...event.parentId ? { parentId: event.parentId } : {},
@@ -406,14 +420,18 @@ function projectSpawnNode(nodes, record) {
406
420
  ...runtime ? { runtime } : {},
407
421
  ...depth !== void 0 ? { depth } : {},
408
422
  ...assignmentId ? { assignmentId } : {},
423
+ ...attemptId ? { attemptId } : {},
424
+ startedAt: startedAt ?? record.observedAt,
409
425
  ...payload && Object.hasOwn(payload, "identity") ? { identity: payload.identity } : {},
410
426
  ...payload && Object.hasOwn(payload, "budget") ? { budget: payload.budget } : {},
411
427
  status: "running",
428
+ modelCalls: [],
412
429
  firstSequence: record.sequence,
413
430
  lastSequence: record.sequence,
414
431
  firstObservedAt: record.observedAt,
415
432
  lastObservedAt: record.observedAt,
416
- eventCount: 1
433
+ eventCount: 1,
434
+ turnCount: 0
417
435
  });
418
436
  }
419
437
  function projectNodeActivity(nodes, record) {
@@ -432,8 +450,14 @@ function projectNodeActivity(nodes, record) {
432
450
  const status = stringField(payload, "status");
433
451
  if (status !== "done" && status !== "down") return;
434
452
  node.status = status;
435
- node.settledAt = record.observedAt;
436
- if (payload && Object.hasOwn(payload, "spent")) node.spent = payload.spent;
453
+ node.settledAt = numberField(payload, "settledAt") ?? record.observedAt;
454
+ const startedAt = numberField(payload, "startedAt");
455
+ if (startedAt !== void 0) node.startedAt = startedAt;
456
+ if (payload && Object.hasOwn(payload, "spent")) node.spent = spendField(payload, "spent");
457
+ const metered = spendField(payload, "metered");
458
+ if (metered) node.ownInference = node.ownInference ? addSpend(node.ownInference, metered) : metered;
459
+ const runtime = stringField(payload, "runtime");
460
+ if (runtime) node.runtime = runtime;
437
461
  const outRef = stringField(payload, "outRef");
438
462
  if (outRef) node.outRef = outRef;
439
463
  const score = numberField(payload, "score");
@@ -444,17 +468,232 @@ function projectNodeActivity(nodes, record) {
444
468
  if (reason) node.reason = reason;
445
469
  const infra = booleanField(payload, "infra");
446
470
  if (infra !== void 0) node.infra = infra;
471
+ const restartCount = numberField(payload, "restartCount");
472
+ if (restartCount !== void 0) node.restartCount = restartCount;
447
473
  if (payload && Object.hasOwn(payload, "wait")) node.wait = payload.wait;
474
+ attachSettlementEvidence(node, payload);
475
+ }
476
+ /**
477
+ * Read the receipts a settlement carries. Each is taken as reported: an `unknown` receipt keeps
478
+ * its `status` and `reason` so a client can show WHY a model or backend is missing instead of
479
+ * showing nothing, and a receipt that never landed leaves every derived field absent.
480
+ */
481
+ function attachSettlementEvidence(node, payload) {
482
+ const providerModel = payload?.providerModel;
483
+ if (providerModel && typeof providerModel === "object") node.providerModel = providerModel;
484
+ const trace = payload?.trace;
485
+ if (trace && typeof trace === "object") node.trace = trace;
486
+ const bindings = payload?.executionBindings;
487
+ if (Array.isArray(bindings) && bindings.length > 0) {
488
+ node.executionBindings = bindings;
489
+ const known = bindings.find((binding) => objectRecord(binding)?.status === "known");
490
+ if (known) {
491
+ node.placement = known.descriptor;
492
+ node.attemptId ??= known.attemptId;
493
+ }
494
+ }
495
+ const receipt = payload?.materialization;
496
+ if (!receipt || typeof receipt !== "object") return;
497
+ node.materialization = receipt;
498
+ if (receipt.status !== "known") return;
499
+ node.backend = receipt.backend;
500
+ if (receipt.model.status === "known") node.model = receipt.model.id;
501
+ node.execution = {
502
+ kind: receipt.execution.kind,
503
+ id: receipt.execution.id
504
+ };
505
+ }
506
+ /**
507
+ * Fold one metered turn onto whoever drove it. `agent.turn` names its subject in `parentId` — the
508
+ * node making the call — not in the payload, because the caller IS the subject. A turn the run
509
+ * root drove belongs to no spawned node and lands on the run instead, so the run's totals stay
510
+ * complete without inventing a node for the root.
511
+ */
512
+ function projectTurn(runs, nodes, record) {
513
+ const event = record.event;
514
+ if (event?.target !== "agent.turn") return;
515
+ const payload = objectRecord(event.payload);
516
+ const spend = spendField(payload, "spend");
517
+ const subject = event.parentId;
518
+ if (subject === void 0) return;
519
+ const node = nodes.get(nodeKey(event.runId, subject));
520
+ if (!node) {
521
+ if (!spend) return;
522
+ const run = runs.get(event.runId);
523
+ if (!run) return;
524
+ run.rootInference = run.rootInference ? addSpend(run.rootInference, spend) : spend;
525
+ return;
526
+ }
527
+ node.turnCount += 1;
528
+ node.lastSequence = record.sequence;
529
+ node.lastObservedAt = record.observedAt;
530
+ if (spend) {
531
+ node.ownInference = node.ownInference ? addSpend(node.ownInference, spend) : spend;
532
+ node.firstOutputAt ??= record.observedAt;
533
+ }
534
+ const reasoning = numberField(payload, "reasoningTokens");
535
+ if (reasoning !== void 0) node.reasoningTokens = (node.reasoningTokens ?? 0) + reasoning;
536
+ const firstTokenAt = numberField(payload, "firstTokenAt");
537
+ if (firstTokenAt !== void 0) node.firstTokenAt ??= firstTokenAt;
538
+ const model = stringField(payload, "model");
539
+ if (model) node.model ??= model;
540
+ const callId = stringField(payload, "callId");
541
+ if (callId && !node.modelCalls.includes(callId)) node.modelCalls.push(callId);
542
+ }
543
+ /**
544
+ * A node's whole reported cost: the child work its settlement reported plus the inference it drove
545
+ * itself. `undefined` when neither landed — the run's `spendGaps` then names the node, so an
546
+ * unaccounted node is visible instead of reading as free.
547
+ */
548
+ function nodeTotal(node) {
549
+ if (node.spent && node.ownInference) return addSpend(node.spent, node.ownInference);
550
+ if (node.spent) return cloneSpend(node.spent);
551
+ if (node.ownInference) return cloneSpend(node.ownInference);
552
+ }
553
+ /**
554
+ * The run counted once, and each node's own share of it.
555
+ *
556
+ * A settled node's `spent` already contains the child work its nested tree reported, so summing
557
+ * every node would count a driver's descendants twice. Inclusive therefore sums only the run's
558
+ * TOP-LEVEL nodes — those whose parent is not another node of this run — plus the root's own
559
+ * turns. Exclusive subtracts each node's direct children from its own total, which telescopes:
560
+ * the exclusive entries sum back to `inclusive` exactly.
561
+ */
562
+ function runTotals(run, nodes) {
563
+ const present = new Set(nodes.map((node) => node.id));
564
+ const children = /* @__PURE__ */ new Map();
565
+ for (const node of nodes) {
566
+ if (node.parentId === void 0 || !present.has(node.parentId)) continue;
567
+ const list = children.get(node.parentId);
568
+ if (list) list.push(node);
569
+ else children.set(node.parentId, [node]);
570
+ }
571
+ const exclusiveByNode = {};
572
+ let inclusive = run.rootInference ? cloneSpend(run.rootInference) : zeroSpend();
573
+ if (run.rootInference) exclusiveByNode[run.runId] = cloneSpend(run.rootInference);
574
+ for (const node of nodes) {
575
+ const total = nodeTotal(node);
576
+ if (total === void 0) continue;
577
+ if (node.parentId === void 0 || !present.has(node.parentId)) inclusive = addSpend(inclusive, total);
578
+ let exclusive = total;
579
+ for (const child of children.get(node.id) ?? []) {
580
+ const childTotal = nodeTotal(child);
581
+ if (childTotal !== void 0) exclusive = subtractSpend(exclusive, childTotal);
582
+ }
583
+ exclusiveByNode[node.id] = exclusive;
584
+ }
585
+ return Object.freeze({
586
+ inclusive: Object.freeze(inclusive),
587
+ exclusiveByNode: Object.freeze(exclusiveByNode)
588
+ });
589
+ }
590
+ /**
591
+ * Remove a child's reported total from its parent's. Per channel, never below zero: a parent whose
592
+ * executor reported less than its children did is a reporting gap, and a negative exclusive share
593
+ * would be a fabricated number rather than a measurement.
594
+ */
595
+ function subtractSpend(a, b) {
596
+ const tokens = { ...a.tokens };
597
+ tokens.input = Math.max(0, tokens.input - b.tokens.input);
598
+ tokens.output = Math.max(0, tokens.output - b.tokens.output);
599
+ if (a.tokens.cacheRead !== void 0) tokens.cacheRead = Math.max(0, a.tokens.cacheRead - (b.tokens.cacheRead ?? 0));
600
+ if (a.tokens.cacheWrite !== void 0) tokens.cacheWrite = Math.max(0, a.tokens.cacheWrite - (b.tokens.cacheWrite ?? 0));
601
+ if (a.tokens.freshInput !== void 0) tokens.freshInput = Math.max(0, a.tokens.freshInput - (b.tokens.freshInput ?? 0));
602
+ return {
603
+ iterations: Math.max(0, a.iterations - b.iterations),
604
+ tokens,
605
+ ...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
606
+ usd: Math.max(0, a.usd - b.usd),
607
+ ...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
608
+ ...a.usdEstimated !== void 0 ? { usdEstimated: Math.max(0, a.usdEstimated - (b.usdEstimated ?? 0)) } : {},
609
+ ms: Math.max(0, a.ms - b.ms)
610
+ };
448
611
  }
449
- function freezeRun(run) {
612
+ /** The nodes of one run whose accounting is incomplete, in the vocabulary the supervisor already
613
+ * uses: `never-settled` = no terminal record, so every channel is unaccounted; `unreported` = a
614
+ * record landed with a channel the provider did not report, so that channel is a floor. */
615
+ function runSpendGaps(nodes) {
616
+ const gaps = [];
617
+ for (const node of nodes) {
618
+ const label = node.label;
619
+ if (node.status === "running") {
620
+ gaps.push({
621
+ id: node.id,
622
+ ...label !== void 0 ? { label } : {},
623
+ kind: "never-settled",
624
+ channels: ["tokens", "usd"]
625
+ });
626
+ continue;
627
+ }
628
+ const total = nodeTotal(node);
629
+ const channels = [];
630
+ if (total === void 0 || total.tokensKnown === false) channels.push("tokens");
631
+ if (total === void 0 || total.usdKnown === false) channels.push("usd");
632
+ if (channels.length === 0) continue;
633
+ gaps.push({
634
+ id: node.id,
635
+ ...label !== void 0 ? { label } : {},
636
+ kind: "unreported",
637
+ channels
638
+ });
639
+ }
640
+ return gaps;
641
+ }
642
+ function usageOf(total, reasoning) {
643
+ return {
644
+ input: total.tokens.input,
645
+ output: total.tokens.output,
646
+ ...total.tokens.cacheRead !== void 0 ? { cacheRead: total.tokens.cacheRead } : {},
647
+ ...total.tokens.cacheWrite !== void 0 ? { cacheWrite: total.tokens.cacheWrite } : {},
648
+ ...reasoning !== void 0 ? { reasoning } : {},
649
+ tokensKnown: total.tokensKnown !== false && total.tokens.tokensKnown !== false
650
+ };
651
+ }
652
+ function costOf(total) {
653
+ const usdKnown = total.usdKnown !== false;
654
+ const estimated = total.usdEstimated;
655
+ const provenance = usdKnown ? estimated === void 0 || estimated === 0 ? "reported" : "partial" : estimated === void 0 ? "unknown" : estimated >= total.usd ? "estimated" : "partial";
656
+ return {
657
+ usd: total.usd,
658
+ usdKnown,
659
+ ...estimated !== void 0 ? { usdEstimated: estimated } : {},
660
+ provenance
661
+ };
662
+ }
663
+ function timingOf(node) {
664
+ if (node.startedAt === void 0) return void 0;
665
+ return {
666
+ startedAt: node.startedAt,
667
+ ...node.firstOutputAt !== void 0 ? { firstOutputAt: node.firstOutputAt } : {},
668
+ ...node.firstTokenAt !== void 0 ? { firstTokenAt: node.firstTokenAt } : {},
669
+ ...node.settledAt !== void 0 ? { settledAt: node.settledAt } : {},
670
+ ...node.settledAt !== void 0 ? { wallMs: Math.max(0, node.settledAt - node.startedAt) } : {}
671
+ };
672
+ }
673
+ function freezeRun(run, nodes) {
674
+ const gaps = runSpendGaps(nodes);
675
+ const { rootInference: _rootInference, ...rest } = run;
450
676
  return Object.freeze({
451
- ...run,
677
+ ...rest,
452
678
  targets: Object.freeze({ ...run.targets }),
453
- decisions: Object.freeze({ ...run.decisions })
679
+ decisions: Object.freeze({ ...run.decisions }),
680
+ totals: runTotals(run, nodes),
681
+ ...gaps.length > 0 ? { spendGaps: Object.freeze(gaps) } : {}
454
682
  });
455
683
  }
456
684
  function freezeNode(node) {
457
- return Object.freeze({ ...node });
685
+ const { modelCalls, reasoningTokens, startedAt: _startedAt, ...rest } = node;
686
+ const total = nodeTotal(node);
687
+ const timing = timingOf(node);
688
+ return Object.freeze({
689
+ ...rest,
690
+ ...modelCalls.length > 0 ? { modelCalls: Object.freeze([...modelCalls]) } : {},
691
+ ...total !== void 0 ? {
692
+ usage: usageOf(total, reasoningTokens),
693
+ cost: costOf(total)
694
+ } : {},
695
+ ...timing !== void 0 ? { timing } : {}
696
+ });
458
697
  }
459
698
  function increment(target, key) {
460
699
  target[key] = (target[key] ?? 0) + 1;
@@ -462,6 +701,16 @@ function increment(target, key) {
462
701
  function objectRecord(value) {
463
702
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
464
703
  }
704
+ /** Read a journaled `Spend` without trusting the wire: a record missing the conserved channels is
705
+ * not a zero-cost turn, it is an unparseable one, and it must not enter a total. */
706
+ function spendField(value, key) {
707
+ const field = objectRecord(value?.[key]);
708
+ if (!field) return void 0;
709
+ const tokens = objectRecord(field.tokens);
710
+ if (typeof field.usd !== "number" || !Number.isFinite(field.usd)) return void 0;
711
+ if (!tokens || typeof tokens.input !== "number" || typeof tokens.output !== "number") return;
712
+ return cloneSpend(field);
713
+ }
465
714
  function stringField(value, key) {
466
715
  const field = value?.[key];
467
716
  return typeof field === "string" && field.length > 0 ? field : void 0;