@animalabs/agent-framework 0.23.2 → 0.24.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 (338) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +60 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +92 -12
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +14 -12
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/engine.d.ts +177 -0
  10. package/dist/src/code-execution/engine.d.ts.map +1 -0
  11. package/dist/src/code-execution/engine.js +494 -0
  12. package/dist/src/code-execution/engine.js.map +1 -0
  13. package/dist/src/code-execution/index.d.ts +5 -10
  14. package/dist/src/code-execution/index.d.ts.map +1 -1
  15. package/dist/src/code-execution/index.js +5 -9
  16. package/dist/src/code-execution/index.js.map +1 -1
  17. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  18. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  19. package/dist/src/code-execution/isolate/calls.js +145 -0
  20. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  21. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  22. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  24. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  26. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-main.js +132 -0
  28. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  30. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child.js +315 -0
  32. package/dist/src/code-execution/isolate/child.js.map +1 -0
  33. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  34. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/frames.js +210 -0
  36. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  37. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  38. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/output.js +143 -0
  40. package/dist/src/code-execution/isolate/output.js.map +1 -0
  41. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  42. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/prelude.js +401 -0
  44. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  45. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  46. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/protocol.js +150 -0
  48. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  49. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  50. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/render.js +214 -0
  52. package/dist/src/code-execution/isolate/render.js.map +1 -0
  53. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  54. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/runner.js +854 -0
  56. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  57. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  58. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/scripts.js +357 -0
  60. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  61. package/dist/src/code-execution/quickjs.wasm +0 -0
  62. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  63. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  64. package/dist/src/code-execution/tool-definition.js +176 -142
  65. package/dist/src/code-execution/tool-definition.js.map +1 -1
  66. package/dist/src/deadline-timer.d.ts +3 -0
  67. package/dist/src/deadline-timer.d.ts.map +1 -1
  68. package/dist/src/deadline-timer.js +1 -1
  69. package/dist/src/deadline-timer.js.map +1 -1
  70. package/dist/src/framework.d.ts +1027 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4093 -2238
  73. package/dist/src/framework.js.map +1 -1
  74. package/dist/src/gate/event-gate.d.ts +28 -1
  75. package/dist/src/gate/event-gate.d.ts.map +1 -1
  76. package/dist/src/gate/event-gate.js +88 -10
  77. package/dist/src/gate/event-gate.js.map +1 -1
  78. package/dist/src/held-writes.d.ts +16 -3
  79. package/dist/src/held-writes.d.ts.map +1 -1
  80. package/dist/src/held-writes.js +16 -3
  81. package/dist/src/held-writes.js.map +1 -1
  82. package/dist/src/index.d.ts +6 -6
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +9 -8
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/mcpl/channel-registry.d.ts +471 -75
  87. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  88. package/dist/src/mcpl/channel-registry.js +1673 -337
  89. package/dist/src/mcpl/channel-registry.js.map +1 -1
  90. package/dist/src/mcpl/conversation-router.d.ts +12 -7
  91. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  92. package/dist/src/mcpl/conversation-router.js +12 -7
  93. package/dist/src/mcpl/conversation-router.js.map +1 -1
  94. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  95. package/dist/src/mcpl/convert-block.js +9 -5
  96. package/dist/src/mcpl/convert-block.js.map +1 -1
  97. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  98. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  99. package/dist/src/mcpl/inbound-source.js +130 -0
  100. package/dist/src/mcpl/inbound-source.js.map +1 -1
  101. package/dist/src/mcpl/index.d.ts +2 -1
  102. package/dist/src/mcpl/index.d.ts.map +1 -1
  103. package/dist/src/mcpl/index.js +3 -2
  104. package/dist/src/mcpl/index.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +6 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  107. package/dist/src/mcpl/log-value.js +8 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -1
  109. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  110. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  111. package/dist/src/mcpl/modern-connection.js +168 -53
  112. package/dist/src/mcpl/modern-connection.js.map +1 -1
  113. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  114. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  115. package/dist/src/mcpl/prose-grammar.js +4 -5
  116. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  117. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  118. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  119. package/dist/src/mcpl/protocol-family.js +173 -16
  120. package/dist/src/mcpl/protocol-family.js.map +1 -1
  121. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  122. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  123. package/dist/src/mcpl/push-coalescer.js +227 -50
  124. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  125. package/dist/src/mcpl/push-handler.d.ts +6 -1
  126. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-handler.js +4 -1
  128. package/dist/src/mcpl/push-handler.js.map +1 -1
  129. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  130. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  131. package/dist/src/mcpl/reference-fetcher.js +4 -4
  132. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  133. package/dist/src/mcpl/server-connection.d.ts +50 -21
  134. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  135. package/dist/src/mcpl/server-connection.js +158 -74
  136. package/dist/src/mcpl/server-connection.js.map +1 -1
  137. package/dist/src/mcpl/tags.d.ts +24 -0
  138. package/dist/src/mcpl/tags.d.ts.map +1 -1
  139. package/dist/src/mcpl/tags.js +33 -0
  140. package/dist/src/mcpl/tags.js.map +1 -1
  141. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  142. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  143. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  144. package/dist/src/mcpl/transport.d.ts +55 -7
  145. package/dist/src/mcpl/transport.d.ts.map +1 -1
  146. package/dist/src/mcpl/transport.js +163 -34
  147. package/dist/src/mcpl/transport.js.map +1 -1
  148. package/dist/src/mcpl/types.d.ts +22 -6
  149. package/dist/src/mcpl/types.d.ts.map +1 -1
  150. package/dist/src/mcpl/types.js.map +1 -1
  151. package/dist/src/module-registry.d.ts +4 -2
  152. package/dist/src/module-registry.d.ts.map +1 -1
  153. package/dist/src/module-registry.js +100 -13
  154. package/dist/src/module-registry.js.map +1 -1
  155. package/dist/src/modules/discord/index.d.ts.map +1 -1
  156. package/dist/src/modules/discord/index.js +8 -13
  157. package/dist/src/modules/discord/index.js.map +1 -1
  158. package/dist/src/modules/history/index.d.ts.map +1 -1
  159. package/dist/src/modules/history/index.js +27 -4
  160. package/dist/src/modules/history/index.js.map +1 -1
  161. package/dist/src/modules/history/semantic.d.ts +15 -3
  162. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  163. package/dist/src/modules/history/semantic.js +27 -5
  164. package/dist/src/modules/history/semantic.js.map +1 -1
  165. package/dist/src/modules/index.d.ts +1 -1
  166. package/dist/src/modules/index.d.ts.map +1 -1
  167. package/dist/src/modules/index.js.map +1 -1
  168. package/dist/src/modules/unknown-tool.d.ts +4 -1
  169. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  170. package/dist/src/modules/unknown-tool.js +4 -1
  171. package/dist/src/modules/unknown-tool.js.map +1 -1
  172. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  173. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  174. package/dist/src/modules/workspace/branch-intent.js +3 -1
  175. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  176. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  177. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  178. package/dist/src/modules/workspace/edit-plan.js +794 -0
  179. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  180. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  181. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-text.js +584 -0
  183. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  184. package/dist/src/modules/workspace/effects.d.ts +5 -4
  185. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/effects.js +78 -25
  187. package/dist/src/modules/workspace/effects.js.map +1 -1
  188. package/dist/src/modules/workspace/index.d.ts +484 -61
  189. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/index.js +2173 -645
  191. package/dist/src/modules/workspace/index.js.map +1 -1
  192. package/dist/src/modules/workspace/observe.d.ts +213 -38
  193. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/observe.js +416 -36
  195. package/dist/src/modules/workspace/observe.js.map +1 -1
  196. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/reconcile.js +15 -8
  198. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  199. package/dist/src/modules/workspace/text.d.ts +32 -0
  200. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  201. package/dist/src/modules/workspace/text.js +67 -0
  202. package/dist/src/modules/workspace/text.js.map +1 -0
  203. package/dist/src/modules/workspace/types.d.ts +210 -6
  204. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/types.js.map +1 -1
  206. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  207. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  208. package/dist/src/modules/workspace/watcher.js.map +1 -1
  209. package/dist/src/operator-log.d.ts +52 -24
  210. package/dist/src/operator-log.d.ts.map +1 -1
  211. package/dist/src/operator-log.js.map +1 -1
  212. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  213. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  214. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  215. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  216. package/dist/src/recovery/offline-branch.d.ts +26 -1
  217. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  218. package/dist/src/recovery/offline-branch.js +164 -39
  219. package/dist/src/recovery/offline-branch.js.map +1 -1
  220. package/dist/src/recovery/recover-cli.js +23 -4
  221. package/dist/src/recovery/recover-cli.js.map +1 -1
  222. package/dist/src/send-tools.d.ts +11 -0
  223. package/dist/src/send-tools.d.ts.map +1 -0
  224. package/dist/src/send-tools.js +11 -0
  225. package/dist/src/send-tools.js.map +1 -0
  226. package/dist/src/tool-result-guard.d.ts +4 -3
  227. package/dist/src/tool-result-guard.d.ts.map +1 -1
  228. package/dist/src/tool-result-guard.js +5 -4
  229. package/dist/src/tool-result-guard.js.map +1 -1
  230. package/dist/src/tool-result-history.d.ts +1 -1
  231. package/dist/src/tool-result-history.js +1 -1
  232. package/dist/src/tune-out/coordinator.d.ts +21 -0
  233. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  234. package/dist/src/tune-out/coordinator.js +139 -37
  235. package/dist/src/tune-out/coordinator.js.map +1 -1
  236. package/dist/src/types/agent.d.ts +4 -0
  237. package/dist/src/types/agent.d.ts.map +1 -1
  238. package/dist/src/types/events.d.ts +62 -9
  239. package/dist/src/types/events.d.ts.map +1 -1
  240. package/dist/src/types/framework.d.ts +62 -58
  241. package/dist/src/types/framework.d.ts.map +1 -1
  242. package/dist/src/types/module.d.ts +40 -8
  243. package/dist/src/types/module.d.ts.map +1 -1
  244. package/dist/src/types/trace.d.ts +111 -10
  245. package/dist/src/types/trace.d.ts.map +1 -1
  246. package/dist/src/usage/types.d.ts +16 -0
  247. package/dist/src/usage/types.d.ts.map +1 -1
  248. package/dist/src/usage/usage-tracker.d.ts +14 -1
  249. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  250. package/dist/src/usage/usage-tracker.js +81 -41
  251. package/dist/src/usage/usage-tracker.js.map +1 -1
  252. package/package.json +6 -5
  253. package/src/agent.ts +93 -12
  254. package/src/api/server.ts +14 -12
  255. package/src/code-execution/engine.ts +595 -0
  256. package/src/code-execution/index.ts +5 -17
  257. package/src/code-execution/isolate/calls.ts +199 -0
  258. package/src/code-execution/isolate/child-entry.ts +5 -0
  259. package/src/code-execution/isolate/child-main.ts +132 -0
  260. package/src/code-execution/isolate/child.ts +327 -0
  261. package/src/code-execution/isolate/frames.ts +209 -0
  262. package/src/code-execution/isolate/output.ts +161 -0
  263. package/src/code-execution/isolate/prelude.ts +409 -0
  264. package/src/code-execution/isolate/protocol.ts +240 -0
  265. package/src/code-execution/isolate/render.ts +230 -0
  266. package/src/code-execution/isolate/runner.ts +911 -0
  267. package/src/code-execution/isolate/scripts.ts +479 -0
  268. package/src/code-execution/quickjs.wasm +0 -0
  269. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  270. package/src/code-execution/tool-definition.ts +236 -167
  271. package/src/deadline-timer.ts +1 -1
  272. package/src/framework.ts +4468 -2418
  273. package/src/gate/event-gate.ts +78 -11
  274. package/src/held-writes.ts +17 -3
  275. package/src/index.ts +30 -22
  276. package/src/mcpl/channel-registry.ts +1825 -368
  277. package/src/mcpl/conversation-router.ts +12 -7
  278. package/src/mcpl/convert-block.ts +9 -5
  279. package/src/mcpl/inbound-source.ts +131 -0
  280. package/src/mcpl/index.ts +4 -2
  281. package/src/mcpl/log-value.ts +9 -0
  282. package/src/mcpl/modern-connection.ts +162 -53
  283. package/src/mcpl/prose-grammar.ts +4 -5
  284. package/src/mcpl/protocol-family.ts +199 -21
  285. package/src/mcpl/push-coalescer.ts +306 -62
  286. package/src/mcpl/push-handler.ts +10 -2
  287. package/src/mcpl/reference-fetcher.ts +4 -4
  288. package/src/mcpl/server-connection.ts +180 -79
  289. package/src/mcpl/tags.ts +36 -0
  290. package/src/mcpl/tool-result-normalize.ts +2 -1
  291. package/src/mcpl/transport.ts +162 -33
  292. package/src/mcpl/types.ts +23 -6
  293. package/src/module-registry.ts +92 -13
  294. package/src/modules/discord/index.ts +8 -13
  295. package/src/modules/history/index.ts +25 -3
  296. package/src/modules/history/semantic.ts +29 -6
  297. package/src/modules/index.ts +1 -0
  298. package/src/modules/unknown-tool.ts +4 -1
  299. package/src/modules/workspace/branch-intent.ts +3 -1
  300. package/src/modules/workspace/edit-plan.ts +954 -0
  301. package/src/modules/workspace/edit-text.ts +618 -0
  302. package/src/modules/workspace/effects.ts +70 -19
  303. package/src/modules/workspace/index.ts +2247 -642
  304. package/src/modules/workspace/observe.ts +465 -56
  305. package/src/modules/workspace/reconcile.ts +14 -7
  306. package/src/modules/workspace/text.ts +68 -0
  307. package/src/modules/workspace/types.ts +211 -6
  308. package/src/modules/workspace/watcher.ts +3 -2
  309. package/src/operator-log.ts +47 -19
  310. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  311. package/src/recovery/offline-branch.ts +189 -55
  312. package/src/recovery/recover-cli.ts +25 -4
  313. package/src/send-tools.ts +11 -0
  314. package/src/tool-result-guard.ts +6 -5
  315. package/src/tool-result-history.ts +1 -1
  316. package/src/tune-out/coordinator.ts +163 -42
  317. package/src/types/agent.ts +4 -0
  318. package/src/types/events.ts +62 -9
  319. package/src/types/framework.ts +62 -58
  320. package/src/types/module.ts +40 -8
  321. package/src/types/trace.ts +121 -15
  322. package/src/usage/types.ts +16 -0
  323. package/src/usage/usage-tracker.ts +86 -43
  324. package/dist/src/code-execution/py-runner.d.ts +0 -191
  325. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  326. package/dist/src/code-execution/py-runner.js +0 -655
  327. package/dist/src/code-execution/py-runner.js.map +0 -1
  328. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  329. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  330. package/dist/src/code-execution/runtime-py.js +0 -677
  331. package/dist/src/code-execution/runtime-py.js.map +0 -1
  332. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  333. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  334. package/dist/src/mcpl/prose-stream-router.js +0 -291
  335. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  336. package/src/code-execution/py-runner.ts +0 -791
  337. package/src/code-execution/runtime-py.ts +0 -677
  338. package/src/mcpl/prose-stream-router.ts +0 -268
