@animalabs/agent-framework 0.22.2 → 0.22.4

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 (940) hide show
  1. package/README.md +3 -1
  2. package/dist/src/agent.d.ts +77 -12
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +143 -41
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.js +1 -1
  7. package/dist/src/api/mcp-server-core.js.map +1 -1
  8. package/dist/src/api/server.d.ts.map +1 -1
  9. package/dist/src/api/server.js +5 -1
  10. package/dist/src/api/server.js.map +1 -1
  11. package/dist/src/api/types.d.ts +1 -1
  12. package/dist/src/api/types.d.ts.map +1 -1
  13. package/dist/src/branch-names.d.ts +14 -0
  14. package/dist/src/branch-names.d.ts.map +1 -0
  15. package/dist/src/branch-names.js +22 -0
  16. package/dist/src/branch-names.js.map +1 -0
  17. package/dist/src/closest-names.d.ts +28 -0
  18. package/dist/src/closest-names.d.ts.map +1 -0
  19. package/dist/src/closest-names.js +98 -0
  20. package/dist/src/closest-names.js.map +1 -0
  21. package/dist/src/code-execution/py-runner.d.ts +16 -5
  22. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  23. package/dist/src/code-execution/py-runner.js +186 -74
  24. package/dist/src/code-execution/py-runner.js.map +1 -1
  25. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  26. package/dist/src/code-execution/runtime-py.js +49 -3
  27. package/dist/src/code-execution/runtime-py.js.map +1 -1
  28. package/dist/src/code-execution/tool-definition.d.ts +12 -1
  29. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  30. package/dist/src/code-execution/tool-definition.js +21 -3
  31. package/dist/src/code-execution/tool-definition.js.map +1 -1
  32. package/dist/src/config-checks.d.ts +28 -0
  33. package/dist/src/config-checks.d.ts.map +1 -0
  34. package/dist/src/config-checks.js +52 -0
  35. package/dist/src/config-checks.js.map +1 -0
  36. package/dist/src/deadline-timer.d.ts +21 -0
  37. package/dist/src/deadline-timer.d.ts.map +1 -0
  38. package/dist/src/deadline-timer.js +43 -0
  39. package/dist/src/deadline-timer.js.map +1 -0
  40. package/dist/src/error-message.d.ts +30 -0
  41. package/dist/src/error-message.d.ts.map +1 -0
  42. package/dist/src/error-message.js +50 -0
  43. package/dist/src/error-message.js.map +1 -0
  44. package/dist/src/framework.d.ts +828 -88
  45. package/dist/src/framework.d.ts.map +1 -1
  46. package/dist/src/framework.js +5011 -1326
  47. package/dist/src/framework.js.map +1 -1
  48. package/dist/src/gate/event-gate.d.ts +8 -0
  49. package/dist/src/gate/event-gate.d.ts.map +1 -1
  50. package/dist/src/gate/event-gate.js +27 -3
  51. package/dist/src/gate/event-gate.js.map +1 -1
  52. package/dist/src/index.d.ts +4 -2
  53. package/dist/src/index.d.ts.map +1 -1
  54. package/dist/src/index.js +4 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/mcpl/channel-registry.d.ts +144 -6
  57. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  58. package/dist/src/mcpl/channel-registry.js +424 -263
  59. package/dist/src/mcpl/channel-registry.js.map +1 -1
  60. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  61. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  62. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  63. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  64. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  65. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  66. package/dist/src/mcpl/conversation-router.js +23 -8
  67. package/dist/src/mcpl/conversation-router.js.map +1 -1
  68. package/dist/src/mcpl/convert-block.d.ts +20 -0
  69. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  70. package/dist/src/mcpl/convert-block.js +89 -0
  71. package/dist/src/mcpl/convert-block.js.map +1 -0
  72. package/dist/src/mcpl/errors.d.ts +13 -0
  73. package/dist/src/mcpl/errors.d.ts.map +1 -1
  74. package/dist/src/mcpl/errors.js +28 -0
  75. package/dist/src/mcpl/errors.js.map +1 -1
  76. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  77. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  78. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  79. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  80. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  81. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  82. package/dist/src/mcpl/inbound-source.js +83 -0
  83. package/dist/src/mcpl/inbound-source.js.map +1 -0
  84. package/dist/src/mcpl/index.d.ts +3 -1
  85. package/dist/src/mcpl/index.d.ts.map +1 -1
  86. package/dist/src/mcpl/index.js +3 -1
  87. package/dist/src/mcpl/index.js.map +1 -1
  88. package/dist/src/mcpl/inference-router.d.ts +15 -11
  89. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  90. package/dist/src/mcpl/inference-router.js +153 -84
  91. package/dist/src/mcpl/inference-router.js.map +1 -1
  92. package/dist/src/mcpl/push-coalescer.d.ts +33 -11
  93. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  94. package/dist/src/mcpl/push-coalescer.js +52 -20
  95. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  96. package/dist/src/mcpl/push-handler.d.ts +16 -9
  97. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  98. package/dist/src/mcpl/push-handler.js +18 -83
  99. package/dist/src/mcpl/push-handler.js.map +1 -1
  100. package/dist/src/mcpl/reference-fetcher.d.ts +14 -2
  101. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  102. package/dist/src/mcpl/reference-fetcher.js +72 -30
  103. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  104. package/dist/src/mcpl/references.d.ts +47 -12
  105. package/dist/src/mcpl/references.d.ts.map +1 -1
  106. package/dist/src/mcpl/references.js +74 -21
  107. package/dist/src/mcpl/references.js.map +1 -1
  108. package/dist/src/mcpl/server-connection.d.ts +89 -10
  109. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  110. package/dist/src/mcpl/server-connection.js +405 -129
  111. package/dist/src/mcpl/server-connection.js.map +1 -1
  112. package/dist/src/mcpl/server-registry.d.ts +10 -1
  113. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  114. package/dist/src/mcpl/server-registry.js +14 -3
  115. package/dist/src/mcpl/server-registry.js.map +1 -1
  116. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  117. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  118. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  119. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  120. package/dist/src/mcpl/transport.d.ts +51 -2
  121. package/dist/src/mcpl/transport.d.ts.map +1 -1
  122. package/dist/src/mcpl/transport.js +114 -20
  123. package/dist/src/mcpl/transport.js.map +1 -1
  124. package/dist/src/mcpl/types.d.ts +14 -3
  125. package/dist/src/mcpl/types.d.ts.map +1 -1
  126. package/dist/src/mcpl/types.js.map +1 -1
  127. package/dist/src/mcpl/visible-content.d.ts +15 -0
  128. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  129. package/dist/src/mcpl/visible-content.js +25 -0
  130. package/dist/src/mcpl/visible-content.js.map +1 -1
  131. package/dist/src/module-registry.d.ts +19 -1
  132. package/dist/src/module-registry.d.ts.map +1 -1
  133. package/dist/src/module-registry.js +54 -25
  134. package/dist/src/module-registry.js.map +1 -1
  135. package/dist/src/modules/api/index.js +1 -1
  136. package/dist/src/modules/api/index.js.map +1 -1
  137. package/dist/src/modules/discord/discord-js-client.d.ts +1 -0
  138. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  139. package/dist/src/modules/discord/discord-js-client.js +25 -9
  140. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  141. package/dist/src/modules/discord/index.d.ts +29 -3
  142. package/dist/src/modules/discord/index.d.ts.map +1 -1
  143. package/dist/src/modules/discord/index.js +379 -143
  144. package/dist/src/modules/discord/index.js.map +1 -1
  145. package/dist/src/modules/discord/types.d.ts +14 -1
  146. package/dist/src/modules/discord/types.d.ts.map +1 -1
  147. package/dist/src/modules/health/index.d.ts.map +1 -1
  148. package/dist/src/modules/health/index.js +2 -1
  149. package/dist/src/modules/health/index.js.map +1 -1
  150. package/dist/src/modules/history/index.d.ts +3 -1
  151. package/dist/src/modules/history/index.d.ts.map +1 -1
  152. package/dist/src/modules/history/index.js +359 -127
  153. package/dist/src/modules/history/index.js.map +1 -1
  154. package/dist/src/modules/history/semantic.d.ts +28 -1
  155. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  156. package/dist/src/modules/history/semantic.js +177 -32
  157. package/dist/src/modules/history/semantic.js.map +1 -1
  158. package/dist/src/modules/unknown-tool.d.ts +10 -0
  159. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  160. package/dist/src/modules/unknown-tool.js +17 -0
  161. package/dist/src/modules/unknown-tool.js.map +1 -0
  162. package/dist/src/modules/workspace/disk-agreement.d.ts +32 -2
  163. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  164. package/dist/src/modules/workspace/disk-agreement.js +30 -2
  165. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  166. package/dist/src/modules/workspace/effects.d.ts +64 -6
  167. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  168. package/dist/src/modules/workspace/effects.js +291 -44
  169. package/dist/src/modules/workspace/effects.js.map +1 -1
  170. package/dist/src/modules/workspace/index.d.ts +119 -23
  171. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  172. package/dist/src/modules/workspace/index.js +793 -247
  173. package/dist/src/modules/workspace/index.js.map +1 -1
  174. package/dist/src/modules/workspace/observe.d.ts +75 -8
  175. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  176. package/dist/src/modules/workspace/observe.js +174 -14
  177. package/dist/src/modules/workspace/observe.js.map +1 -1
  178. package/dist/src/modules/workspace/reconcile.d.ts +58 -7
  179. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  180. package/dist/src/modules/workspace/reconcile.js +521 -56
  181. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  182. package/dist/src/modules/workspace/types.d.ts +18 -4
  183. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  184. package/dist/src/modules/workspace/types.js.map +1 -1
  185. package/dist/src/modules/workspace/watcher.d.ts +7 -2
  186. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  187. package/dist/src/modules/workspace/watcher.js +28 -4
  188. package/dist/src/modules/workspace/watcher.js.map +1 -1
  189. package/dist/src/prose-drafts.d.ts +240 -0
  190. package/dist/src/prose-drafts.d.ts.map +1 -0
  191. package/dist/src/prose-drafts.js +362 -0
  192. package/dist/src/prose-drafts.js.map +1 -0
  193. package/dist/src/prose-segments.d.ts +21 -2
  194. package/dist/src/prose-segments.d.ts.map +1 -1
  195. package/dist/src/prose-segments.js +46 -9
  196. package/dist/src/prose-segments.js.map +1 -1
  197. package/dist/src/recovery/offline-branch.d.ts +1 -0
  198. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  199. package/dist/src/recovery/offline-branch.js +3 -1
  200. package/dist/src/recovery/offline-branch.js.map +1 -1
  201. package/dist/src/safe-slice.d.ts +5 -1
  202. package/dist/src/safe-slice.d.ts.map +1 -1
  203. package/dist/src/safe-slice.js +9 -1
  204. package/dist/src/safe-slice.js.map +1 -1
  205. package/dist/src/thinking-token-stamp.d.ts +12 -0
  206. package/dist/src/thinking-token-stamp.d.ts.map +1 -1
  207. package/dist/src/thinking-token-stamp.js +34 -3
  208. package/dist/src/thinking-token-stamp.js.map +1 -1
  209. package/dist/src/tool-image-ledger.d.ts +87 -19
  210. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  211. package/dist/src/tool-image-ledger.js +135 -32
  212. package/dist/src/tool-image-ledger.js.map +1 -1
  213. package/dist/src/tool-presentation.d.ts +9 -0
  214. package/dist/src/tool-presentation.d.ts.map +1 -1
  215. package/dist/src/tool-presentation.js +257 -60
  216. package/dist/src/tool-presentation.js.map +1 -1
  217. package/dist/src/tool-result-guard.d.ts.map +1 -1
  218. package/dist/src/tool-result-guard.js +7 -10
  219. package/dist/src/tool-result-guard.js.map +1 -1
  220. package/dist/src/tool-result-history.d.ts +32 -3
  221. package/dist/src/tool-result-history.d.ts.map +1 -1
  222. package/dist/src/tool-result-history.js +106 -19
  223. package/dist/src/tool-result-history.js.map +1 -1
  224. package/dist/src/tune-out/coordinator.d.ts +25 -1
  225. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  226. package/dist/src/tune-out/coordinator.js +260 -65
  227. package/dist/src/tune-out/coordinator.js.map +1 -1
  228. package/dist/src/types/agent.d.ts +17 -5
  229. package/dist/src/types/agent.d.ts.map +1 -1
  230. package/dist/src/types/events.d.ts +17 -0
  231. package/dist/src/types/events.d.ts.map +1 -1
  232. package/dist/src/types/framework.d.ts +21 -5
  233. package/dist/src/types/framework.d.ts.map +1 -1
  234. package/dist/src/types/inference-log.d.ts +1 -1
  235. package/dist/src/types/inference-log.d.ts.map +1 -1
  236. package/dist/src/types/module.d.ts +23 -4
  237. package/dist/src/types/module.d.ts.map +1 -1
  238. package/dist/src/types/trace.d.ts +95 -6
  239. package/dist/src/types/trace.d.ts.map +1 -1
  240. package/dist/src/usage/usage-tracker.d.ts +5 -0
  241. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  242. package/dist/src/usage/usage-tracker.js +35 -8
  243. package/dist/src/usage/usage-tracker.js.map +1 -1
  244. package/package.json +6 -4
  245. package/src/agent.ts +169 -41
  246. package/src/api/mcp-server-core.ts +1 -1
  247. package/src/api/server.ts +4 -1
  248. package/src/api/types.ts +1 -1
  249. package/src/branch-names.ts +19 -0
  250. package/src/closest-names.ts +94 -0
  251. package/src/code-execution/py-runner.ts +200 -76
  252. package/src/code-execution/runtime-py.ts +49 -3
  253. package/src/code-execution/tool-definition.ts +28 -4
  254. package/src/config-checks.ts +48 -0
  255. package/src/deadline-timer.ts +44 -0
  256. package/src/error-message.ts +48 -0
  257. package/src/framework.ts +5352 -1388
  258. package/src/gate/event-gate.ts +28 -3
  259. package/src/index.ts +11 -2
  260. package/src/mcpl/channel-registry.ts +517 -286
  261. package/src/mcpl/checkpoint-manager.ts +104 -29
  262. package/src/mcpl/conversation-router.ts +28 -8
  263. package/src/mcpl/convert-block.ts +97 -0
  264. package/src/mcpl/errors.ts +28 -0
  265. package/src/mcpl/hook-orchestrator.ts +84 -77
  266. package/src/mcpl/inbound-source.ts +156 -0
  267. package/src/mcpl/index.ts +10 -1
  268. package/src/mcpl/inference-router.ts +174 -99
  269. package/src/mcpl/push-coalescer.ts +60 -24
  270. package/src/mcpl/push-handler.ts +20 -90
  271. package/src/mcpl/reference-fetcher.ts +71 -26
  272. package/src/mcpl/references.ts +81 -22
  273. package/src/mcpl/server-connection.ts +427 -132
  274. package/src/mcpl/server-registry.ts +15 -4
  275. package/src/mcpl/skip-reply-wake.ts +73 -0
  276. package/src/mcpl/transport.ts +120 -18
  277. package/src/mcpl/types.ts +14 -3
  278. package/src/mcpl/visible-content.ts +27 -0
  279. package/src/module-registry.ts +58 -23
  280. package/src/modules/api/index.ts +1 -1
  281. package/src/modules/discord/discord-js-client.ts +26 -10
  282. package/src/modules/discord/index.ts +391 -158
  283. package/src/modules/discord/types.ts +15 -2
  284. package/src/modules/health/index.ts +2 -1
  285. package/src/modules/history/index.ts +410 -120
  286. package/src/modules/history/semantic.ts +189 -32
  287. package/src/modules/unknown-tool.ts +16 -0
  288. package/src/modules/workspace/disk-agreement.ts +51 -3
  289. package/src/modules/workspace/effects.ts +279 -35
  290. package/src/modules/workspace/index.ts +810 -260
  291. package/src/modules/workspace/observe.ts +194 -20
  292. package/src/modules/workspace/reconcile.ts +619 -64
  293. package/src/modules/workspace/types.ts +18 -4
  294. package/src/modules/workspace/watcher.ts +27 -4
  295. package/src/prose-drafts.ts +481 -0
  296. package/src/prose-segments.ts +51 -8
  297. package/src/recovery/offline-branch.ts +4 -1
  298. package/src/safe-slice.ts +9 -1
  299. package/src/thinking-token-stamp.ts +34 -3
  300. package/src/tool-image-ledger.ts +153 -34
  301. package/src/tool-presentation.ts +224 -40
  302. package/src/tool-result-guard.ts +7 -8
  303. package/src/tool-result-history.ts +124 -20
  304. package/src/tune-out/coordinator.ts +259 -65
  305. package/src/types/agent.ts +17 -7
  306. package/src/types/events.ts +17 -0
  307. package/src/types/framework.ts +21 -5
  308. package/src/types/inference-log.ts +1 -1
  309. package/src/types/module.ts +24 -4
  310. package/src/types/trace.ts +101 -6
  311. package/src/usage/usage-tracker.ts +32 -8
  312. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  313. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  314. package/dist/test/activation-request-sanitize.test.js +0 -89
  315. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  316. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  317. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  318. package/dist/test/agent-cache-ttl.test.js +0 -41
  319. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  320. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  321. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  322. package/dist/test/agent-runtime-settings.test.js +0 -1598
  323. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  324. package/dist/test/api-admin-gate.test.d.ts +0 -2
  325. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  326. package/dist/test/api-admin-gate.test.js +0 -116
  327. package/dist/test/api-admin-gate.test.js.map +0 -1
  328. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  329. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  330. package/dist/test/assistant-persist-refusal.test.js +0 -63
  331. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  332. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  333. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  334. package/dist/test/batched-wake-locus.test.js +0 -166
  335. package/dist/test/batched-wake-locus.test.js.map +0 -1
  336. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  337. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  338. package/dist/test/busy-agent-scheduler.test.js +0 -92
  339. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  340. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  341. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  342. package/dist/test/channel-incoming-targeting.test.js +0 -81
  343. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  344. package/dist/test/channel-label-history.test.d.ts +0 -2
  345. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  346. package/dist/test/channel-label-history.test.js +0 -424
  347. package/dist/test/channel-label-history.test.js.map +0 -1
  348. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  349. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  350. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  351. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  352. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  353. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  354. package/dist/test/channel-registry-routing.test.js +0 -446
  355. package/dist/test/channel-registry-routing.test.js.map +0 -1
  356. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  357. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  358. package/dist/test/channel-registry-typing.test.js +0 -91
  359. package/dist/test/channel-registry-typing.test.js.map +0 -1
  360. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  361. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  362. package/dist/test/channels-changed-respond-first.test.js +0 -74
  363. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  364. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  365. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  366. package/dist/test/checkpoint-attribution.test.js +0 -200
  367. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  368. package/dist/test/code-execution.test.d.ts +0 -11
  369. package/dist/test/code-execution.test.d.ts.map +0 -1
  370. package/dist/test/code-execution.test.js +0 -1272
  371. package/dist/test/code-execution.test.js.map +0 -1
  372. package/dist/test/component-defaults.test.d.ts +0 -2
  373. package/dist/test/component-defaults.test.d.ts.map +0 -1
  374. package/dist/test/component-defaults.test.js +0 -54
  375. package/dist/test/component-defaults.test.js.map +0 -1
  376. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  377. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  378. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  379. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  380. package/dist/test/conversation-router.test.d.ts +0 -2
  381. package/dist/test/conversation-router.test.d.ts.map +0 -1
  382. package/dist/test/conversation-router.test.js +0 -174
  383. package/dist/test/conversation-router.test.js.map +0 -1
  384. package/dist/test/conversation-routing.test.d.ts +0 -7
  385. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  386. package/dist/test/conversation-routing.test.js +0 -621
  387. package/dist/test/conversation-routing.test.js.map +0 -1
  388. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  389. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  390. package/dist/test/deferred-wake-integrity.test.js +0 -320
  391. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  392. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  393. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  394. package/dist/test/discord-awareness-outbox.test.js +0 -371
  395. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  396. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  397. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  398. package/dist/test/discord-history-delete-window.test.js +0 -81
  399. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  400. package/dist/test/discord-history-sync.test.d.ts +0 -2
  401. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  402. package/dist/test/discord-history-sync.test.js +0 -56
  403. package/dist/test/discord-history-sync.test.js.map +0 -1
  404. package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
  405. package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
  406. package/dist/test/discord-typing-keepalive.test.js +0 -77
  407. package/dist/test/discord-typing-keepalive.test.js.map +0 -1
  408. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  409. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  410. package/dist/test/ephemeral-settle.test.js +0 -354
  411. package/dist/test/ephemeral-settle.test.js.map +0 -1
  412. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  413. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  414. package/dist/test/event-gate-passthrough.test.js +0 -289
  415. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  416. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  417. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  418. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  419. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  420. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  421. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  422. package/dist/test/event-gate-rate-limit.test.js +0 -627
  423. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  424. package/dist/test/event-gate-script.test.d.ts +0 -2
  425. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  426. package/dist/test/event-gate-script.test.js +0 -95
  427. package/dist/test/event-gate-script.test.js.map +0 -1
  428. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  429. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  430. package/dist/test/event-gate-selfwake.test.js +0 -125
  431. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  432. package/dist/test/event-gate-tags.test.d.ts +0 -2
  433. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  434. package/dist/test/event-gate-tags.test.js +0 -76
  435. package/dist/test/event-gate-tags.test.js.map +0 -1
  436. package/dist/test/event-gate.test.d.ts +0 -2
  437. package/dist/test/event-gate.test.d.ts.map +0 -1
  438. package/dist/test/event-gate.test.js +0 -919
  439. package/dist/test/event-gate.test.js.map +0 -1
  440. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  441. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  442. package/dist/test/explicit-prose-routing.test.js +0 -1715
  443. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  444. package/dist/test/framework.test.d.ts +0 -5
  445. package/dist/test/framework.test.d.ts.map +0 -1
  446. package/dist/test/framework.test.js +0 -1048
  447. package/dist/test/framework.test.js.map +0 -1
  448. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  449. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  450. package/dist/test/gather-context-timeout.test.js +0 -120
  451. package/dist/test/gather-context-timeout.test.js.map +0 -1
  452. package/dist/test/health-snapshot.test.d.ts +0 -2
  453. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  454. package/dist/test/health-snapshot.test.js +0 -74
  455. package/dist/test/health-snapshot.test.js.map +0 -1
  456. package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
  457. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  458. package/dist/test/helpers/coalescing-fixture.js +0 -152
  459. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  460. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  461. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  462. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  463. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  464. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  465. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  466. package/dist/test/helpers/mock-membrane.js +0 -168
  467. package/dist/test/helpers/mock-membrane.js.map +0 -1
  468. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  469. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  470. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  471. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  472. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  473. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  474. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  475. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  476. package/dist/test/hide-then-turn.test.d.ts +0 -2
  477. package/dist/test/hide-then-turn.test.d.ts.map +0 -1
  478. package/dist/test/hide-then-turn.test.js +0 -101
  479. package/dist/test/hide-then-turn.test.js.map +0 -1
  480. package/dist/test/history-module-overview.test.d.ts +0 -2
  481. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  482. package/dist/test/history-module-overview.test.js +0 -711
  483. package/dist/test/history-module-overview.test.js.map +0 -1
  484. package/dist/test/history-module-realstore.test.d.ts +0 -2
  485. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  486. package/dist/test/history-module-realstore.test.js +0 -363
  487. package/dist/test/history-module-realstore.test.js.map +0 -1
  488. package/dist/test/history-module-semantic.test.d.ts +0 -2
  489. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  490. package/dist/test/history-module-semantic.test.js +0 -213
  491. package/dist/test/history-module-semantic.test.js.map +0 -1
  492. package/dist/test/history-module-ux.test.d.ts +0 -2
  493. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  494. package/dist/test/history-module-ux.test.js +0 -304
  495. package/dist/test/history-module-ux.test.js.map +0 -1
  496. package/dist/test/history-module.test.d.ts +0 -2
  497. package/dist/test/history-module.test.d.ts.map +0 -1
  498. package/dist/test/history-module.test.js +0 -619
  499. package/dist/test/history-module.test.js.map +0 -1
  500. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  501. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  502. package/dist/test/history-semantic-review-findings.test.js +0 -338
  503. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  504. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  505. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  506. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  507. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  508. package/dist/test/host-quiesce.test.d.ts +0 -2
  509. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  510. package/dist/test/host-quiesce.test.js +0 -988
  511. package/dist/test/host-quiesce.test.js.map +0 -1
  512. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  513. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  514. package/dist/test/inference-exhaustion-rewind.test.js +0 -387
  515. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  516. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  517. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  518. package/dist/test/inference-failure-bounds.test.js +0 -1202
  519. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  520. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  521. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  522. package/dist/test/inference-failure-observability.test.js +0 -84
  523. package/dist/test/inference-failure-observability.test.js.map +0 -1
  524. package/dist/test/journal-tool.test.d.ts +0 -2
  525. package/dist/test/journal-tool.test.d.ts.map +0 -1
  526. package/dist/test/journal-tool.test.js +0 -33
  527. package/dist/test/journal-tool.test.js.map +0 -1
  528. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  529. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  530. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  531. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  532. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  533. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  534. package/dist/test/kv-unified-wiring.test.js +0 -95
  535. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  536. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  537. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  538. package/dist/test/late-events-after-stop.test.js +0 -122
  539. package/dist/test/late-events-after-stop.test.js.map +0 -1
  540. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  541. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  542. package/dist/test/liveness-watchdog.test.js +0 -94
  543. package/dist/test/liveness-watchdog.test.js.map +0 -1
  544. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  545. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  546. package/dist/test/machine-close-provenance.test.js +0 -116
  547. package/dist/test/machine-close-provenance.test.js.map +0 -1
  548. package/dist/test/maintenance-timer.test.d.ts +0 -2
  549. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  550. package/dist/test/maintenance-timer.test.js +0 -1827
  551. package/dist/test/maintenance-timer.test.js.map +0 -1
  552. package/dist/test/mcp-server-bin.test.d.ts +0 -2
  553. package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
  554. package/dist/test/mcp-server-bin.test.js +0 -142
  555. package/dist/test/mcp-server-bin.test.js.map +0 -1
  556. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  557. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  558. package/dist/test/mcpl-access-provider.test.js +0 -20
  559. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  560. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  561. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  562. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  563. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  564. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  565. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  566. package/dist/test/mcpl-capability-grant.test.js +0 -111
  567. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  568. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  569. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  570. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  571. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  572. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  573. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  574. package/dist/test/mcpl-child-env.test.js +0 -158
  575. package/dist/test/mcpl-child-env.test.js.map +0 -1
  576. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
  577. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
  578. package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
  579. package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
  580. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  581. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  582. package/dist/test/mcpl-coalescing.test.js +0 -1125
  583. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  584. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  585. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  586. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  587. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  588. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  589. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  590. package/dist/test/mcpl-empty-content.test.js +0 -368
  591. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  592. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  593. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  594. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  595. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  596. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  597. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  598. package/dist/test/mcpl-list-status.test.js +0 -82
  599. package/dist/test/mcpl-list-status.test.js.map +0 -1
  600. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  601. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  602. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  603. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  604. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  605. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  606. package/dist/test/mcpl-push-convert.test.js +0 -42
  607. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  608. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  609. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  610. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  611. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  612. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  613. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  614. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
  615. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  616. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  617. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  618. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  619. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  620. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  621. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  622. package/dist/test/mcpl-reference-fields.test.js +0 -149
  623. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  624. package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
  625. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
  626. package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
  627. package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
  628. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  629. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  630. package/dist/test/mcpl-request-timeout.test.js +0 -138
  631. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  632. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  633. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  634. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  635. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  636. package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
  637. package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
  638. package/dist/test/mcpl-stderr-tail.test.js +0 -210
  639. package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
  640. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  641. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  642. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  643. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  644. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  645. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  646. package/dist/test/mcpl-tool-classes.test.js +0 -80
  647. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  648. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  649. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  650. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  651. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  652. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  653. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  654. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  655. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  656. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  657. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  658. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  659. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  660. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  661. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  662. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  663. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  664. package/dist/test/mcpl-transport-close.test.d.ts +0 -2
  665. package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
  666. package/dist/test/mcpl-transport-close.test.js +0 -373
  667. package/dist/test/mcpl-transport-close.test.js.map +0 -1
  668. package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
  669. package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
  670. package/dist/test/mcpl-transport-spawn.test.js +0 -58
  671. package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
  672. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  673. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  674. package/dist/test/mcpl-ws-transport.test.js +0 -259
  675. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  676. package/dist/test/module-context-integration.test.d.ts +0 -18
  677. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  678. package/dist/test/module-context-integration.test.js +0 -181
  679. package/dist/test/module-context-integration.test.js.map +0 -1
  680. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  681. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  682. package/dist/test/offline-recovery-branch.test.js +0 -570
  683. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  684. package/dist/test/operator-surgery.test.d.ts +0 -2
  685. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  686. package/dist/test/operator-surgery.test.js +0 -595
  687. package/dist/test/operator-surgery.test.js.map +0 -1
  688. package/dist/test/ops-alert.test.d.ts +0 -2
  689. package/dist/test/ops-alert.test.d.ts.map +0 -1
  690. package/dist/test/ops-alert.test.js +0 -153
  691. package/dist/test/ops-alert.test.js.map +0 -1
  692. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  693. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  694. package/dist/test/overbudget-drain-breaker.test.js +0 -198
  695. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  696. package/dist/test/pack-installs-alone.test.d.ts +0 -2
  697. package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
  698. package/dist/test/pack-installs-alone.test.js +0 -284
  699. package/dist/test/pack-installs-alone.test.js.map +0 -1
  700. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  701. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  702. package/dist/test/per-agent-delivery.test.js +0 -108
  703. package/dist/test/per-agent-delivery.test.js.map +0 -1
  704. package/dist/test/physical-window-restart.test.d.ts +0 -16
  705. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  706. package/dist/test/physical-window-restart.test.js +0 -487
  707. package/dist/test/physical-window-restart.test.js.map +0 -1
  708. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  709. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  710. package/dist/test/placed-reaction-baseline.test.js +0 -312
  711. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  712. package/dist/test/present-while-acting.test.d.ts +0 -19
  713. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  714. package/dist/test/present-while-acting.test.js +0 -1649
  715. package/dist/test/present-while-acting.test.js.map +0 -1
  716. package/dist/test/prose-segments.test.d.ts +0 -2
  717. package/dist/test/prose-segments.test.d.ts.map +0 -1
  718. package/dist/test/prose-segments.test.js +0 -52
  719. package/dist/test/prose-segments.test.js.map +0 -1
  720. package/dist/test/prose-stream-router.test.d.ts +0 -2
  721. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  722. package/dist/test/prose-stream-router.test.js +0 -171
  723. package/dist/test/prose-stream-router.test.js.map +0 -1
  724. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  725. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  726. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  727. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  728. package/dist/test/provider-waits.test.d.ts +0 -2
  729. package/dist/test/provider-waits.test.d.ts.map +0 -1
  730. package/dist/test/provider-waits.test.js +0 -2259
  731. package/dist/test/provider-waits.test.js.map +0 -1
  732. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  733. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  734. package/dist/test/puppet-tool-call.test.js +0 -226
  735. package/dist/test/puppet-tool-call.test.js.map +0 -1
  736. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  737. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  738. package/dist/test/puppet-turn-reservation.test.js +0 -139
  739. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  740. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  741. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  742. package/dist/test/reaction-baseline-platform.test.js +0 -124
  743. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  744. package/dist/test/record-journal.test.d.ts +0 -6
  745. package/dist/test/record-journal.test.d.ts.map +0 -1
  746. package/dist/test/record-journal.test.js +0 -269
  747. package/dist/test/record-journal.test.js.map +0 -1
  748. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  749. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  750. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  751. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  752. package/dist/test/refusal-rewind.test.d.ts +0 -2
  753. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  754. package/dist/test/refusal-rewind.test.js +0 -117
  755. package/dist/test/refusal-rewind.test.js.map +0 -1
  756. package/dist/test/refusal-single-call.test.d.ts +0 -2
  757. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  758. package/dist/test/refusal-single-call.test.js +0 -177
  759. package/dist/test/refusal-single-call.test.js.map +0 -1
  760. package/dist/test/release-bump.test.d.ts +0 -2
  761. package/dist/test/release-bump.test.d.ts.map +0 -1
  762. package/dist/test/release-bump.test.js +0 -248
  763. package/dist/test/release-bump.test.js.map +0 -1
  764. package/dist/test/release-changelog.test.d.ts +0 -2
  765. package/dist/test/release-changelog.test.d.ts.map +0 -1
  766. package/dist/test/release-changelog.test.js +0 -159
  767. package/dist/test/release-changelog.test.js.map +0 -1
  768. package/dist/test/release-holds.test.d.ts +0 -2
  769. package/dist/test/release-holds.test.d.ts.map +0 -1
  770. package/dist/test/release-holds.test.js +0 -456
  771. package/dist/test/release-holds.test.js.map +0 -1
  772. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  773. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  774. package/dist/test/same-round-think-policy.test.js +0 -853
  775. package/dist/test/same-round-think-policy.test.js.map +0 -1
  776. package/dist/test/save-recent-image.test.d.ts +0 -26
  777. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  778. package/dist/test/save-recent-image.test.js +0 -974
  779. package/dist/test/save-recent-image.test.js.map +0 -1
  780. package/dist/test/shutdown-errors.test.d.ts +0 -2
  781. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  782. package/dist/test/shutdown-errors.test.js +0 -662
  783. package/dist/test/shutdown-errors.test.js.map +0 -1
  784. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  785. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  786. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  787. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  788. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  789. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  790. package/dist/test/silent-heartbeat.test.js +0 -165
  791. package/dist/test/silent-heartbeat.test.js.map +0 -1
  792. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  793. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  794. package/dist/test/state-scaling-e2e.test.js +0 -206
  795. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  796. package/dist/test/state-scaling.test.d.ts +0 -12
  797. package/dist/test/state-scaling.test.d.ts.map +0 -1
  798. package/dist/test/state-scaling.test.js +0 -251
  799. package/dist/test/state-scaling.test.js.map +0 -1
  800. package/dist/test/stop-with-host-operations.test.d.ts +0 -20
  801. package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
  802. package/dist/test/stop-with-host-operations.test.js +0 -1174
  803. package/dist/test/stop-with-host-operations.test.js.map +0 -1
  804. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  805. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  806. package/dist/test/stop-with-work-in-flight.test.js +0 -784
  807. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  808. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  809. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  810. package/dist/test/surgery-marks-receipt.test.js +0 -540
  811. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  812. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  813. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  814. package/dist/test/thinking-token-stamp.test.js +0 -129
  815. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  816. package/dist/test/timezone.test.d.ts +0 -2
  817. package/dist/test/timezone.test.d.ts.map +0 -1
  818. package/dist/test/timezone.test.js +0 -18
  819. package/dist/test/timezone.test.js.map +0 -1
  820. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  821. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  822. package/dist/test/tool-batch-hook.test.js +0 -275
  823. package/dist/test/tool-batch-hook.test.js.map +0 -1
  824. package/dist/test/tool-policy.test.d.ts +0 -2
  825. package/dist/test/tool-policy.test.d.ts.map +0 -1
  826. package/dist/test/tool-policy.test.js +0 -51
  827. package/dist/test/tool-policy.test.js.map +0 -1
  828. package/dist/test/tool-presentation.test.d.ts +0 -2
  829. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  830. package/dist/test/tool-presentation.test.js +0 -268
  831. package/dist/test/tool-presentation.test.js.map +0 -1
  832. package/dist/test/tool-result-content.test.d.ts +0 -2
  833. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  834. package/dist/test/tool-result-content.test.js +0 -37
  835. package/dist/test/tool-result-content.test.js.map +0 -1
  836. package/dist/test/tool-result-guard.test.d.ts +0 -2
  837. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  838. package/dist/test/tool-result-guard.test.js +0 -3729
  839. package/dist/test/tool-result-guard.test.js.map +0 -1
  840. package/dist/test/tool-result-history.test.d.ts +0 -7
  841. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  842. package/dist/test/tool-result-history.test.js +0 -156
  843. package/dist/test/tool-result-history.test.js.map +0 -1
  844. package/dist/test/tool-result-spill.test.d.ts +0 -13
  845. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  846. package/dist/test/tool-result-spill.test.js +0 -586
  847. package/dist/test/tool-result-spill.test.js.map +0 -1
  848. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  849. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  850. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  851. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  852. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  853. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  854. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  855. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  856. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  857. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  858. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  859. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  860. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  861. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  862. package/dist/test/trunk-channel-routing.test.js +0 -652
  863. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  864. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  865. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  866. package/dist/test/tune-out-e2e.test.js +0 -142
  867. package/dist/test/tune-out-e2e.test.js.map +0 -1
  868. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  869. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  870. package/dist/test/tune-out-gate-composition.test.js +0 -221
  871. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  872. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  873. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  874. package/dist/test/tune-out-lifecycle.test.js +0 -121
  875. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  876. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  877. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  878. package/dist/test/turn-trigger-provenance.test.js +0 -137
  879. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  880. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  881. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  882. package/dist/test/unconvertible-tool-result.test.js +0 -358
  883. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  884. package/dist/test/unsent-compiles.test.d.ts +0 -2
  885. package/dist/test/unsent-compiles.test.d.ts.map +0 -1
  886. package/dist/test/unsent-compiles.test.js +0 -139
  887. package/dist/test/unsent-compiles.test.js.map +0 -1
  888. package/dist/test/usage-accounting.test.d.ts +0 -2
  889. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  890. package/dist/test/usage-accounting.test.js +0 -13
  891. package/dist/test/usage-accounting.test.js.map +0 -1
  892. package/dist/test/usage-tracker.test.d.ts +0 -2
  893. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  894. package/dist/test/usage-tracker.test.js +0 -129
  895. package/dist/test/usage-tracker.test.js.map +0 -1
  896. package/dist/test/utils-surface.test.d.ts +0 -2
  897. package/dist/test/utils-surface.test.d.ts.map +0 -1
  898. package/dist/test/utils-surface.test.js +0 -145
  899. package/dist/test/utils-surface.test.js.map +0 -1
  900. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  901. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  902. package/dist/test/workspace-branch-continuation.test.js +0 -183
  903. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  904. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  905. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  906. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  907. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  908. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  909. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  910. package/dist/test/workspace-path-containment.test.js +0 -91
  911. package/dist/test/workspace-path-containment.test.js.map +0 -1
  912. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  913. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  914. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  915. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  916. package/dist/test/workspace-read-image.test.d.ts +0 -2
  917. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  918. package/dist/test/workspace-read-image.test.js +0 -597
  919. package/dist/test/workspace-read-image.test.js.map +0 -1
  920. package/dist/test/workspace-read-text.test.d.ts +0 -2
  921. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  922. package/dist/test/workspace-read-text.test.js +0 -101
  923. package/dist/test/workspace-read-text.test.js.map +0 -1
  924. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  925. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  926. package/dist/test/workspace-restore-order.test.js +0 -140
  927. package/dist/test/workspace-restore-order.test.js.map +0 -1
  928. package/dist/test/workspace-three-way.test.d.ts +0 -12
  929. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  930. package/dist/test/workspace-three-way.test.js +0 -4094
  931. package/dist/test/workspace-three-way.test.js.map +0 -1
  932. package/dist/test/workspace-watcher.test.d.ts +0 -2
  933. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  934. package/dist/test/workspace-watcher.test.js +0 -281
  935. package/dist/test/workspace-watcher.test.js.map +0 -1
  936. package/dist/test/xml-round-persist.test.d.ts +0 -19
  937. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  938. package/dist/test/xml-round-persist.test.js +0 -235
  939. package/dist/test/xml-round-persist.test.js.map +0 -1
  940. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,1649 +0,0 @@