@@ -27,7 +27,8 @@ export interface MessageQuery {
27
27
 
28
28
  /**
29
29
  * A message as a module context's reads answer it: a stored row, or a row
30
- * for a write the framework holds (`held`).
30
+ * for a write the framework holds (`held`). Either is marked
31
+ * `heldFromResidents` when a tune-out holds it from the residents.
31
32
  */
32
33
  export type ModuleMessage = StoredMessage & {
33
34
  /**
@@ -39,9 +40,31 @@ export type ModuleMessage = StoredMessage & {
39
40
  * and `timestamp` are provisional: the sequence comes after every stored
40
41
  * row's, the timestamp is when the framework held the write (for a write
41
42
  * an older release held, when its queue was read back), and both change
42
- * when it lands. Absent on a stored row.
43
+ * when it lands. A held write whose earlier write may already be stored
44
+ * is looked up first, and answered as the stored row it is when the store
45
+ * holds it. Absent on a stored row.
43
46
  */
44
47
  held?: true;
48
+ /**
49
+ * True on a message a tune-out holds from the residents: one that arrived
50
+ * in a tuned-out channel, stamped `metadata.tuneOut` as it came in. The
51
+ * store keeps it, but no resident is shown the row itself: no resident's
52
+ * compiled view includes it, during the tune-out or after it, and a tool
53
+ * boundary of a turn alive when it arrived stores it without injecting it
54
+ * into the live turn. The subconscious reads it. Its text can still reach
55
+ * a resident in rows of their own, which aren't marked, such as the
56
+ * cancel's `<tuned-out-backlog>` row, with the text of the tune-out's newest
57
+ * messages, up to `backlogCap` of them and as many as fit in one of the
58
+ * residents' messages, the stored ones first and then those still held;
59
+ * what the subconscious says of it, in its report or its note; and the
60
+ * result of a tool that reads history on request, such as a
61
+ * channel's `fetch_history` or the history module's `extract`. A module
62
+ * that builds on what the residents see, such as one choosing from the
63
+ * recent conversation, leaves a marked row out, and one that matches
64
+ * what's stored, such as a sync's dedupe, keeps it. A held write (`held`)
65
+ * is marked too when it carries the stamp. Absent on every other row.
66
+ */
67
+ heldFromResidents?: true;
45
68
  };
46
69
 
47
70
  /**
@@ -290,13 +313,17 @@ export interface ModuleContext {
290
313
 
291
314
  /**
292
315
  * Edit a message, by its id or a held write's handle. A held write is
293
- * stored with the new content.
316
+ * stored with the new content. One whose earlier write may already be
317
+ * stored, where the store can't be searched to tell, throws with nothing
318
+ * changed.
294
319
  */
295
320
  editMessage(id: MessageId, content: ContentBlock[]): void;
296
321
 
297
322
  /**
298
323
  * Remove a message, by its id or a held write's handle. A held write is
299
- * dropped, so the agent never reads it.
324
+ * dropped, so the agent never reads it. One whose earlier write may
325
+ * already be stored, where the store can't be searched to tell, throws
326
+ * with nothing changed.
300
327
  */
301
328
  removeMessage(id: MessageId): void;
302
329
 
@@ -312,7 +339,9 @@ export interface ModuleContext {
312
339
  findMessageByExternalId(source: string, externalId: string): MessageId | null;
313
340
 
314
341
  /**
315
- * Get a message by its id or a held write's handle.
342
+ * Get a message by its id or a held write's handle, marked
343
+ * `heldFromResidents` when a tune-out holds it from the residents
344
+ * (ModuleMessage).
316
345
  */
317
346
  getMessage(id: MessageId): ModuleMessage | null;
318
347
 
@@ -322,7 +351,9 @@ export interface ModuleContext {
322
351
  * The rows of writes the framework holds come after the stored rows, as
323
352
  * the newest, or before them in a reversed query, marked `held`
324
353
  * (ModuleMessage). Those the last process held show once create() has
325
- * restored them, after every module's start() (addMessage).
354
+ * restored them, after every module's start() (addMessage). A message a
355
+ * tune-out holds from the residents is among the rows, marked
356
+ * `heldFromResidents` (ModuleMessage).
326
357
  */
327
358
  queryMessages(filter: MessageQuery): MessageQueryResult;
328
359
 
@@ -571,8 +602,9 @@ export interface SpeechContext {
571
602
 
572
603
  /**
573
604
  * The turn this speech ends: the same id as `ToolCall.callerTurnId` on the
574
- * calls made in it (its model's calls and its foreground scripts' inner
575
- * calls), kept across the turn's streams (a context-budget restart or a
605
+ * calls made in it (its model's calls, and a script's calls that reach the
606
+ * host while a code_execution call of the turn watches the script), kept across the
607
+ * turn's streams (a context-budget restart or a
576
608
  * retry), and larger for the agent's next turn. Unique only within one run
577
609
  * of the process.
578
610
  */
@@ -33,9 +33,23 @@ export type TraceEvent =
33
33
  | (TraceEventBase & {
34
34
  type: 'process:completed';
35
35
  processEvent: ProcessEvent;
36
+ /** The modules' responses that applied: one refused for malformed
37
+ * content is left out, and named by process:response-refused. */
36
38
  responses: ModuleProcessResponse[];
37
39
  durationMs: number;
38
40
  })
41
+ /** A module's process response held content context-manager won't store,
42
+ * in a message it adds or an edit it makes, and was refused whole before
43
+ * any response to the event applied. `where` names the first such message
44
+ * or edit in the response, such as `addMessages[1]`, and `places` is
45
+ * malformedContentPlaces' answer for it. */
46
+ | (TraceEventBase & {
47
+ type: 'process:response-refused';
48
+ moduleName: string;
49
+ eventType: string;
50
+ where: string;
51
+ places: string[];
52
+ })
39
53
 
40
54
  // Inference lifecycle
41
55
  | (TraceEventBase & {
@@ -124,9 +138,15 @@ export type TraceEvent =
124
138
  blockType: 'text' | 'thinking' | 'tool_call' | 'tool_result';
125
139
  /** 0-indexed block position in the current assistant turn. */
126
140
  blockIndex: number;
127
- /** The channel this stream shows typing in: the turn's locus as the
128
- * stream started (see inference:started), or in explicit mode the
129
- * channel the start's wake came from. */
141
+ /** The channel this stream shows typing in, the conversation the turn
142
+ * is attending. Locus and hybrid mode: the turn's locus, which an
143
+ * addressed re-pin or the agent's own `channel_open` can move between
144
+ * rounds, so it can change within one stream; the indicator and these
145
+ * traces follow it from the next round on, at the model's first
146
+ * content in it or its retry after a refusal, and a tool result the
147
+ * boundary reports carries the channel of the round it answers. In
148
+ * hybrid mode a `>>>` envelope can send the text itself elsewhere.
149
+ * Explicit mode: the channel the start's wake came from. */
130
150
  channelId?: string;
131
151
  })
132
152
  | (TraceEventBase & {
@@ -141,9 +161,15 @@ export type TraceEvent =
141
161
  phase: 'block_start' | 'block_complete';
142
162
  blockType: 'text' | 'thinking' | 'tool_call' | 'tool_result';
143
163
  blockIndex: number;
144
- /** The channel this stream shows typing in: the turn's locus as the
145
- * stream started (see inference:started), or in explicit mode the
146
- * channel the start's wake came from. */
164
+ /** The channel this stream shows typing in, the conversation the turn
165
+ * is attending. Locus and hybrid mode: the turn's locus, which an
166
+ * addressed re-pin or the agent's own `channel_open` can move between
167
+ * rounds, so it can change within one stream; the indicator and these
168
+ * traces follow it from the next round on, at the model's first
169
+ * content in it or its retry after a refusal, and a tool result the
170
+ * boundary reports carries the channel of the round it answers. In
171
+ * hybrid mode a `>>>` envelope can send the text itself elsewhere.
172
+ * Explicit mode: the channel the start's wake came from. */
147
173
  channelId?: string;
148
174
  })
149
175
  | (TraceEventBase & {
@@ -173,6 +199,66 @@ export type TraceEvent =
173
199
  type: 'inference:turn_ended';
174
200
  agentName: string;
175
201
  })
202
+ | (TraceEventBase & {
203
+ /**
204
+ * Prose that delivery published, sent once the publish succeeds: the
205
+ * outgoing stream's chunk (MCPL Spec 14.3 `channels/outgoing/chunk`),
206
+ * emitted whether or not the channel's server takes the stream. It
207
+ * carries only what was published, so a consumer that speaks or shows
208
+ * it, such as a voice relay, never gets text that delivery withholds (a
209
+ * `skip_reply`, a silencing send, a private `think`, a hold, a bounce,
210
+ * a draft) or a publish the server didn't confirm (`delivered: true`):
211
+ * one refused before sending, one that threw, timed out or got an
212
+ * error answer, one the server reported undelivered
213
+ * (`delivered: false`), and one whose reply leaves `delivered` out.
214
+ * `inference:tokens` stays the model's text as it is written (held, as
215
+ * before, while a tool-result guard's batch is pending). A later
216
+ * publish to the same destination, a server and its channel, by the
217
+ * same stream opens with a paragraph break (`\n\n`), so a
218
+ * destination's chunks joined in `index` order are its text, its
219
+ * messages kept apart.
220
+ * `mcpl:speech-routed` also reports speech publishes, with no stream
221
+ * id, index or end, each once delivered: deliverSpeech's, the `sleep`
222
+ * tool's announcement among them, and a subconscious's
223
+ * `speak_in_channel`'s. A relay that voices the agent's prose takes
224
+ * this trace, with `inference:published_complete`, and not
225
+ * `mcpl:speech-routed` as well, and takes each message's id from its
226
+ * `messageId` to edit what it voiced.
227
+ */
228
+ type: 'inference:published';
229
+ agentName: string;
230
+ /** The server the prose is published to, and its channel there: two
231
+ * servers' channels with the same id are two destinations. */
232
+ serverId: string;
233
+ channelId: string;
234
+ /** The stream's id, shared with its `channels/outgoing/*` notifications. */
235
+ inferenceId: string;
236
+ /** Increases by one per chunk of the stream, across its channels. */
237
+ index: number;
238
+ text: string;
239
+ /** The id the channel's server returned for this chunk's publish
240
+ * (`ChannelsPublishResult.messageId`); absent when it returned none.
241
+ * Each chunk is one publish, so each carries its own. */
242
+ messageId?: string;
243
+ })
244
+ | (TraceEventBase & {
245
+ /**
246
+ * A stream's end for one destination it published to, a server and
247
+ * its channel, emitted on every exit path once the stream has ended
248
+ * and its deliveries have run, or, when the host stops, before its
249
+ * connections close: `text` is everything its `inference:published`
250
+ * chunks carried there by then, and none follows it. Like those
251
+ * chunks, it's emitted whether or not that server takes the stream,
252
+ * or registers the channel still; the MCPL
253
+ * `channels/outgoing/complete` goes to the server only when it does.
254
+ */
255
+ type: 'inference:published_complete';
256
+ agentName: string;
257
+ serverId: string;
258
+ channelId: string;
259
+ inferenceId: string;
260
+ text: string;
261
+ })
176
262
 
177
263
  // Tool lifecycle
178
264
  | (TraceEventBase & {
@@ -216,10 +302,11 @@ export type TraceEvent =
216
302
  })
217
303
  | (TraceEventBase & {
218
304
  /**
219
- * A writable workspace existed but the oversized-result spill write
220
- * failed (size cap, storeBlob failure, …) — the over-cap tail was NOT
305
+ * The workspace had a writable mount without an include, where spills
306
+ * go, but the oversized-result spill write failed (size cap, storeBlob failure, …) — the over-cap tail was NOT
221
307
  * retained. Distinct from the no-workspace fallback, which is silent
222
- * by design (nothing unexpected happened).
308
+ * by design (nothing unexpected happened), whether the setup has no
309
+ * writable mount or every writable mount has an include, as configured.
223
310
  */
224
311
  type: 'tool:spill_failed';
225
312
  label: string;
@@ -302,6 +389,18 @@ export type TraceEvent =
302
389
  field?: string;
303
390
  detail?: string;
304
391
  })
392
+ /** MCPL content that context-manager won't store, because it doesn't
393
+ * match ContentBlock, reached the store/wake site and was refused there:
394
+ * no row, no wake. `places` is malformedContentPlaces' answer for it. */
395
+ | (TraceEventBase & {
396
+ type: 'mcpl:malformed-content-refused';
397
+ lane: 'push' | 'channel';
398
+ serverId: string;
399
+ places: string[];
400
+ eventId?: string;
401
+ channelId?: string;
402
+ messageId?: string;
403
+ })
305
404
  /** MCPL content with nothing model-visible reached the store/wake site
306
405
  * (module-emitted or coalescer-delivered) and was dropped: no row, no wake. */
307
406
  | (TraceEventBase & {
@@ -442,13 +541,16 @@ export type TraceEvent =
442
541
  })
443
542
 
444
543
  // Host-owned speech routing (ChannelRegistry.routeSpeech). One trace per
445
- // delivered prose segment. `text` is the segment verbatim — the same content
544
+ // delivered prose segment, and one per publish the subconscious's
545
+ // speak_in_channel delivers (ChannelRegistry.publishForAgent): that one's
546
+ // conversationId is the speaking agent's name, and it carries no
547
+ // messageId. `text` is the segment verbatim — the same content
446
548
  // already on the bus chunk-by-chunk via inference:tokens, and mcpl:* traces
447
549
  // are 'ops'-scoped on the webui wire — and `messageId` is the surface's id
448
- // for the posted message (when the MCPL server reports one). Together they
449
- // give external taps the (channelId, messageId, text) tuple needed to edit
450
- // a just-posted message, e.g. a TTS interruption truncating a reply to the
451
- // words actually voiced.
550
+ // for the posted message (on routeSpeech's traces, when the MCPL server
551
+ // reports one). Together they give external taps the (channelId,
552
+ // messageId, text) tuple needed to edit a just-posted message, e.g. a TTS
553
+ // interruption truncating a reply to the words actually voiced.
452
554
  | (TraceEventBase & {
453
555
  type: 'mcpl:speech-routed';
454
556
  /** Agent / conversation whose speech was routed. */
@@ -459,7 +561,8 @@ export type TraceEvent =
459
561
  textLen: number;
460
562
  /** The routed prose segment, verbatim. */
461
563
  text: string;
462
- /** Surface message id (e.g. Discord message id), when reported. */
564
+ /** Surface message id (e.g. Discord message id), when reported, on
565
+ * routeSpeech's traces. speak_in_channel's carry none. */
463
566
  messageId?: string;
464
567
  })
465
568
  | (TraceEventBase & {
@@ -473,6 +576,9 @@ export type TraceEvent =
473
576
  /** `failed`: nothing was posted; `unknown`: the request was dispatched
474
577
  * and no valid receipt came back, so it may have been. */
475
578
  outcome?: 'failed' | 'unknown';
579
+ /** The connector error's structured data, bounded (connectorText),
580
+ * when it sent some: it may say what a multi-part send posted. */
581
+ detail?: string;
476
582
  })
477
583
 
478
584
  // Admin puppet: an operator executed a tool AS an agent and stored the
@@ -3,13 +3,29 @@ export interface SessionUsage {
3
3
  outputTokens: number;
4
4
  cacheCreationTokens: number;
5
5
  cacheReadTokens: number;
6
+ /** The summed estimated cost of the inferences counted here that membrane
7
+ * priced, in one currency. An inference it couldn't price adds nothing, so
8
+ * this is a lower bound when any were unpriced. Absent when none was
9
+ * priced, and from the moment costs in two currencies meet here, since the
10
+ * two can't be summed, which `mixedCurrencies` records. */
6
11
  estimatedCost?: { total: number; currency: string };
12
+ /** Set once inferences priced in two currencies have been counted here:
13
+ * `estimatedCost` stays absent from then on, since the two can't be
14
+ * summed and a later cost alone isn't this tally's. Stored and restored
15
+ * with it, and carried into the shared ephemeral entry when stored
16
+ * entries fold into it. */
17
+ mixedCurrencies?: true;
7
18
  }
8
19
 
9
20
  export interface AgentUsage {
10
21
  agentName: string;
11
22
  usage: SessionUsage;
12
23
  inferenceCount: number;
24
+ /** Set on the one entry that holds every ephemeral agent's usage together
25
+ * (agentName EPHEMERAL_USAGE_NAME), apart from any agent of that name. Each
26
+ * ephemeral agent runs under a new name, so an entry per name would grow
27
+ * the record by one for every run, for good. */
28
+ ephemeral?: true;
13
29
  }
14
30
 
15
31
  export interface SessionUsageSnapshot {
@@ -7,10 +7,13 @@ function snapshotUsage(usage: SessionUsage): SessionUsage {
7
7
  };
8
8
  }
9
9
 
10
+ /** Add one count into a tally. `mixedCurrencies`: what's added is itself a
11
+ * tally in which two currencies met, as a stored entry being folded can be. */
10
12
  function accumulateUsage(
11
13
  target: SessionUsage,
12
14
  tokens: { inputTokens: number; outputTokens: number; cacheCreationTokens: number; cacheReadTokens: number },
13
- estimatedCost?: { total: number; currency: string }
15
+ estimatedCost?: { total: number; currency: string },
16
+ mixedCurrencies = false,
14
17
  ): void {
15
18
  // A provider's usage can lack a count (membrane 0.5.87's Bedrock stream
16
19
  // takes a message_delta's output count as reported); adding it would make
@@ -20,14 +23,16 @@ function accumulateUsage(
20
23
  target.outputTokens += count(tokens.outputTokens);
21
24
  target.cacheCreationTokens += count(tokens.cacheCreationTokens);
22
25
  target.cacheReadTokens += count(tokens.cacheReadTokens);
26
+ // Costs in two currencies can't be summed, and a cost counted after they
27
+ // met is only part of this tally's, so once they meet the cost stays absent.
28
+ if (target.mixedCurrencies) return;
29
+ if (mixedCurrencies || (estimatedCost && target.estimatedCost && target.estimatedCost.currency !== estimatedCost.currency)) {
30
+ target.estimatedCost = undefined;
31
+ target.mixedCurrencies = true;
32
+ return;
33
+ }
23
34
  if (estimatedCost) {
24
- if (!target.estimatedCost) {
25
- target.estimatedCost = { total: 0, currency: estimatedCost.currency };
26
- } else if (target.estimatedCost.currency !== estimatedCost.currency) {
27
- // Mixed currencies — drop cost tracking rather than produce garbage
28
- target.estimatedCost = undefined;
29
- return;
30
- }
35
+ target.estimatedCost ??= { total: 0, currency: estimatedCost.currency };
31
36
  target.estimatedCost.total += estimatedCost.total;
32
37
  }
33
38
  }
@@ -47,18 +52,35 @@ function parseUsage(raw: unknown): SessionUsage | null {
47
52
  outputTokens,
48
53
  cacheCreationTokens: typeof o.cacheCreationTokens === 'number' ? o.cacheCreationTokens : 0,
49
54
  cacheReadTokens: typeof o.cacheReadTokens === 'number' ? o.cacheReadTokens : 0,
50
- estimatedCost: o.estimatedCost && typeof o.estimatedCost === 'object'
51
- ? { total: (o.estimatedCost as any).total, currency: (o.estimatedCost as any).currency }
52
- : undefined,
55
+ ...(o.mixedCurrencies === true
56
+ ? { mixedCurrencies: true as const }
57
+ : {
58
+ estimatedCost: o.estimatedCost && typeof o.estimatedCost === 'object'
59
+ ? { total: (o.estimatedCost as any).total, currency: (o.estimatedCost as any).currency }
60
+ : undefined,
61
+ }),
53
62
  };
54
63
  }
55
64
 
56
65
  export interface PersistedUsageState {
57
66
  totals: SessionUsage;
58
- byAgent: Array<{ agentName: string; usage: SessionUsage; inferenceCount: number }>;
67
+ byAgent: Array<{ agentName: string; usage: SessionUsage; inferenceCount: number; ephemeral?: true }>;
59
68
  inferenceCount: number;
60
69
  }
61
70
 
71
+ /** The name the entry for every ephemeral agent's usage together goes by. */
72
+ export const EPHEMERAL_USAGE_NAME = 'ephemeral agents';
73
+
74
+ type Tally = { usage: SessionUsage; inferenceCount: number };
75
+
76
+ const emptyUsage = (): SessionUsage => ({ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 });
77
+
78
+ /** Add one tally's stored usage and inference count into another. */
79
+ function addTally(target: Tally, usage: SessionUsage, inferenceCount: number): void {
80
+ accumulateUsage(target.usage, usage, usage.estimatedCost, usage.mixedCurrencies === true);
81
+ target.inferenceCount += inferenceCount;
82
+ }
83
+
62
84
  /** What keeps a stored usage record from restoring, or undefined when it
63
85
  * restores: it must be an object whose totals are token counts and whose
64
86
  * byAgent, if it has one, is a list. Its per-agent entries are checked one
@@ -72,22 +94,30 @@ export function persistedUsageProblem(value: unknown): 'not a record' | 'totals'
72
94
  }
73
95
 
74
96
  export class UsageTracker {
75
- private totals: SessionUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
76
- private byAgent: Map<string, { usage: SessionUsage; inferenceCount: number }> = new Map();
97
+ private totals: SessionUsage = emptyUsage();
98
+ private byAgent: Map<string, Tally> = new Map();
99
+ /** Every ephemeral agent's usage together, kept apart from byAgent so an
100
+ * agent named EPHEMERAL_USAGE_NAME has an entry of its own. */
101
+ private ephemeral: Tally | undefined;
77
102
  private inferenceCount = 0;
78
103
  private emitTrace: (event: UsageUpdatedEvent) => void;
79
104
 
105
+ /** `wasEphemeral` names the stored unmarked entries that belong in the
106
+ * shared entry, such as an ephemeral agent's own from before ephemeral
107
+ * agents shared one: each restores into the shared entry instead. A stored
108
+ * entry marked ephemeral restores into it whatever its name. */
80
109
  constructor(opts: {
81
110
  emitTrace: (event: UsageUpdatedEvent) => void;
82
111
  restored?: PersistedUsageState;
112
+ wasEphemeral?: (agentName: string) => boolean;
83
113
  }) {
84
114
  this.emitTrace = opts.emitTrace;
85
115
  if (opts.restored) {
86
- this.restoreFrom(opts.restored);
116
+ this.restoreFrom(opts.restored, opts.wasEphemeral ?? (() => false));
87
117
  }
88
118
  }
89
119
 
90
- private restoreFrom(state: PersistedUsageState): void {
120
+ private restoreFrom(state: PersistedUsageState, wasEphemeral: (agentName: string) => boolean): void {
91
121
  const totals = parseUsage(state.totals);
92
122
  if (!totals) return;
93
123
  this.totals = totals;
@@ -95,36 +125,43 @@ export class UsageTracker {
95
125
  if (Array.isArray(state.byAgent)) {
96
126
  for (const item of state.byAgent as unknown[]) {
97
127
  if (!item || typeof item !== 'object') continue;
98
- const entry = item as { agentName?: unknown; usage?: unknown; inferenceCount?: unknown };
128
+ const entry = item as { agentName?: unknown; usage?: unknown; inferenceCount?: unknown; ephemeral?: unknown };
99
129
  const usage = parseUsage(entry.usage);
100
130
  if (usage && typeof entry.agentName === 'string') {
101
- this.byAgent.set(entry.agentName, {
102
- usage,
103
- inferenceCount: typeof entry.inferenceCount === 'number' ? entry.inferenceCount : 0,
104
- });
131
+ const inferenceCount = typeof entry.inferenceCount === 'number' ? entry.inferenceCount : 0;
132
+ if (entry.ephemeral === true || wasEphemeral(entry.agentName)) {
133
+ this.ephemeral ??= { usage: emptyUsage(), inferenceCount: 0 };
134
+ addTally(this.ephemeral, usage, inferenceCount);
135
+ } else {
136
+ this.byAgent.set(entry.agentName, { usage, inferenceCount });
137
+ }
105
138
  }
106
139
  }
107
140
  }
141
+ // The totals count every entry's inferences, so stored entries whose
142
+ // costs are in two currencies, or one where two met, mean two met in the
143
+ // totals too. A record written before mixedCurrencies shows it only so.
144
+ const tallies = [...this.byAgent.values(), ...(this.ephemeral ? [this.ephemeral] : [])].map((tally) => tally.usage);
145
+ const currencies = new Set(tallies.flatMap((usage) => (usage.estimatedCost ? [usage.estimatedCost.currency] : [])));
146
+ if (this.totals.estimatedCost) currencies.add(this.totals.estimatedCost.currency);
147
+ if (!this.totals.mixedCurrencies && (currencies.size > 1 || tallies.some((usage) => usage.mixedCurrencies))) {
148
+ this.totals.estimatedCost = undefined;
149
+ this.totals.mixedCurrencies = true;
150
+ }
108
151
  }
109
152
 
110
153
  toJSON(): PersistedUsageState {
111
- return {
112
- totals: snapshotUsage(this.totals),
113
- byAgent: [...this.byAgent.entries()].map(([name, data]) => ({
114
- agentName: name,
115
- usage: snapshotUsage(data.usage),
116
- inferenceCount: data.inferenceCount,
117
- })),
118
- inferenceCount: this.inferenceCount,
119
- };
154
+ return this.getSnapshot();
120
155
  }
121
156
 
157
+ /** `ephemeral`: the agent is an ephemeral one, whose usage counts in the
158
+ * shared entry; the trace still names the agent. */
122
159
  onInferenceCompleted(agentName: string, usage: {
123
160
  inputTokens: number;
124
161
  outputTokens: number;
125
162
  cacheCreationTokens?: number;
126
163
  cacheReadTokens?: number;
127
- }, estimatedCost?: { total: number; currency: string }): void {
164
+ }, estimatedCost?: { total: number; currency: string }, ephemeral = false): void {
128
165
  const tokens = {
129
166
  inputTokens: usage.inputTokens,
130
167
  outputTokens: usage.outputTokens,
@@ -135,10 +172,11 @@ export class UsageTracker {
135
172
  accumulateUsage(this.totals, tokens, estimatedCost);
136
173
  this.inferenceCount++;
137
174
 
138
- let agent = this.byAgent.get(agentName);
175
+ let agent = ephemeral ? this.ephemeral : this.byAgent.get(agentName);
139
176
  if (!agent) {
140
- agent = { usage: { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 }, inferenceCount: 0 };
141
- this.byAgent.set(agentName, agent);
177
+ agent = { usage: emptyUsage(), inferenceCount: 0 };
178
+ if (ephemeral) this.ephemeral = agent;
179
+ else this.byAgent.set(agentName, agent);
142
180
  }
143
181
  accumulateUsage(agent.usage, tokens, estimatedCost);
144
182
  agent.inferenceCount++;
@@ -152,14 +190,19 @@ export class UsageTracker {
152
190
  }
153
191
 
154
192
  getSnapshot(): SessionUsageSnapshot {
155
- return {
156
- totals: snapshotUsage(this.totals),
157
- byAgent: [...this.byAgent.entries()].map(([name, data]) => ({
158
- agentName: name,
159
- usage: snapshotUsage(data.usage),
160
- inferenceCount: data.inferenceCount,
161
- })),
162
- inferenceCount: this.inferenceCount,
163
- };
193
+ const byAgent: SessionUsageSnapshot['byAgent'] = [...this.byAgent.entries()].map(([name, data]) => ({
194
+ agentName: name,
195
+ usage: snapshotUsage(data.usage),
196
+ inferenceCount: data.inferenceCount,
197
+ }));
198
+ if (this.ephemeral) {
199
+ byAgent.push({
200
+ agentName: EPHEMERAL_USAGE_NAME,
201
+ usage: snapshotUsage(this.ephemeral.usage),
202
+ inferenceCount: this.ephemeral.inferenceCount,
203
+ ephemeral: true,
204
+ });
205
+ }
206
+ return { totals: snapshotUsage(this.totals), byAgent, inferenceCount: this.inferenceCount };
164
207
  }
165
208
  }