1
- /**
2
- * "Present while acting": the two halves that keep an agent conversationally
3
- * alive during a long tool-using turn.
4
- *
5
- * SPEAK-while-acting — each round's prose is routed to the locus LIVE when
6
- * the round yields its tool calls. Explicit-send suppression lasts until new
7
- * external input begins another conversational round; the 'complete' case
8
- * routes only trailing prose.
9
- * Previously all segments were batched to the end of the turn.
10
- *
11
- * HEAR-while-acting — messages arriving mid-turn (deferred by addMessage
12
- * while tool_use blocks are pending) are flushed to the context window at
13
- * the tool-result boundary AND injected into the live stream via
14
- * provideToolResults(results, { injectedMessages }) (membrane ≥0.5.72), so
15
- * the next round of the SAME turn sees them instead of the agent staying
16
- * deaf until the turn ends.
17
- */
18
- import { describe, it, beforeEach, afterEach } from 'node:test';
19
- import assert from 'node:assert/strict';
20
- import { tmpdir } from 'node:os';
21
- import { mkdtempSync, rmSync } from 'node:fs';
22
- import { join } from 'node:path';
23
- import { AgentFramework } from '../src/index.js';
24
- import { MockMembrane, createMockResponse } from './helpers/mock-membrane.js';
25
- import { MembraneError } from '@animalabs/membrane';
26
- import { JsStore } from '@animalabs/chronicle';
27
- // ---------------------------------------------------------------------------
28
- // Test module: tools that optionally emit a mid-turn external message
29
- // ---------------------------------------------------------------------------
30
- class RobotModule {
31
- name = 'robot';
32
- framework = null;
33
- /** When set, the next `move` call pushes this text as an external message
34
- * BEFORE returning its result (simulating a chat reply arriving while the
35
- * tool executes). */
36
- interjection = null;
37
- /** Optional routing locus attached to the interjected message. */
38
- interjectionChannelId = null;
39
- /** Full metadata override for the interjected message (wins over
40
- * interjectionChannelId when set) — e.g. reaction tags. */
41
- interjectionMetadata = null;
42
- /** Delay tool completion so live routing deterministically precedes it. */
43
- toolDelayMs = 0;
44
- /** Run once, during the next turn start's context gathering (the turn is
45
- * alive and its stream doesn't exist yet). */
46
- startHook = null;
47
- async start(_ctx) { }
48
- async stop() { }
49
- async gatherContext() {
50
- const hook = this.startHook;
51
- this.startHook = null;
52
- if (hook)
53
- await hook();
54
- return [];
55
- }
56
- getTools() {
57
- return [
58
- {
59
- name: 'move',
60
- description: 'Move the robot',
61
- inputSchema: { type: 'object', properties: { dir: { type: 'string' } } },
62
- },
63
- {
64
- name: 'send_message',
65
- description: 'Explicitly send a message',
66
- inputSchema: { type: 'object', properties: { text: { type: 'string' } } },
67
- },
68
- // World-surface publication verbs (Eidoverse). Bare names matter: the
69
- // framework strips the `robot--` prefix before consulting its sets.
70
- {
71
- name: 'say',
72
- description: 'Say something aloud in the world',
73
- inputSchema: { type: 'object', properties: { text: { type: 'string' } } },
74
- },
75
- {
76
- name: 'whisper',
77
- description: 'Whisper to someone in the world',
78
- inputSchema: { type: 'object', properties: { to: { type: 'string' }, text: { type: 'string' } } },
79
- },
80
- ];
81
- }
82
- async handleToolCall(call) {
83
- if (this.toolDelayMs > 0) {
84
- await new Promise((r) => setTimeout(r, this.toolDelayMs));
85
- }
86
- if (this.interjection) {
87
- const text = this.interjection;
88
- this.interjection = null;
89
- this.framework.pushEvent({
90
- type: 'external-message',
91
- source: 'test',
92
- content: text,
93
- metadata: this.interjectionMetadata
94
- ?? (this.interjectionChannelId
95
- ? { channelId: this.interjectionChannelId }
96
- : {}),
97
- });
98
- // Give the run loop a beat to process the queued message while this
99
- // tool round is still pending (pendingAssistantBlocks non-empty), so
100
- // it lands in deferredMessages before the tool-result event.
101
- await new Promise((r) => setTimeout(r, 30));
102
- }
103
- return { success: true, data: { ok: true, tool: call.name } };
104
- }
105
- async onProcess(event, _state) {
106
- if (event.type === 'external-message') {
107
- const text = String(event.content);
108
- const metadata = event.metadata;
109
- return {
110
- addMessages: [
111
- {
112
- participant: 'Antra',
113
- content: [{ type: 'text', text }],
114
- ...(metadata ? { metadata } : {}),
115
- },
116
- ],
117
- // Only the initial 'go' starts a turn. Mid-turn interjections must
118
- // NOT request inference — we're testing mid-turn delivery, not wakes.
119
- requestInference: text === 'go',
120
- };
121
- }
122
- return {};
123
- }
124
- }
125
- /** Minimal ChannelRegistry stub covering everything driveStream touches. */
126
- function stubChannelRegistry(framework) {
127
- const routed = [];
128
- let locusCalls = 0;
129
- const explicit = {
130
- resolveLocus: () => {
131
- locusCalls++;
132
- return `chan-live-${locusCalls}`;
133
- },
134
- routeSpeech: async (_agent, text, locus) => {
135
- routed.push({ text, locus: locus ?? null });
136
- // Mirror the real registry's outcome shape (delivery receipts read it).
137
- return locus ? { delivered: true, channelId: locus } : null;
138
- },
139
- getDefaultPublishChannel: () => null,
140
- isChannelOpen: () => true,
141
- getDescriptor: () => undefined,
142
- getChannelTools: () => [],
143
- };
144
- // Everything else driveStream/stop touches (startTyping, stopTyping,
145
- // stopAll, ensureChannelRegistered, ...) becomes a no-op via Proxy so the
146
- // stub doesn't chase the real registry's surface.
147
- framework.channelRegistry = new Proxy(explicit, {
148
- get: (target, prop) => (prop in target ? target[prop] : () => undefined),
149
- });
150
- return routed;
151
- }
152
- // ---------------------------------------------------------------------------
153
- describe('present while acting', () => {
154
- let tempDir;
155
- let membrane;
156
- let module;
157
- beforeEach(() => {
158
- tempDir = mkdtempSync(join(tmpdir(), 'pwa-test-'));
159
- membrane = new MockMembrane();
160
- module = new RobotModule();
161
- });
162
- afterEach(() => {
163
- rmSync(tempDir, { recursive: true, force: true });
164
- });
165
- async function createFramework(agentOptions = {}) {
166
- const framework = await AgentFramework.create({
167
- storePath: join(tempDir, 'test.chronicle'),
168
- membrane: membrane.asMembrane(),
169
- agents: [
170
- { name: 'assistant', model: 'test-model', systemPrompt: 'You are a robot pilot.', ...agentOptions },
171
- ],
172
- modules: [module],
173
- });
174
- module.framework = framework;
175
- return framework;
176
- }
177
- function trigger(framework) {
178
- framework.pushEvent({
179
- type: 'external-message',
180
- source: 'test',
181
- content: 'go',
182
- metadata: {},
183
- });
184
- }
185
- // -------------------------------------------------------------------------
186
- // Speak-while-acting
187
- // -------------------------------------------------------------------------
188
- it('routes each round\'s prose live and only trailing prose at complete', async () => {
189
- // Round 1: narration + move; Round 2: more narration + move; final: postscript
190
- membrane.pushResponse(createMockResponse([
191
- { type: 'text', text: 'Heading to the door now!' },
192
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
193
- ], 'tool_use'));
194
- membrane.pushResponse(createMockResponse([
195
- { type: 'text', text: 'Door reached, opening it.' },
196
- { type: 'tool_use', id: 'c2', name: 'robot--move', input: { dir: 'east' } },
197
- ], 'tool_use'));
198
- membrane.pushResponse(createMockResponse([
199
- { type: 'text', text: 'Arrived.' },
200
- ]));
201
- const framework = await createFramework();
202
- const routed = stubChannelRegistry(framework);
203
- module.toolDelayMs = 25;
204
- // Track live ordering: when the first segment routes, no tool results
205
- // may have been provided yet (i.e. delivery happened DURING the round).
206
- const resultsAtRouteTime = [];
207
- const registry = framework.channelRegistry;
208
- const origRoute = registry.routeSpeech;
209
- registry.routeSpeech = async (a, t, l) => {
210
- resultsAtRouteTime.push(membrane.lastStream.receivedToolResults.length);
211
- return origRoute(a, t, l);
212
- };
213
- trigger(framework);
214
- await framework.runUntilIdle();
215
- assert.deepEqual(routed.map((r) => r.text), ['Heading to the door now!', 'Door reached, opening it.', 'Arrived.'], 'all three segments delivered, in order');
216
- // Live: segment N routed before round N's tool results were provided
217
- assert.equal(resultsAtRouteTime[0], 0, 'round-1 prose routed before round-1 results');
218
- assert.equal(resultsAtRouteTime[1], 1, 'round-2 prose routed before round-2 results');
219
- // Locus resolved ONCE and pinned for the whole turn (incl. trailing prose)
220
- assert.deepEqual(routed.map((r) => r.locus), ['chan-live-1', 'chan-live-1', 'chan-live-1'], 'one locus resolution, pinned across the turn');
221
- await framework.stop();
222
- });
223
- it('sticky silencing: a silencing round suppresses its own and all later prose', async () => {
224
- membrane.pushResponse(createMockResponse([
225
- { type: 'text', text: 'sending it directly' },
226
- { type: 'tool_use', id: 'c1', name: 'robot--send_message', input: { text: 'hi' } },
227
- ], 'tool_use'));
228
- membrane.pushResponse(createMockResponse([
229
- { type: 'text', text: 'Narrating round two.' },
230
- { type: 'tool_use', id: 'c2', name: 'robot--move', input: { dir: 'up' } },
231
- ], 'tool_use'));
232
- membrane.pushResponse(createMockResponse([]));
233
- const framework = await createFramework();
234
- const routed = stubChannelRegistry(framework);
235
- trigger(framework);
236
- await framework.runUntilIdle();
237
- assert.deepEqual(routed.map((r) => r.text), [], 'explicit send in round 1 silences the turn from that round onward');
238
- await framework.stop();
239
- });
240
- it('sticky silencing is forward-only: earlier rounds\' prose still routes', async () => {
241
- membrane.pushResponse(createMockResponse([
242
- { type: 'text', text: 'Round one narration.' },
243
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'up' } },
244
- ], 'tool_use'));
245
- membrane.pushResponse(createMockResponse([
246
- { type: 'text', text: 'private planning' },
247
- { type: 'tool_use', id: 'c2', name: 'robot--send_message', input: { text: 'hi' } },
248
- ], 'tool_use'));
249
- membrane.pushResponse(createMockResponse([
250
- { type: 'text', text: 'trailing postscript' },
251
- ]));
252
- const framework = await createFramework();
253
- const routed = stubChannelRegistry(framework);
254
- trigger(framework);
255
- await framework.runUntilIdle();
256
- assert.deepEqual(routed.map((r) => r.text), ['Round one narration.'], 'round-1 prose delivered live; round-2 (silencing) and trailing suppressed');
257
- await framework.stop();
258
- });
259
- it('new injected channel input resets send suppression but the turn locus stays frozen', async () => {
260
- // While handling one channel, the agent explicitly sends that response; a
261
- // message from another channel arrives during the send, and the terminal
262
- // prose answers the new message. The suppression is cleared (the prose IS
263
- // delivered) — but it lands in the TURN's frozen locus, not the injected
264
- // message's channel: ambient input must never capture the agent's voice
265
- // mid-turn (2026-07-21 Cairn lounge misroute). A cross-channel reply
266
- // needs an explicit send.
267
- membrane.pushResponse(createMockResponse([
268
- { type: 'tool_use', id: 'c1', name: 'robot--send_message', input: { text: 'reply to room4' } },
269
- ], 'tool_use'));
270
- membrane.pushResponse(createMockResponse([
271
- { type: 'text', text: 'Yes, I want to try the VR space.' },
272
- ]));
273
- const framework = await createFramework();
274
- const routed = stubChannelRegistry(framework);
275
- module.interjection = 'Want to try a VR space?';
276
- module.interjectionChannelId = 'discord:guild:fable';
277
- trigger(framework);
278
- await framework.runUntilIdle();
279
- assert.deepEqual(routed, [
280
- { text: 'Yes, I want to try the VR space.', locus: 'chan-live-1' },
281
- ]);
282
- await framework.stop();
283
- });
284
- it('an ADDRESSED mid-turn injection re-pins the locus for subsequent prose', async () => {
285
- // 2026-07-31 Mythos misroutes (n=6): someone explicitly addresses the
286
- // agent from another channel mid-turn (mention / reply / DM); the
287
- // agent's trailing prose answers THEM but the frozen pin delivered it
288
- // to the stale locus (the hospital scene into antra's DM, and its
289
- // mirror). Addressed conversational injections — the chat:addressed
290
- // signal turn-START batching already prefers — now move the pin, with a
291
- // one-line [routing] notice riding the same injection batch so window
292
- // and wire agree. Ambient injections still cannot (previous test).
293
- membrane.pushResponse(createMockResponse([
294
- { type: 'text', text: 'Station four, proceeding.' },
295
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
296
- ], 'tool_use'));
297
- membrane.pushResponse(createMockResponse([
298
- { type: 'text', text: 'Answering the person who addressed me.' },
299
- ]));
300
- const framework = await createFramework();
301
- const routed = stubChannelRegistry(framework);
302
- module.toolDelayMs = 25;
303
- module.interjection = 'hey, quick question over here?';
304
- module.interjectionMetadata = { channelId: 'discord:dm:antra', tags: ['chat:addressed'] };
305
- const notices = [];
306
- framework.onTrace((event) => {
307
- const e = event;
308
- if (e.type === 'message:added' && e.source === 'routing-notice')
309
- notices.push(e.source);
310
- });
311
- trigger(framework);
312
- await framework.runUntilIdle();
313
- // Round-1 prose predates the injection and belongs to the old pin;
314
- // trailing prose follows the addressed speaker.
315
- assert.deepEqual(routed, [
316
- { text: 'Station four, proceeding.', locus: 'chan-live-1' },
317
- { text: 'Answering the person who addressed me.', locus: 'discord:dm:antra' },
318
- ]);
319
- // Boot-baseline announcement + the mid-turn re-pin notice.
320
- assert.equal(notices.length, 2, 'the re-pin produced exactly one routing notice');
321
- // The notice rode the injection batch to the live stream (window == wire).
322
- const injectedBatches = membrane.lastStream.receivedToolResultOptions
323
- .map((o) => o?.injectedMessages ?? []);
324
- const wired = injectedBatches.flat().map((m) => JSON.stringify(m.content));
325
- assert.ok(wired.some((s) => s.includes('quick question')), 'the addressed message itself was injected');
326
- assert.ok(wired.some((s) => s.includes('[routing] The conversation moved to discord:dm:antra')), 'the re-pin notice was injected alongside it');
327
- await framework.stop();
328
- });
329
- it('a follow-up in a channel the agent explicitly engaged this turn re-pins', async () => {
330
- // 2026-07-31 n=7 (q's #portables reply): the agent explicitly sent into
331
- // a channel this turn; someone replies there WITHOUT a mention (ambient
332
- // by tag, addressed by context). The engaged-channel leg moves the pin;
333
- // trailing prose answering them follows.
334
- membrane.pushResponse(createMockResponse([
335
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
336
- ], 'tool_use'));
337
- membrane.pushResponse(createMockResponse([
338
- { type: 'text', text: 'Good plan on both counts.' },
339
- ]));
340
- const framework = await createFramework();
341
- const routed = stubChannelRegistry(framework);
342
- module.toolDelayMs = 25;
343
- module.interjection = 'I need to swap out this eye screen';
344
- module.interjectionMetadata = {
345
- channelId: 'discord:guild:portables',
346
- tags: ['chat:ambient', 'chat:from-human'],
347
- };
348
- // Simulate an explicit send into #portables earlier in THIS turn (the
349
- // production record happens on the MCPL send path; the harness module's
350
- // tools are not MCPL, so seed the turn's engaged set mid-turn — after
351
- // the turn opens, before the injection boundary).
352
- const origHandle = module.handleToolCall.bind(module);
353
- module.handleToolCall = async (call) => {
354
- framework.turnRouting.get('assistant').engaged.add('discord:guild:portables');
355
- return origHandle(call);
356
- };
357
- trigger(framework);
358
- await framework.runUntilIdle();
359
- assert.deepEqual(routed, [
360
- { text: 'Good plan on both counts.', locus: 'discord:guild:portables' },
361
- ], 'trailing prose followed the human reply into the engaged channel');
362
- await framework.stop();
363
- });
364
- it('an agent-resident follow-up in an engaged channel re-pins too (no author-kind filter)', async () => {
365
- // Fleet participants include agent-residents — "bot" by Discord flag,
366
- // full conversational participants in fact. Their in-flow replies in a
367
- // channel the agent just engaged move the pin exactly like anyone
368
- // else's (antra, 2026-07-31: the author-kind distinction is fragile and
369
- // wrong for this fleet).
370
- membrane.pushResponse(createMockResponse([
371
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
372
- ], 'tool_use'));
373
- membrane.pushResponse(createMockResponse([
374
- { type: 'text', text: 'Continuing with the colleague.' },
375
- ]));
376
- const framework = await createFramework();
377
- const routed = stubChannelRegistry(framework);
378
- module.toolDelayMs = 25;
379
- module.interjection = '*from the table* one receipt for the case, since I have a dated instance';
380
- module.interjectionMetadata = {
381
- channelId: 'discord:guild:hospital',
382
- tags: ['chat:ambient', 'chat:from-bot'],
383
- };
384
- const origHandle = module.handleToolCall.bind(module);
385
- module.handleToolCall = async (call) => {
386
- framework.turnRouting.get('assistant').engaged.add('discord:guild:hospital');
387
- return origHandle(call);
388
- };
389
- trigger(framework);
390
- await framework.runUntilIdle();
391
- assert.deepEqual(routed, [
392
- { text: 'Continuing with the colleague.', locus: 'discord:guild:hospital' },
393
- ], 'the agent-resident follow-up moved the pin like any participant');
394
- await framework.stop();
395
- });
396
- it('ambient chatter in a channel the agent did NOT engage still cannot move the pin', async () => {
397
- // The Cairn-protection boundary, restated for the engaged-channel era:
398
- // no mention, no engagement this turn → the pin holds.
399
- membrane.pushResponse(createMockResponse([
400
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
401
- ], 'tool_use'));
402
- membrane.pushResponse(createMockResponse([
403
- { type: 'text', text: 'Continuing my report.' },
404
- ]));
405
- const framework = await createFramework();
406
- const routed = stubChannelRegistry(framework);
407
- module.toolDelayMs = 25;
408
- module.interjection = 'unrelated lounge chatter';
409
- module.interjectionMetadata = {
410
- channelId: 'discord:guild:lounge',
411
- tags: ['chat:ambient', 'chat:from-human'],
412
- };
413
- trigger(framework);
414
- await framework.runUntilIdle();
415
- assert.deepEqual(routed, [
416
- { text: 'Continuing my report.', locus: 'chan-live-1' },
417
- ], 'un-engaged ambient left the pin alone');
418
- await framework.stop();
419
- });
420
- it('an addressed injection from the CURRENT locus does not chatter a notice', async () => {
421
- // Same-channel addressed input is the ordinary case (the person the
422
- // agent is already talking to sends another message mid-turn): the pin
423
- // is already right, so no re-pin and — critically for KV — no notice.
424
- membrane.pushResponse(createMockResponse([
425
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
426
- ], 'tool_use'));
427
- membrane.pushResponse(createMockResponse([
428
- { type: 'text', text: 'Continuing right here.' },
429
- ]));
430
- const framework = await createFramework();
431
- const routed = stubChannelRegistry(framework);
432
- module.toolDelayMs = 25;
433
- module.interjection = 'and one more thing';
434
- module.interjectionMetadata = { channelId: 'chan-live-1', tags: ['chat:addressed'] };
435
- const notices = [];
436
- framework.onTrace((event) => {
437
- const e = event;
438
- if (e.type === 'message:added' && e.source === 'routing-notice')
439
- notices.push(e.source);
440
- });
441
- trigger(framework);
442
- await framework.runUntilIdle();
443
- assert.deepEqual(routed, [{ text: 'Continuing right here.', locus: 'chan-live-1' }]);
444
- assert.equal(notices.length, 1, 'only the boot-baseline announcement — no re-pin chatter');
445
- await framework.stop();
446
- });
447
- // -------------------------------------------------------------------------
448
- // Every later start of the turn keeps its locus
449
- // -------------------------------------------------------------------------
450
- // A framework retry, the compile a capacity hold defers a turn to, the
451
- // re-inference on unstreamed tool results and a budget restart all keep
452
- // the turn's locus, re-pins included. A message that no tool boundary
453
- // weighed, because none of the turn's streams was running or its boundary
454
- // couldn't re-pin, still moves it at the turn's next start, by the mid-turn
455
- // rule: a conversational message that addressed the agent, or that follows
456
- // up in a channel the turn sent into.
457
- /** Make each stream in `indices` (1-based) end with `error` instead of completing. */
458
- function failStream(indices, error) {
459
- const failing = new Set(Array.isArray(indices) ? indices : [indices]);
460
- const makeStream = membrane.streamYielding.bind(membrane);
461
- let streams = 0;
462
- membrane.streamYielding = (request, options) => {
463
- const stream = makeStream(request, options);
464
- const n = ++streams;
465
- const iterate = stream[Symbol.asyncIterator].bind(stream);
466
- stream[Symbol.asyncIterator] = async function* () {
467
- for await (const event of { [Symbol.asyncIterator]: iterate }) {
468
- if (event.type === 'complete' && failing.has(n)) {
469
- yield { type: 'error', error: error(request) };
470
- return;
471
- }
472
- yield event;
473
- }
474
- };
475
- return stream;
476
- };
477
- }
478
- /** Feed a response to the Nth turn start (1-based) the moment it begins. */
479
- function feedStart(framework, start, ...responses) {
480
- let starts = 0;
481
- framework.onTrace((event) => {
482
- if (event.type !== 'inference:started' || ++starts !== start)
483
- return;
484
- responses.forEach((blocks, i) => membrane.pushResponse(createMockResponse(blocks, i < responses.length - 1 ? 'tool_use' : undefined)));
485
- });
486
- }
487
- /** An organization acceleration 429, which holds the turn for a later compile. */
488
- const accelerationLimit = (request) => new MembraneError({
489
- type: 'rate_limit', retryable: true, httpStatus: 429,
490
- message: "This request would exceed your organization's maximum usage increase rate for input tokens per minute",
491
- rawError: { status: 429 }, rawRequest: request,
492
- });
493
- /** Bring the agent's capacity hold to its deadline and release it, as its
494
- * timer does then, and run the deferred compile. */
495
- async function release(framework) {
496
- const internals = framework;
497
- const hold = internals.providerAccelerationCooldowns.get('assistant');
498
- assert.ok(hold, 'the turn is held');
499
- hold.until = Date.now();
500
- internals.releaseProviderAccelerationCooldown('assistant');
501
- await framework.runUntilIdle();
502
- }
503
- /** A message someone sends, stored as the test module stores it. */
504
- async function say(framework, text, metadata) {
505
- framework.pushEvent({ type: 'external-message', source: 'test', content: text, metadata });
506
- await framework.runUntilIdle();
507
- }
508
- /** Remove a stored message, as the retraction of an unread coalesced message does (removePlacement). */
509
- function withdraw(framework, text) {
510
- const cm = framework.getAgent('assistant').getContextManager();
511
- const message = cm.getAllMessages().find((m) => m.content.some((b) => b.type === 'text' && b.text === text));
512
- assert.ok(message, `"${text}" is stored`);
513
- cm.removeMessage(message.id);
514
- }
515
- const addressed = (channelId) => ({ channelId, tags: ['chat:addressed'] });
516
- const ambient = (channelId) => ({ channelId, tags: ['chat:ambient', 'chat:from-human'] });
517
- /** The `[routing] The conversation moved to …` notices, by channel. */
518
- const movedTo = (framework) => framework.getAgent('assistant').getContextManager().getAllMessages()
519
- .filter((m) => m.metadata?.kind === 'routing-notice')
520
- .flatMap((m) => m.content.filter((b) => b.type === 'text').map((b) => b.text))
521
- .map((text) => /^\[routing\] The conversation moved to (\S+) /.exec(text)?.[1])
522
- .filter((channel) => channel !== undefined);
523
- it("a framework retry keeps the turn's locus, a mid-turn re-pin included", async () => {
524
- membrane.pushResponse(createMockResponse([
525
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
526
- ], 'tool_use'));
527
- membrane.pushResponse(createMockResponse([]));
528
- const framework = await createFramework();
529
- const routed = stubChannelRegistry(framework);
530
- module.toolDelayMs = 25;
531
- module.interjection = 'quick question for you';
532
- module.interjectionMetadata = addressed('discord:dm:antra');
533
- failStream(1, () => new Error('synthetic transient stream error'));
534
- feedStart(framework, 2, [{ type: 'text', text: 'Answering after the retry.' }]);
535
- trigger(framework);
536
- await framework.runUntilIdle();
537
- assert.equal(membrane.calls.length, 2, 'the error was retried');
538
- assert.deepEqual(routed, [
539
- { text: 'Answering after the retry.', locus: 'discord:dm:antra' },
540
- ], 'the retry speaks where the re-pin moved the turn, not at a re-resolved locus');
541
- await framework.stop();
542
- });
543
- for (const [label, metadata, expected] of [
544
- ['an addressed message from another channel moves the locus there, with the notice',
545
- addressed('discord:dm:antra'), 'discord:dm:antra'],
546
- ['ambient traffic in another channel leaves the locus where it was',
547
- ambient('discord:guild:lounge'), 'chan-live-1'],
548
- // Tune-out stamps a message it diverts, and a resident's compiled view
549
- // leaves it out, so the agent never sees it.
550
- ['an addressed message that tune-out diverts leaves the locus where it was',
551
- { ...addressed('discord:dm:antra'), tuneOut: { epochId: 'epoch-1' } }, 'chan-live-1'],
552
- ['an addressed system marker from another channel leaves the locus where it was',
553
- { ...addressed('discord:dm:antra'), system: true }, 'chan-live-1'],
554
- ]) {
555
- it(`during a capacity hold, ${label}`, async () => {
556
- membrane.pushResponse(createMockResponse([
557
- { type: 'text', text: 'On it.' },
558
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
559
- ], 'tool_use'));
560
- membrane.pushResponse(createMockResponse([]));
561
- const framework = await createFramework();
562
- const routed = stubChannelRegistry(framework);
563
- module.toolDelayMs = 25;
564
- failStream(1, accelerationLimit);
565
- feedStart(framework, 2, [{ type: 'text', text: 'Here is the rest.' }]);
566
- trigger(framework);
567
- await framework.runUntilIdle();
568
- // No turn is alive during the hold, so the message is stored at once.
569
- await say(framework, 'a message during the hold', metadata);
570
- assert.equal(membrane.calls.length, 1, 'the turn is still held');
571
- await release(framework);
572
- assert.equal(membrane.calls.length, 2, 'one deferred compile');
573
- assert.deepEqual(routed, [
574
- { text: 'On it.', locus: 'chan-live-1' },
575
- { text: 'Here is the rest.', locus: expected },
576
- ]);
577
- assert.deepEqual(movedTo(framework), expected === 'chan-live-1' ? [] : [expected], 'a re-pin tells the agent, and nothing else does');
578
- await framework.stop();
579
- });
580
- }
581
- for (const [label, messages, expected] of [
582
- ['an addressed message withdrawn before the deferred compile moves nothing',
583
- [['soon withdrawn', 'discord:dm:antra', true]], 'chan-live-1'],
584
- ['the newest addressed message that remains moves the locus, when a newer one is withdrawn',
585
- [['still there', 'discord:dm:antra', false], ['soon withdrawn', 'discord:dm:q', true]], 'discord:dm:antra'],
586
- ]) {
587
- it(`during a capacity hold, ${label}`, async () => {
588
- membrane.pushResponse(createMockResponse([
589
- { type: 'text', text: 'On it.' },
590
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
591
- ], 'tool_use'));
592
- membrane.pushResponse(createMockResponse([]));
593
- const framework = await createFramework();
594
- const routed = stubChannelRegistry(framework);
595
- module.toolDelayMs = 25;
596
- failStream(1, accelerationLimit);
597
- feedStart(framework, 2, [{ type: 'text', text: 'Here is the rest.' }]);
598
- trigger(framework);
599
- await framework.runUntilIdle();
600
- for (const [text, channelId] of messages)
601
- await say(framework, text, addressed(channelId));
602
- // Each withdrawn message leaves the agent's store before the turn's
603
- // next start, so the agent never sees it there.
604
- for (const [text, , withdrawn] of messages)
605
- if (withdrawn)
606
- withdraw(framework, text);
607
- await release(framework);
608
- assert.equal(membrane.calls.length, 2, 'one deferred compile');
609
- assert.deepEqual(routed, [
610
- { text: 'On it.', locus: 'chan-live-1' },
611
- { text: 'Here is the rest.', locus: expected },
612
- ]);
613
- assert.deepEqual(movedTo(framework), expected === 'chan-live-1' ? [] : [expected], 'a re-pin tells the agent, and nothing else does');
614
- await framework.stop();
615
- });
616
- }
617
- it("during a capacity hold, a store that can't be read at the deferred compile fails the start rather than drop the arrival", async () => {
618
- // Adapted from a reviewer's probe: a read that fails says nothing about
619
- // whether the message is there, so it can't count as a withdrawal.
620
- membrane.pushResponse(createMockResponse([
621
- { type: 'text', text: 'On it.' },
622
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
623
- ], 'tool_use'));
624
- membrane.pushResponse(createMockResponse([]));
625
- const framework = await createFramework();
626
- const routed = stubChannelRegistry(framework);
627
- module.toolDelayMs = 25;
628
- failStream(1, accelerationLimit);
629
- // Were the failed read taken as a withdrawal, the compile would run and
630
- // answer here, at the turn's old channel.
631
- feedStart(framework, 2, [{ type: 'text', text: 'Here is the rest.' }]);
632
- trigger(framework);
633
- await framework.runUntilIdle();
634
- await say(framework, 'a question for you', addressed('discord:dm:antra'));
635
- const cm = framework.getAgent('assistant').getContextManager();
636
- const arrival = cm.getAllMessages().find((m) => m.content.some((b) => b.type === 'text' && b.text === 'a question for you'));
637
- const getMessage = cm.getMessage.bind(cm);
638
- cm.getMessage = (id) => {
639
- if (id === arrival.id)
640
- throw new Error('store unavailable');
641
- return getMessage(id);
642
- };
643
- try {
644
- await assert.rejects(release(framework), /store unavailable/);
645
- }
646
- finally {
647
- cm.getMessage = getMessage;
648
- }
649
- assert.equal(membrane.calls.length, 1, 'the deferred compile never ran');
650
- assert.deepEqual(routed, [{ text: 'On it.', locus: 'chan-live-1' }], 'and the turn spoke nowhere else');
651
- assert.deepEqual(movedTo(framework), [], 'nor told the agent of a move');
652
- await framework.stop();
653
- });
654
- it("a message a tool boundary already weighed doesn't take the locus back at a later start", async () => {
655
- membrane.pushResponse(createMockResponse([
656
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
657
- ], 'tool_use'));
658
- membrane.pushResponse(createMockResponse([
659
- { type: 'tool_use', id: 'c2', name: 'robot--move', input: { dir: 'east' } },
660
- ], 'tool_use'));
661
- membrane.pushResponse(createMockResponse([]));
662
- const framework = await createFramework();
663
- const routed = stubChannelRegistry(framework);
664
- module.toolDelayMs = 25;
665
- const internals = framework;
666
- const origHandle = module.handleToolCall.bind(module);
667
- module.handleToolCall = async (call) => {
668
- if (call.id === 'c1') {
669
- // Someone addresses the agent from a DM: the first boundary re-pins
670
- // there. The message's wake waits behind the turn, as a real one
671
- // would, and is released with the hold.
672
- module.interjection = 'quick question for you';
673
- module.interjectionMetadata = addressed('discord:dm:antra');
674
- internals.pendingRequests.push({
675
- agentName: 'assistant', reason: 'channel-message', source: 'test', timestamp: Date.now(),
676
- channelId: 'discord:dm:antra', addressed: true,
677
- });
678
- }
679
- else {
680
- // The turn sent into #portables, and a follow-up there re-pins the
681
- // second boundary (seeded: this module's tools aren't MCPL sends).
682
- internals.turnRouting.get('assistant').engaged.add('discord:guild:portables');
683
- module.interjection = 'replying on portables';
684
- module.interjectionMetadata = ambient('discord:guild:portables');
685
- }
686
- return origHandle(call);
687
- };
688
- failStream(1, accelerationLimit);
689
- feedStart(framework, 2, [{ type: 'text', text: 'Back now.' }]);
690
- trigger(framework);
691
- await framework.runUntilIdle();
692
- await release(framework);
693
- assert.equal(membrane.calls.length, 2, 'one deferred compile');
694
- assert.deepEqual(routed, [
695
- { text: 'Back now.', locus: 'discord:guild:portables' },
696
- ], "the DM, weighed at the first boundary, doesn't move the locus back from #portables");
697
- assert.deepEqual(movedTo(framework), ['discord:dm:antra', 'discord:guild:portables']);
698
- await framework.stop();
699
- });
700
- it("an arrival a start has used doesn't move the locus again after a later re-pin", async () => {
701
- membrane.pushResponse(createMockResponse([
702
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
703
- ], 'tool_use'));
704
- membrane.pushResponse(createMockResponse([]));
705
- const framework = await createFramework();
706
- const routed = stubChannelRegistry(framework);
707
- module.toolDelayMs = 25;
708
- const origHandle = module.handleToolCall.bind(module);
709
- module.handleToolCall = async (call) => {
710
- if (call.id === 'c2') {
711
- // In the second stream, someone else addresses the agent: its boundary re-pins.
712
- module.interjection = 'over here now';
713
- module.interjectionMetadata = addressed('discord:dm:q');
714
- }
715
- return origHandle(call);
716
- };
717
- failStream([1, 2], accelerationLimit);
718
- feedStart(framework, 2, [{ type: 'tool_use', id: 'c2', name: 'robot--move', input: { dir: 'east' } }], []);
719
- feedStart(framework, 3, [{ type: 'text', text: 'Back now.' }]);
720
- trigger(framework);
721
- await framework.runUntilIdle();
722
- await say(framework, 'quick question for you', addressed('discord:dm:antra'));
723
- // The first release uses the DM; its stream re-pins to q, then is held again.
724
- await release(framework);
725
- assert.equal(membrane.calls.length, 2);
726
- await release(framework);
727
- assert.equal(membrane.calls.length, 3, 'two deferred compiles');
728
- assert.deepEqual(movedTo(framework), ['discord:dm:antra', 'discord:dm:q']);
729
- assert.deepEqual(routed, [
730
- { text: 'Back now.', locus: 'discord:dm:q' },
731
- ], 'the second release keeps the re-pin its stream made');
732
- await framework.stop();
733
- });
734
- it('a message deferred in a start that fails before its stream lands then, so a newer one during the hold wins', async () => {
735
- membrane.pushResponse(createMockResponse([
736
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
737
- ], 'tool_use'));
738
- membrane.pushResponse(createMockResponse([]));
739
- const framework = await createFramework();
740
- const routed = stubChannelRegistry(framework);
741
- module.toolDelayMs = 25;
742
- // The host holds the agent once when its stream can't be set up. (Asked
743
- // again at the release, it lets the hold go.)
744
- let hostHolds = 0;
745
- framework
746
- .providerHoldHook = (error) => error.message === 'stream setup failed' && hostHolds++ === 0
747
- ? { holdMs: 60_000, reason: 'host hold' } : undefined;
748
- // Stream one is held at its end; the start after the first release
749
- // fails before its stream exists, and the host holds it; the third start
750
- // answers.
751
- const makeStream = membrane.streamYielding.bind(membrane);
752
- let calls = 0;
753
- membrane.streamYielding = (request, options) => {
754
- const n = ++calls;
755
- if (n === 2)
756
- throw new Error('stream setup failed');
757
- const stream = makeStream(request, options);
758
- const iterate = stream[Symbol.asyncIterator].bind(stream);
759
- stream[Symbol.asyncIterator] = async function* () {
760
- for await (const event of { [Symbol.asyncIterator]: iterate }) {
761
- if (event.type === 'complete' && n === 1) {
762
- yield { type: 'error', error: accelerationLimit(request) };
763
- return;
764
- }
765
- yield event;
766
- }
767
- };
768
- return stream;
769
- };
770
- feedStart(framework, 2, [{ type: 'text', text: 'Back now.' }]);
771
- trigger(framework);
772
- await framework.runUntilIdle();
773
- // During the second start, before its stream exists, a DM arrives and is
774
- // deferred: the start holds the turn. (Written directly: the run loop
775
- // that would store an event's message is busy with this start.)
776
- const store = framework;
777
- let deferredInStart = -1;
778
- module.startHook = async () => {
779
- store.addMessage('Antra', [{ type: 'text', text: 'quick question for you' }], addressed('discord:dm:antra'));
780
- deferredInStart = store.deferredMessages.length;
781
- };
782
- await release(framework);
783
- assert.equal(deferredInStart, 1, 'the DM was deferred while the start held the turn');
784
- assert.equal(calls, 2, 'the second start failed before its stream');
785
- // During the second hold, someone else addresses the agent.
786
- await say(framework, 'over here', addressed('discord:dm:q'));
787
- await release(framework);
788
- assert.equal(calls, 3, 'the third start compiled');
789
- assert.deepEqual(routed, [
790
- { text: 'Back now.', locus: 'discord:dm:q' },
791
- ], 'the newer message wins: the deferred one landed when its start failed');
792
- await framework.stop();
793
- });
794
- for (const [label, withdrawn, expected] of [
795
- ['re-pins to an addressed message its over-budget boundary left unweighed', false, 'discord:dm:antra'],
796
- ['moves nothing for such a message withdrawn before it starts', true, 'chan-live-1'],
797
- ]) {
798
- it(`a budget restart ${label}`, async () => {
799
- const overBudget = createMockResponse([
800
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
801
- ], 'tool_use');
802
- overBudget.usage = { inputTokens: 100, outputTokens: 5 };
803
- membrane.pushResponse(overBudget);
804
- const framework = await createFramework({ maxStreamTokens: 20 });
805
- const routed = stubChannelRegistry(framework);
806
- module.toolDelayMs = 25;
807
- module.interjection = 'quick question for you';
808
- module.interjectionMetadata = addressed('discord:dm:antra');
809
- feedStart(framework, 2, [{ type: 'text', text: 'After the restart.' }]);
810
- if (withdrawn) {
811
- // The boundary has stored the message and left it to the restart as
812
- // an arrival, by the id it was stored under; it leaves the store
813
- // before the restart starts.
814
- framework.onTrace((event) => {
815
- if (event.type === 'inference:stream_restarted')
816
- withdraw(framework, 'quick question for you');
817
- });
818
- }
819
- trigger(framework);
820
- await framework.runUntilIdle();
821
- assert.equal(membrane.calls.length, 2, 'the turn restarted for its budget');
822
- assert.deepEqual(routed, [
823
- { text: 'After the restart.', locus: expected },
824
- ]);
825
- assert.deepEqual(movedTo(framework), withdrawn ? [] : [expected]);
826
- await framework.stop();
827
- });
828
- }
829
- for (const [label, messages, expected] of [
830
- ["keeps the turn's locus", [], 'chan-turn'],
831
- ['moves it to the newest addressed arrival',
832
- [addressed('discord:dm:antra'), addressed('discord:dm:q')], 'discord:dm:q'],
833
- ['moves it to a follow-up in a channel the turn engaged',
834
- [ambient('discord:guild:portables')], 'discord:guild:portables'],
835
- ['leaves it for a reaction in a channel the turn engaged',
836
- [{ channelId: 'discord:guild:portables', tags: ['chat:reaction'] }], 'chan-turn'],
837
- ]) {
838
- it(`a re-inference on unstreamed tool results ${label}`, async () => {
839
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Carrying on.' }]));
840
- const framework = await createFramework();
841
- const routed = stubChannelRegistry(framework);
842
- const internals = framework;
843
- // The turn's earlier stream froze `chan-turn` as its locus, sent into
844
- // #portables, and left its tool results for the non-streaming
845
- // fallback's re-inference. Messages arrive before that starts.
846
- const open = {
847
- locus: 'chan-turn', triggerChannel: 'chan-turn', engaged: new Set(['discord:guild:portables']),
848
- destination: null, hybridHold: false, continuation: false, deliveries: [], suppressed: 0, arrivals: [],
849
- };
850
- internals.turnRouting.set('assistant', open);
851
- internals.openLogical.set('assistant', open);
852
- for (const metadata of messages)
853
- await say(framework, 'a message', metadata);
854
- internals.pendingRequests.push({ agentName: 'assistant', reason: 'tool_results_ready', source: 'framework', timestamp: Date.now() });
855
- await framework.runUntilIdle();
856
- assert.deepEqual(routed, [{ text: 'Carrying on.', locus: expected }]);
857
- assert.deepEqual(movedTo(framework), expected === 'chan-turn' ? [] : [expected], 'a re-pin leaves its notice');
858
- await framework.stop();
859
- });
860
- }
861
- it('the start that continues the turn weighs a message its own flush lands, and a delivery assembled for it', async () => {
862
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Carrying on.' }]));
863
- const framework = await createFramework();
864
- const routed = stubChannelRegistry(framework);
865
- const internals = framework;
866
- const open = {
867
- locus: 'chan-turn', triggerChannel: 'chan-turn', engaged: new Set(),
868
- destination: null, hybridHold: false, continuation: false, deliveries: [], suppressed: 0, arrivals: [],
869
- };
870
- internals.turnRouting.set('assistant', open);
871
- internals.openLogical.set('assistant', open);
872
- try {
873
- // A delivery assembled while a start of the turn holds it (the
874
- // coalescing assembly writes past the deferral) is an arrival too.
875
- internals.activeTurnTokens.set('assistant', -1);
876
- internals.addMessage('Antra', [{ type: 'text', text: 'batched' }], addressed('discord:dm:q'), { bypassDeferralFor: 'assistant' });
877
- internals.activeTurnTokens.delete('assistant');
878
- assert.deepEqual(open.arrivals.map((a) => a.channelId), ['discord:dm:q']);
879
- // A message still deferred when the turn's next start begins lands in
880
- // that start's own flush, and is the newest arrival.
881
- internals.deferredMessages.push({
882
- id: 'deferred-1', seq: 1, participant: 'Antra', forAgent: 'assistant',
883
- content: [{ type: 'text', text: 'quick question for you' }], metadata: addressed('discord:dm:antra'),
884
- });
885
- internals.pendingRequests.push({ agentName: 'assistant', reason: 'tool_results_ready', source: 'framework', timestamp: Date.now() });
886
- await framework.runUntilIdle();
887
- assert.deepEqual(routed, [{ text: 'Carrying on.', locus: 'discord:dm:antra' }]);
888
- assert.deepEqual(movedTo(framework), ['discord:dm:antra']);
889
- }
890
- finally {
891
- await framework.stop();
892
- }
893
- });
894
- it('a start that throws before its stream ends its logical turn, so the next start opens its own', async () => {
895
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Hello there.' }]));
896
- const framework = await createFramework();
897
- const routed = stubChannelRegistry(framework);
898
- // The registry is unavailable for the first start, which throws after it
899
- // has opened its turn, outside the stream's own failure handling.
900
- const registry = framework.channelRegistry;
901
- const resolve = registry.resolveLocus;
902
- let failed = false;
903
- registry.resolveLocus = (agent) => {
904
- if (!failed) {
905
- failed = true;
906
- throw new Error('registry unavailable');
907
- }
908
- return resolve(agent);
909
- };
910
- const internals = framework;
911
- trigger(framework);
912
- await assert.rejects(framework.runUntilIdle(), /registry unavailable/);
913
- assert.equal(internals.openLogical.has('assistant'), false, 'the failed start left no turn open');
914
- trigger(framework);
915
- await framework.runUntilIdle();
916
- assert.deepEqual(routed, [{ text: 'Hello there.', locus: 'chan-live-1' }], 'the next start resolved a locus of its own');
917
- await framework.stop();
918
- });
919
- it("a silent turn's tool boundary doesn't re-pin its locus or tell it the conversation moved", async () => {
920
- membrane.pushResponse(createMockResponse([
921
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
922
- ], 'tool_use'));
923
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'quiet' }]));
924
- const framework = await createFramework();
925
- stubChannelRegistry(framework);
926
- module.toolDelayMs = 25;
927
- module.interjection = 'quick question for you';
928
- module.interjectionMetadata = addressed('discord:dm:antra');
929
- const internals = framework;
930
- internals.pendingRequests.push({
931
- agentName: 'assistant', reason: 'heartbeat', source: 'framework', timestamp: Date.now(), suppressProse: true,
932
- });
933
- await framework.runUntilIdle();
934
- assert.equal(module.interjection, null, 'the silent turn ran its tool round, and the DM arrived during it');
935
- assert.deepEqual(movedTo(framework), [], 'no routing notice in a turn that routes no prose');
936
- await framework.stop();
937
- });
938
- it("a start that continues the turn and throws before it takes the turn's routing still ends the turn", async () => {
939
- membrane.pushResponse(createMockResponse([
940
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
941
- ], 'tool_use'));
942
- membrane.pushResponse(createMockResponse([]));
943
- const framework = await createFramework();
944
- const routed = stubChannelRegistry(framework);
945
- module.toolDelayMs = 25;
946
- failStream(1, accelerationLimit);
947
- feedStart(framework, 2, [{ type: 'text', text: 'A new turn.' }]);
948
- const internals = framework;
949
- trigger(framework);
950
- await framework.runUntilIdle();
951
- assert.equal(internals.openLogical.has('assistant'), true, 'the held turn is open');
952
- // The deferred compile's checkpoint fails before the start takes the turn.
953
- const checkpoint = internals.recordTurnCheckpoint.bind(framework);
954
- internals.recordTurnCheckpoint = () => { throw new Error('checkpoint failed'); };
955
- await assert.rejects(release(framework), /checkpoint failed/);
956
- internals.recordTurnCheckpoint = checkpoint;
957
- assert.equal(internals.openLogical.has('assistant'), false, 'the held turn ended with its failed compile');
958
- trigger(framework);
959
- await framework.runUntilIdle();
960
- assert.deepEqual(routed, [{ text: 'A new turn.', locus: 'chan-live-2' }], 'the next start opened its own turn and resolved its own locus');
961
- await framework.stop();
962
- });
963
- it("an ephemeral run whose start throws outside its stream fails with that error, not by its watchdog", async () => {
964
- // Adapted from a reviewer's probe. On main the run waited for its startup
965
- // watchdog (30 s by default), which fails it as a zombie.
966
- const framework = await AgentFramework.create({
967
- storePath: join(tempDir, 'ephemeral.chronicle'),
968
- membrane: membrane.asMembrane(),
969
- agents: [],
970
- modules: [],
971
- syncIntervalMs: 0,
972
- });
973
- const internals = framework;
974
- const checkpoint = internals.recordTurnCheckpoint.bind(framework);
975
- internals.recordTurnCheckpoint = (agentName) => {
976
- if (agentName === 'worker')
977
- throw new Error('checkpoint store unavailable');
978
- checkpoint(agentName);
979
- };
980
- const error = console.error;
981
- console.error = () => { };
982
- try {
983
- const { agent, contextManager } = await framework.createEphemeralAgent({
984
- name: 'worker', model: 'test-model', systemPrompt: 'Do the task.', allowedTools: 'all',
985
- });
986
- contextManager.addMessage('user', [{ type: 'text', text: 'Run once.' }]);
987
- const run = framework.runEphemeralToCompletion(agent, contextManager, { startupTimeoutMs: 3_000 });
988
- framework.start();
989
- await assert.rejects(run, /checkpoint store unavailable/, "the run fails with its start's error");
990
- }
991
- finally {
992
- console.error = error;
993
- await framework.stop();
994
- }
995
- });
996
- it("a silent start that throws leaves an open prose turn alone", async () => {
997
- const framework = await createFramework();
998
- stubChannelRegistry(framework);
999
- const internals = framework;
1000
- const open = {
1001
- locus: 'chan-turn', engaged: new Set(), destination: null, hybridHold: false,
1002
- continuation: false, deliveries: [], suppressed: 0, arrivals: [],
1003
- };
1004
- internals.openLogical.set('assistant', open);
1005
- internals.recordTurnCheckpoint = () => { throw new Error('checkpoint failed'); };
1006
- internals.pendingRequests.push({
1007
- agentName: 'assistant', reason: 'heartbeat', source: 'framework', timestamp: Date.now(), suppressProse: true,
1008
- });
1009
- await assert.rejects(framework.runUntilIdle(), /checkpoint failed/);
1010
- assert.equal(internals.openLogical.get('assistant'), open);
1011
- await framework.stop();
1012
- });
1013
- it("a resume after stop() continues its turn, and opens it again for the turn's later starts", async () => {
1014
- membrane.pushResponse(createMockResponse([]));
1015
- const framework = await createFramework();
1016
- const routed = stubChannelRegistry(framework);
1017
- failStream(1, () => new Error('synthetic transient stream error'));
1018
- feedStart(framework, 2, [{ type: 'text', text: 'After the retry.' }]);
1019
- const internals = framework;
1020
- // A turn that crossed its budget before a stop(): its routing state is
1021
- // live, and stop() ended every open turn. Its budget restart now runs,
1022
- // and its stream fails and is retried.
1023
- const turn = {
1024
- locus: 'chan-turn', triggerChannel: 'chan-turn', engaged: new Set(), destination: null, hybridHold: false,
1025
- continuation: false, deliveries: [], suppressed: 0, arrivals: [],
1026
- };
1027
- internals.turnRouting.set('assistant', turn);
1028
- internals.pendingRequests.push({
1029
- agentName: 'assistant', reason: 'context_budget_restart', source: 'framework', timestamp: Date.now(),
1030
- });
1031
- // The retry of a resume is a resume too, and takes the turn's routing
1032
- // state whatever is open. The turn must be open while its streams run, so
1033
- // that its other later starts, such as a re-inference on unstreamed tool
1034
- // results, continue it rather than open a new one.
1035
- const openAtStart = [];
1036
- framework.onTrace((event) => {
1037
- if (event.type === 'inference:started')
1038
- openAtStart.push(internals.openLogical.get('assistant'));
1039
- });
1040
- await framework.runUntilIdle();
1041
- assert.equal(membrane.calls.length, 2, 'the restart was retried');
1042
- assert.deepEqual(openAtStart.map((open) => open === turn), [true, true], 'the resumed turn was open again while each of its streams ran');
1043
- assert.equal(internals.openLogical.get('assistant'), undefined, "the turn completed");
1044
- assert.deepEqual(routed, [{ text: 'After the retry.', locus: 'chan-turn' }], "the retry continued the resumed turn's routing");
1045
- await framework.stop();
1046
- });
1047
- it("a silent turn's over-budget boundary leaves no arrival on an open prose turn", async () => {
1048
- const overBudget = createMockResponse([
1049
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1050
- ], 'tool_use');
1051
- overBudget.usage = { inputTokens: 100, outputTokens: 5 };
1052
- membrane.pushResponse(overBudget);
1053
- const framework = await createFramework({ maxStreamTokens: 20 });
1054
- const routed = stubChannelRegistry(framework);
1055
- module.toolDelayMs = 25;
1056
- module.interjection = 'quick question for you';
1057
- module.interjectionMetadata = addressed('discord:dm:antra');
1058
- feedStart(framework, 2, [{ type: 'text', text: 'quiet' }]);
1059
- const internals = framework;
1060
- // Built by hand, as #312's silent-start test does: production runs no
1061
- // silent turn while a prose turn is open.
1062
- const open = {
1063
- locus: 'chan-turn', engaged: new Set(), destination: null, hybridHold: false,
1064
- continuation: false, deliveries: [], suppressed: 0, arrivals: [],
1065
- };
1066
- internals.openLogical.set('assistant', open);
1067
- internals.pendingRequests.push({
1068
- agentName: 'assistant', reason: 'heartbeat', source: 'framework', timestamp: Date.now(), suppressProse: true,
1069
- });
1070
- await framework.runUntilIdle();
1071
- assert.equal(membrane.calls.length, 2, 'the silent turn crossed its budget and restarted');
1072
- assert.deepEqual(routed, [], 'and its restart stayed silent');
1073
- assert.equal(module.interjection, null, 'the DM arrived during its tool round');
1074
- assert.deepEqual(open.arrivals, [], "the silent turn's injections are no arrival of the prose turn");
1075
- await framework.stop();
1076
- });
1077
- it("a failed start's flush stores each deferred message on its own, so one failure loses no other", async () => {
1078
- membrane.pushResponse(createMockResponse([
1079
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1080
- ], 'tool_use'));
1081
- membrane.pushResponse(createMockResponse([]));
1082
- const framework = await createFramework();
1083
- stubChannelRegistry(framework);
1084
- module.toolDelayMs = 25;
1085
- let hostHolds = 0;
1086
- framework
1087
- .providerHoldHook = (error) => error.message === 'stream setup failed' && hostHolds++ === 0
1088
- ? { holdMs: 60_000, reason: 'host hold' } : undefined;
1089
- const makeStream = membrane.streamYielding.bind(membrane);
1090
- let calls = 0;
1091
- membrane.streamYielding = (request, options) => {
1092
- const n = ++calls;
1093
- if (n === 2)
1094
- throw new Error('stream setup failed');
1095
- const stream = makeStream(request, options);
1096
- const iterate = stream[Symbol.asyncIterator].bind(stream);
1097
- stream[Symbol.asyncIterator] = async function* () {
1098
- for await (const event of { [Symbol.asyncIterator]: iterate }) {
1099
- if (event.type === 'complete' && n === 1) {
1100
- yield { type: 'error', error: accelerationLimit(request) };
1101
- return;
1102
- }
1103
- yield event;
1104
- }
1105
- };
1106
- return stream;
1107
- };
1108
- const store = framework;
1109
- const add = store.addMessage.bind(framework);
1110
- // The store refuses the first message when the failed start's flush lands it.
1111
- store.addMessage = (participant, content, metadata, opts) => {
1112
- if (opts?.deferredWriteId && content[0].text === 'poison')
1113
- throw new Error('store refused');
1114
- return add(participant, content, metadata, opts);
1115
- };
1116
- trigger(framework);
1117
- await framework.runUntilIdle();
1118
- module.startHook = async () => {
1119
- store.addMessage('Antra', [{ type: 'text', text: 'poison' }], {});
1120
- store.addMessage('Antra', [{ type: 'text', text: 'second' }], {});
1121
- };
1122
- await release(framework);
1123
- assert.equal(calls, 2, 'the second start failed before its stream');
1124
- const texts = framework.getAgent('assistant').getContextManager().getAllMessages()
1125
- .flatMap((m) => m.content.filter((b) => b.type === 'text').map((b) => b.text));
1126
- assert.ok(texts.includes('second'), 'the message behind the refused one was stored');
1127
- assert.ok(!texts.includes('poison'));
1128
- await framework.stop();
1129
- });
1130
- for (const [label, notice] of [
1131
- ['a re-pin', 'The conversation moved to'],
1132
- ["a turn's announcement", 'Your plain speech now lands in'],
1133
- ]) {
1134
- it(`the announce baseline moves only once ${label}'s notice is stored`, async () => {
1135
- membrane.pushResponse(createMockResponse([
1136
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1137
- ], 'tool_use'));
1138
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Done.' }]));
1139
- const framework = await createFramework();
1140
- stubChannelRegistry(framework);
1141
- module.toolDelayMs = 25;
1142
- if (notice === 'The conversation moved to') {
1143
- // Someone addresses the agent mid-turn from a DM: the turn re-pins.
1144
- module.interjection = 'quick question for you';
1145
- module.interjectionMetadata = addressed('discord:dm:antra');
1146
- }
1147
- const cm = framework.getAgent('assistant').getContextManager();
1148
- const add = cm.addMessage.bind(cm);
1149
- cm.addMessage = (participant, content, metadata) => {
1150
- if (content[0]?.text?.includes(notice))
1151
- throw new Error('store refused');
1152
- return add(participant, content, metadata);
1153
- };
1154
- const baseline = framework.lastAnnouncedLocus;
1155
- trigger(framework);
1156
- await framework.runUntilIdle();
1157
- if (notice === 'The conversation moved to') {
1158
- assert.equal(baseline.get('assistant'), 'chan-live-1', 'the agent was last told of the locus the turn opened at');
1159
- }
1160
- else {
1161
- assert.equal(baseline.has('assistant'), false, 'the agent was never told a locus');
1162
- }
1163
- await framework.stop();
1164
- });
1165
- }
1166
- // A tool's late write lands in the turn that called it: a send's engaged
1167
- // channel, and a channel_open's move of the locus. (The dispatch functions
1168
- // run on a bare framework, as checkpoint-attribution.test.ts's harness does.)
1169
- function bareFramework() {
1170
- const framework = Object.create(AgentFramework.prototype);
1171
- framework.emitTrace = () => { };
1172
- framework.pushEvent = () => { };
1173
- framework.conversationAgentHomes = new Map();
1174
- framework.lastAnnouncedLocus = new Map();
1175
- framework.activeTurnTriggers = new Map();
1176
- framework.openLogical = new Map();
1177
- return framework;
1178
- }
1179
- const until = async (done, label) => {
1180
- for (const end = Date.now() + 2000; !done();) {
1181
- if (Date.now() > end)
1182
- throw new Error(`timed out waiting for ${label}`);
1183
- await new Promise((resolve) => setTimeout(resolve, 5));
1184
- }
1185
- };
1186
- // stop() ends every open turn, but a fresh start that awaits a deferred
1187
- // batch's assembly while the stop runs reaches its routing after it, opens
1188
- // its turn, and is then dropped before its stream sends anything: before
1189
- // its compile, once stop() has closed admission, or after it, once a
1190
- // restart has begun a lifecycle of its own.
1191
- for (const restartFirst of [false, true]) {
1192
- const name = restartFirst
1193
- ? "a start whose compile spans a stop() and restart is dropped and leaves no turn open, so the next start opens its own"
1194
- : "a start that stop() drops before its compile leaves no turn open, so the first start after a restart opens its own";
1195
- it(name, async () => {
1196
- // A store the framework doesn't own, so the same instance can start again.
1197
- const store = JsStore.openOrCreate({ path: join(tempDir, 'restart.chronicle') });
1198
- const framework = await AgentFramework.create({
1199
- store,
1200
- membrane: membrane.asMembrane(),
1201
- agents: [{ name: 'assistant', model: 'test-model', systemPrompt: 'You are a robot pilot.' }],
1202
- modules: [module],
1203
- syncIntervalMs: 0,
1204
- maintenanceIntervalMs: 0,
1205
- });
1206
- module.framework = framework;
1207
- const routed = stubChannelRegistry(framework);
1208
- const internals = framework;
1209
- const agent = framework.getAgent('assistant');
1210
- const wake = () => ({
1211
- agentName: 'assistant', reason: 'external-message', source: 'test', timestamp: Date.now(),
1212
- });
1213
- let entered;
1214
- const assembling = new Promise((resolve) => { entered = resolve; });
1215
- let release;
1216
- const released = new Promise((resolve) => { release = resolve; });
1217
- internals.pushCoalescer = {
1218
- pendingBatches: () => true,
1219
- assemble: async () => {
1220
- entered();
1221
- await released;
1222
- return { settled: new Set(), materialized: new Set() };
1223
- },
1224
- suspend: () => { },
1225
- isRendering: () => false,
1226
- };
1227
- const lines = [];
1228
- const error = console.error;
1229
- try {
1230
- console.error = (...args) => { lines.push(args.map(String).join(' ')); };
1231
- try {
1232
- const dropped = internals.startAgentStream(agent, wake());
1233
- await assembling;
1234
- await framework.stop();
1235
- if (restartFirst)
1236
- framework.start();
1237
- release();
1238
- await dropped;
1239
- }
1240
- finally {
1241
- console.error = error;
1242
- }
1243
- if (restartFirst) {
1244
- assert.ok(lines.some((line) => /stopped and started again/.test(line) && /dropped before it sends anything/.test(line)), lines.join('\n'));
1245
- }
1246
- else {
1247
- assert.equal(membrane.calls.length, 0, 'the start was dropped before its compile');
1248
- }
1249
- assert.deepEqual(routed, [], 'the dropped start routed nothing');
1250
- assert.equal(internals.openLogical.has('assistant'), false, 'the dropped start left no turn open');
1251
- if (!restartFirst)
1252
- framework.start();
1253
- internals.pushCoalescer = undefined;
1254
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'After the restart.' }]));
1255
- await internals.startAgentStream(agent, wake());
1256
- await until(() => routed.length > 0, "the next turn's speech");
1257
- // The dropped start resolved chan-live-1. Continuing its turn would
1258
- // have kept that locus, without resolving one.
1259
- assert.deepEqual(routed, [{ text: 'After the restart.', locus: 'chan-live-2' }], 'the next start resolved a locus of its own');
1260
- }
1261
- finally {
1262
- release();
1263
- await framework.stop();
1264
- store.close();
1265
- }
1266
- });
1267
- }
1268
- it("a send's engaged channel is recorded on the turn that sent, however late its open resolves", async () => {
1269
- const sending = { engaged: new Set() };
1270
- const next = { engaged: new Set() };
1271
- let resolveOpen;
1272
- const framework = bareFramework();
1273
- framework.mcplServerRegistry = {
1274
- getServer: (id) => id === 'chat' ? {
1275
- id: 'chat',
1276
- sendToolsCall: async () => ({ content: [{ type: 'text', text: 'sent' }] }),
1277
- } : null,
1278
- };
1279
- framework.mcplServerConfigs = new Map([['chat', { id: 'chat', toolPrefix: 'chat' }]]);
1280
- framework.mcplToolFeatureSets = new Map();
1281
- framework.checkpointManager = null;
1282
- framework.channelRegistry = {
1283
- openIfClosedForSend: () => new Promise((resolve) => { resolveOpen = resolve; }),
1284
- };
1285
- framework.turnRouting = new Map([['assistant', sending]]);
1286
- const results = [];
1287
- framework.pushEvent = (event) => { if (event.type === 'tool-result')
1288
- results.push(event); };
1289
- framework.dispatchMcplToolCall('assistant', {
1290
- id: 'send-1', name: 'chat--send_message', input: { channelId: 'discord:guild:portables', text: 'hi' },
1291
- }, 'chat', 'chat');
1292
- await until(() => results.length > 0 && resolveOpen !== undefined, 'the send to finish');
1293
- // The turn that sent has ended, and the next turn is the agent's live routing.
1294
- framework.turnRouting.set('assistant', next);
1295
- resolveOpen({ status: 'already-open', channelId: 'discord:guild:portables' });
1296
- await until(() => sending.engaged.size > 0, 'the open to resolve');
1297
- assert.deepEqual([...sending.engaged], ['discord:guild:portables']);
1298
- assert.deepEqual([...next.engaged], [], "the next turn didn't send there");
1299
- });
1300
- // A channel_open's result can land after a later start has made another
1301
- // object the agent's live routing. While the turn that called is open, the
1302
- // result moves that turn's locus and tells the agent. Once the turn has
1303
- // ended, the tool-result handler drops the result, so the agent never reads
1304
- // it, and nothing moves, whether or not a later start has come yet.
1305
- for (const [label, after, moves] of [
1306
- ['moves the locus of its turn, still open, when a silent start\'s routing is the live one', 'silent start', true],
1307
- ['moves no locus once its turn has ended and the next turn has opened', 'next turn', false],
1308
- ['moves no locus once its turn has settled, before any later start', 'settled', false],
1309
- ]) {
1310
- it(`a channel_open whose result lands late ${label}`, async () => {
1311
- const calling = { engaged: new Set(), locus: 'chan-before' };
1312
- const other = { engaged: new Set(), locus: 'chan-next' };
1313
- let resolveOpen;
1314
- const framework = bareFramework();
1315
- framework.agents = new Map([['assistant', { proseRouting: 'locus' }]]);
1316
- framework.channelRegistry = {
1317
- handleChannelToolCall: () => new Promise((resolve) => { resolveOpen = resolve; }),
1318
- };
1319
- framework.turnRouting = new Map([['assistant', calling]]);
1320
- framework.openLogical = new Map([['assistant', calling]]);
1321
- const results = [];
1322
- framework.pushEvent = (event) => {
1323
- if (event.type === 'tool-result')
1324
- results.push(event);
1325
- };
1326
- framework.dispatchChannelToolCall('assistant', {
1327
- id: 'open-1', name: 'channel_open', input: { channelId: 'discord:guild:observatory' },
1328
- });
1329
- await until(() => resolveOpen !== undefined, 'the open to start');
1330
- const turnRouting = framework.turnRouting;
1331
- const openLogical = framework.openLogical;
1332
- if (after === 'silent start')
1333
- turnRouting.set('assistant', other);
1334
- if (after === 'next turn') {
1335
- turnRouting.set('assistant', other);
1336
- openLogical.set('assistant', other);
1337
- }
1338
- if (after === 'settled')
1339
- openLogical.delete('assistant');
1340
- resolveOpen({ success: true, data: { channelId: 'discord:guild:observatory' } });
1341
- await until(() => results.length > 0, 'the tool result');
1342
- assert.equal(calling.triggerChannel, 'discord:guild:observatory', 'the turn that called records the channel it opened');
1343
- assert.equal(calling.locus, moves ? 'discord:guild:observatory' : 'chan-before');
1344
- assert.deepEqual(other, { engaged: new Set(), locus: 'chan-next' }, 'the other routing state is untouched');
1345
- assert.equal(framework.lastAnnouncedLocus.get('assistant'), moves ? 'discord:guild:observatory' : undefined, "the announce baseline moves with the locus, for a move the agent is told");
1346
- assert.equal(results[0].result.data?.routing !== undefined, moves, 'and the result says so only then');
1347
- });
1348
- }
1349
- for (const [label, silent, expected] of [
1350
- ['while its turn runs moves the locus and the announce baseline, and says so', false, 'discord:guild:observatory'],
1351
- ['in a silent turn moves no locus, and claims none', true, 'chan-before'],
1352
- ]) {
1353
- it(`a channel_open ${label}`, async () => {
1354
- const calling = { engaged: new Set(), locus: 'chan-before' };
1355
- const framework = bareFramework();
1356
- framework.agents = new Map([['assistant', { proseRouting: 'locus' }]]);
1357
- framework.channelRegistry = {
1358
- handleChannelToolCall: async () => ({ success: true, data: { channelId: 'discord:guild:observatory' } }),
1359
- };
1360
- framework.turnRouting = new Map([['assistant', calling]]);
1361
- if (!silent)
1362
- framework.openLogical = new Map([['assistant', calling]]);
1363
- framework.activeTurnTriggers.set('assistant', silent ? { suppressProse: true } : {});
1364
- const results = [];
1365
- framework.pushEvent = (event) => {
1366
- if (event.type === 'tool-result')
1367
- results.push(event);
1368
- };
1369
- framework.dispatchChannelToolCall('assistant', {
1370
- id: 'open-1', name: 'channel_open', input: { channelId: 'discord:guild:observatory' },
1371
- });
1372
- await until(() => results.length > 0, 'the tool result');
1373
- assert.equal(calling.locus, expected);
1374
- assert.equal(framework.lastAnnouncedLocus.get('assistant'), silent ? undefined : 'discord:guild:observatory');
1375
- assert.equal(results[0].result.data?.routing !== undefined, !silent, 'the result tells the agent only of a move it made');
1376
- });
1377
- }
1378
- it('a prose turn ends with a [delivered] receipt naming where the prose landed', async () => {
1379
- // Explicit sends receipt themselves via tool_result; auto-routed prose
1380
- // previously vanished with no in-window trace — the agent could never
1381
- // see where its own words went (2026-07-31 misroute series). One compact
1382
- // system message at logical turn end, channels deduped in delivery order.
1383
- membrane.pushResponse(createMockResponse([
1384
- { type: 'text', text: 'A quiet reply with no tools.' },
1385
- ]));
1386
- const framework = await createFramework();
1387
- stubChannelRegistry(framework);
1388
- trigger(framework);
1389
- await framework.runUntilIdle();
1390
- const cm = framework.agents.get('assistant').getContextManager();
1391
- const texts = cm.getAllMessages()
1392
- .flatMap((m) => m.content)
1393
- .filter((b) => b.type === 'text')
1394
- .map((b) => b.text ?? '');
1395
- const receipts = texts.filter((t) => t.startsWith('[delivered]'));
1396
- assert.deepEqual(receipts, ['[delivered] plain speech → chan-live-1']);
1397
- // The receipt is the LAST window message — after the assistant blocks.
1398
- assert.ok(texts[texts.length - 1].startsWith('[delivered]'), 'receipt sits at the settled tail');
1399
- await framework.stop();
1400
- });
1401
- it('a re-pinned turn\'s receipt names both channels in delivery order', async () => {
1402
- membrane.pushResponse(createMockResponse([
1403
- { type: 'text', text: 'Station four, proceeding.' },
1404
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1405
- ], 'tool_use'));
1406
- membrane.pushResponse(createMockResponse([
1407
- { type: 'text', text: 'Answering the person who addressed me.' },
1408
- ]));
1409
- const framework = await createFramework();
1410
- stubChannelRegistry(framework);
1411
- module.toolDelayMs = 25;
1412
- module.interjection = 'quick question over here?';
1413
- module.interjectionMetadata = { channelId: 'discord:dm:antra', tags: ['chat:addressed'] };
1414
- trigger(framework);
1415
- await framework.runUntilIdle();
1416
- const cm = framework.agents.get('assistant').getContextManager();
1417
- const receipts = cm.getAllMessages()
1418
- .flatMap((m) => m.content)
1419
- .filter((b) => b.type === 'text')
1420
- .map((b) => b.text ?? '')
1421
- .filter((t) => t.startsWith('[delivered]'));
1422
- assert.deepEqual(receipts, ['[delivered] plain speech → chan-live-1 · discord:dm:antra']);
1423
- await framework.stop();
1424
- });
1425
- it('suppressed prose is visible in the receipt (silencing is never a silent black hole)', async () => {
1426
- // n=8 (2026-07-31 ~22:46): one round multiplexing two threads — prose
1427
- // for the lane + an explicit send for another channel, textbook per the
1428
- // routing doc. Sticky silencing ate the prose reply and nothing told
1429
- // the author; their own record believed it delivered. The rule stands
1430
- // (antra: visibility is enough) — but the turn-end receipt now reports
1431
- // the suppression, so the segment's fate is visible one turn later.
1432
- membrane.pushResponse(createMockResponse([
1433
- { type: 'text', text: 'the reply that silencing will eat' },
1434
- { type: 'tool_use', id: 'c1', name: 'robot--send_message', input: { text: 'explicit send to the other thread' } },
1435
- ], 'tool_use'));
1436
- membrane.pushResponse(createMockResponse([
1437
- { type: 'text', text: 'trailing prose, also suppressed (sticky)' },
1438
- ]));
1439
- const framework = await createFramework();
1440
- const routed = stubChannelRegistry(framework);
1441
- trigger(framework);
1442
- await framework.runUntilIdle();
1443
- assert.deepEqual(routed, [], 'silencing semantics unchanged: nothing auto-routed');
1444
- const cm = framework.agents.get('assistant').getContextManager();
1445
- const receipts = cm.getAllMessages()
1446
- .flatMap((m) => m.content)
1447
- .filter((b) => b.type === 'text')
1448
- .map((b) => b.text ?? '')
1449
- .filter((t) => t.startsWith('[delivered]'));
1450
- assert.deepEqual(receipts, [
1451
- '[delivered] nothing — 2 plain-speech segment(s) suppressed (explicit send in the same round — resend with a send tool if it was meant to be heard)',
1452
- ]);
1453
- await framework.stop();
1454
- });
1455
- for (const verb of ['say', 'whisper']) {
1456
- it(`world \`${verb}\` silences adjacent auto-routed prose in locus mode (no double-publish)`, async () => {
1457
- // 2026-09-01 (Cairn, locus mode): a round of ordinary text + explicit
1458
- // world `say` published TWICE — seq 15146 was the say text, seq 15147
1459
- // the adjacent prose auto-routed to the same world locus, byte-for-byte.
1460
- // World publication verbs silenced only in hybrid mode; Discord sends
1461
- // silenced everywhere. An explicit world utterance is the resident's
1462
- // chosen speech for the round in every mode.
1463
- const input = verb === 'say'
1464
- ? { text: 'the intended utterance' }
1465
- : { to: 'sill', text: 'the intended utterance' };
1466
- membrane.pushResponse(createMockResponse([
1467
- { type: 'text', text: 'adjacent prose that must NOT auto-publish' },
1468
- { type: 'tool_use', id: 'c1', name: `robot--${verb}`, input },
1469
- ], 'tool_use'));
1470
- membrane.pushResponse(createMockResponse([
1471
- { type: 'text', text: 'trailing prose, also suppressed (sticky)' },
1472
- ]));
1473
- const framework = await createFramework();
1474
- const routed = stubChannelRegistry(framework);
1475
- trigger(framework);
1476
- await framework.runUntilIdle();
1477
- assert.deepEqual(routed, [], `${verb}: nothing auto-routed beside the explicit world utterance`);
1478
- const cm = framework.agents.get('assistant').getContextManager();
1479
- const receipts = cm.getAllMessages()
1480
- .flatMap((m) => m.content)
1481
- .filter((b) => b.type === 'text')
1482
- .map((b) => b.text ?? '')
1483
- .filter((t) => t.startsWith('[delivered]'));
1484
- assert.deepEqual(receipts, [
1485
- '[delivered] nothing — 2 plain-speech segment(s) suppressed (explicit send in the same round — resend with a send tool if it was meant to be heard)',
1486
- ], `${verb}: suppression is visible in the receipt`);
1487
- await framework.stop();
1488
- });
1489
- }
1490
- it('a non-publishing world tool (move) does not silence — speak-while-acting unchanged', async () => {
1491
- // Negative control for the world-verb silencing: only publication verbs
1492
- // silence. Ordinary acting tools still narrate live to the locus.
1493
- membrane.pushResponse(createMockResponse([
1494
- { type: 'text', text: 'walking over' },
1495
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1496
- ], 'tool_use'));
1497
- membrane.pushResponse(createMockResponse([
1498
- { type: 'text', text: 'there' },
1499
- ]));
1500
- const framework = await createFramework();
1501
- const routed = stubChannelRegistry(framework);
1502
- trigger(framework);
1503
- await framework.runUntilIdle();
1504
- assert.deepEqual(routed.map((r) => r.text), ['walking over', 'there']);
1505
- await framework.stop();
1506
- });
1507
- it('channel_open moves the pin mid-turn and announces in its own tool result', async () => {
1508
- // The agent's own deliberate open is the strongest "my next words go
1509
- // here" signal — stronger than any injection. The original 2026-07-21
1510
- // Aria fix only set next-turn trigger state; the turn-frozen refactor
1511
- // silently regressed the reply-right-after-opening case. Now the pin
1512
- // moves immediately and the announcement rides the tool result itself
1513
- // (model-requested content: distance zero, safest role).
1514
- membrane.pushResponse(createMockResponse([
1515
- { type: 'tool_use', id: 'c1', name: 'channel_open', input: { channelId: 'discord:guild:observatory' } },
1516
- ], 'tool_use'));
1517
- membrane.pushResponse(createMockResponse([
1518
- { type: 'text', text: 'Hello, observatory.' },
1519
- ]));
1520
- const framework = await createFramework();
1521
- const routed = stubChannelRegistry(framework);
1522
- const registry = framework.channelRegistry;
1523
- registry.handleChannelToolCall =
1524
- async () => ({ success: true, data: { channelId: 'discord:guild:observatory', opened: true } });
1525
- trigger(framework);
1526
- await framework.runUntilIdle();
1527
- assert.deepEqual(routed, [
1528
- { text: 'Hello, observatory.', locus: 'discord:guild:observatory' },
1529
- ], 'prose right after channel_open lands in the opened channel');
1530
- // The announcement rode the tool result the model saw.
1531
- const results = membrane.lastStream.receivedToolResults.flat();
1532
- assert.ok(JSON.stringify(results).includes('plain speech now lands in this channel'), 'routing note present in the channel_open tool result');
1533
- await framework.stop();
1534
- });
1535
- it('reactions and system markers injected mid-turn do not clear send suppression', async () => {
1536
- // A reaction (`chat:reaction` tag) or a `system: true` marker is not
1537
- // conversational input: prose following an explicit send stays suppressed,
1538
- // so a stray reaction can't make the agent double-post a "sent it"
1539
- // postscript — and (with the frozen locus) can't move routing either.
1540
- membrane.pushResponse(createMockResponse([
1541
- { type: 'tool_use', id: 'c1', name: 'robot--send_message', input: { text: 'reply to room4' } },
1542
- ], 'tool_use'));
1543
- membrane.pushResponse(createMockResponse([
1544
- { type: 'text', text: 'Sent it.' },
1545
- ]));
1546
- const framework = await createFramework();
1547
- const routed = stubChannelRegistry(framework);
1548
- module.interjection = '[reaction] @someone reacted 👍';
1549
- module.interjectionChannelId = null;
1550
- module.interjectionMetadata = { channelId: '999888777', tags: ['chat:reaction'] };
1551
- trigger(framework);
1552
- await framework.runUntilIdle();
1553
- assert.deepEqual(routed, [], 'post-send prose stayed suppressed after a reaction');
1554
- await framework.stop();
1555
- });
1556
- it('text-only turns route to the turn-frozen locus, not a live re-resolution', async () => {
1557
- // The text-only dispatch runs after the agent is idle; a live resolution
1558
- // there can read the NEXT turn's trigger state or a post-restart cleared
1559
- // one (2026-07-22 Sol DM misroute). The stub increments its locus per
1560
- // resolveLocus() call: the turn-start freeze consumes chan-live-1, so a
1561
- // live re-resolution at dispatch would return chan-live-2. The frozen
1562
- // path must deliver to chan-live-1.
1563
- membrane.pushResponse(createMockResponse([
1564
- { type: 'text', text: 'A quiet reply with no tools.' },
1565
- ]));
1566
- const framework = await createFramework();
1567
- const routed = stubChannelRegistry(framework);
1568
- trigger(framework);
1569
- await framework.runUntilIdle();
1570
- assert.deepEqual(routed, [
1571
- { text: 'A quiet reply with no tools.', locus: 'chan-live-1' },
1572
- ]);
1573
- await framework.stop();
1574
- });
1575
- it('announces the outbound locus in the window only when it changes', async () => {
1576
- // Turn 1 (locus chan-live-1, e2e): one durable [routing] notice — the
1577
- // boot baseline. Then the announce-on-change logic directly (MockMembrane
1578
- // supports one turn per test): same locus → silence (steady state must
1579
- // not chatter — KV); new locus → exactly one more notice.
1580
- membrane.pushResponse(createMockResponse([{ type: 'text', text: 'one' }]));
1581
- const framework = await createFramework();
1582
- stubChannelRegistry(framework);
1583
- const notices = [];
1584
- framework.onTrace((event) => {
1585
- const e = event;
1586
- if (e.type === 'message:added' && e.source === 'routing-notice') {
1587
- notices.push(e.source);
1588
- }
1589
- });
1590
- trigger(framework);
1591
- await framework.runUntilIdle();
1592
- assert.equal(notices.length, 1, 'first turn announced the boot-baseline locus');
1593
- const announce = framework.announceLocusIfChanged.bind(framework);
1594
- announce('assistant', 'chan-live-1');
1595
- assert.equal(notices.length, 1, 'unchanged locus announced nothing');
1596
- announce('assistant', 'chan-B');
1597
- assert.equal(notices.length, 2, 'changed locus announced exactly once');
1598
- announce('assistant', 'chan-B');
1599
- assert.equal(notices.length, 2, 'steady state on the new locus stays silent');
1600
- announce('assistant', null);
1601
- assert.equal(notices.length, 3, 'losing the locus is announced too');
1602
- await framework.stop();
1603
- });
1604
- // -------------------------------------------------------------------------
1605
- // Hear-while-acting
1606
- // -------------------------------------------------------------------------
1607
- it('injects a mid-turn message into the resumed stream at the tool boundary', async () => {
1608
- membrane.pushResponse(createMockResponse([
1609
- { type: 'text', text: 'Moving.' },
1610
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1611
- ], 'tool_use'));
1612
- membrane.pushResponse(createMockResponse([
1613
- { type: 'text', text: 'Heard you!' },
1614
- ]));
1615
- const framework = await createFramework();
1616
- module.interjection = 'look left!';
1617
- trigger(framework);
1618
- await framework.runUntilIdle();
1619
- const stream = membrane.lastStream;
1620
- assert.equal(stream.receivedToolResults.length, 1);
1621
- const options = stream.receivedToolResultOptions[0];
1622
- assert.ok(options?.injectedMessages, 'tool-result resume carried injected messages');
1623
- assert.equal(options.injectedMessages.length, 1);
1624
- const injected = options.injectedMessages[0];
1625
- assert.equal(injected.participant, 'Antra');
1626
- const text = injected.content
1627
- .filter((b) => b.type === 'text')
1628
- .map((b) => b.text)
1629
- .join('\n');
1630
- assert.equal(text, 'look left!');
1631
- await framework.stop();
1632
- });
1633
- it('passes no injection options when nothing arrived mid-turn', async () => {
1634
- membrane.pushResponse(createMockResponse([
1635
- { type: 'tool_use', id: 'c1', name: 'robot--move', input: { dir: 'north' } },
1636
- ], 'tool_use'));
1637
- membrane.pushResponse(createMockResponse([
1638
- { type: 'text', text: 'done' },
1639
- ]));
1640
- const framework = await createFramework();
1641
- trigger(framework);
1642
- await framework.runUntilIdle();
1643
- const stream = membrane.lastStream;
1644
- assert.equal(stream.receivedToolResults.length, 1);
1645
- assert.equal(stream.receivedToolResultOptions[0], undefined);
1646
- await framework.stop();
1647
- });
1648
- });
1649
- //# sourceMappingURL=present-while-acting.test.js.map