@animalabs/agent-framework 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/README.md +54 -4
  2. package/dist/src/agent.d.ts +17 -4
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +105 -43
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +41 -4
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/py-runner.d.ts +22 -1
  10. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  11. package/dist/src/code-execution/py-runner.js +64 -9
  12. package/dist/src/code-execution/py-runner.js.map +1 -1
  13. package/dist/src/code-execution/runtime-py.d.ts +4 -0
  14. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  15. package/dist/src/code-execution/runtime-py.js +215 -38
  16. package/dist/src/code-execution/runtime-py.js.map +1 -1
  17. package/dist/src/framework.d.ts +569 -59
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3091 -542
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +21 -0
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +40 -7
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/index.d.ts +7 -2
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +4 -0
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  30. package/dist/src/mcpl/channel-registry.js +26 -3
  31. package/dist/src/mcpl/channel-registry.js.map +1 -1
  32. package/dist/src/mcpl/conversation-router.d.ts +28 -1
  33. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  34. package/dist/src/mcpl/conversation-router.js +17 -0
  35. package/dist/src/mcpl/conversation-router.js.map +1 -1
  36. package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
  37. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  38. package/dist/src/mcpl/feature-set-manager.js +31 -7
  39. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  40. package/dist/src/mcpl/index.d.ts +4 -0
  41. package/dist/src/mcpl/index.d.ts.map +1 -1
  42. package/dist/src/mcpl/index.js +1 -0
  43. package/dist/src/mcpl/index.js.map +1 -1
  44. package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
  45. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  46. package/dist/src/mcpl/prose-stream-router.js +85 -22
  47. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  48. package/dist/src/mcpl/push-coalescer.d.ts +32 -4
  49. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  50. package/dist/src/mcpl/push-coalescer.js +46 -13
  51. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  52. package/dist/src/mcpl/push-handler.d.ts +5 -3
  53. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  54. package/dist/src/mcpl/push-handler.js +41 -11
  55. package/dist/src/mcpl/push-handler.js.map +1 -1
  56. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  57. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  58. package/dist/src/mcpl/reference-fetcher.js +20 -9
  59. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  60. package/dist/src/mcpl/references.d.ts +29 -6
  61. package/dist/src/mcpl/references.d.ts.map +1 -1
  62. package/dist/src/mcpl/references.js +57 -16
  63. package/dist/src/mcpl/references.js.map +1 -1
  64. package/dist/src/mcpl/server-connection.d.ts +32 -1
  65. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  66. package/dist/src/mcpl/server-connection.js +76 -11
  67. package/dist/src/mcpl/server-connection.js.map +1 -1
  68. package/dist/src/mcpl/server-registry.d.ts +5 -2
  69. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  70. package/dist/src/mcpl/server-registry.js +25 -7
  71. package/dist/src/mcpl/server-registry.js.map +1 -1
  72. package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
  73. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  74. package/dist/src/mcpl/tool-lifecycle.js +47 -1
  75. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  76. package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
  77. package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
  78. package/dist/src/mcpl/tool-pattern-check.js +79 -0
  79. package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
  80. package/dist/src/mcpl/types.d.ts +15 -5
  81. package/dist/src/mcpl/types.d.ts.map +1 -1
  82. package/dist/src/mcpl/types.js.map +1 -1
  83. package/dist/src/mcpl/visible-content.d.ts +39 -0
  84. package/dist/src/mcpl/visible-content.d.ts.map +1 -0
  85. package/dist/src/mcpl/visible-content.js +54 -0
  86. package/dist/src/mcpl/visible-content.js.map +1 -0
  87. package/dist/src/module-registry.d.ts +90 -6
  88. package/dist/src/module-registry.d.ts.map +1 -1
  89. package/dist/src/module-registry.js +260 -18
  90. package/dist/src/module-registry.js.map +1 -1
  91. package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
  92. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
  93. package/dist/src/modules/workspace/branch-intent.js +83 -0
  94. package/dist/src/modules/workspace/branch-intent.js.map +1 -0
  95. package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
  96. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
  97. package/dist/src/modules/workspace/disk-agreement.js +259 -0
  98. package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
  99. package/dist/src/modules/workspace/effects.d.ts +84 -0
  100. package/dist/src/modules/workspace/effects.d.ts.map +1 -0
  101. package/dist/src/modules/workspace/effects.js +368 -0
  102. package/dist/src/modules/workspace/effects.js.map +1 -0
  103. package/dist/src/modules/workspace/index.d.ts +155 -17
  104. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  105. package/dist/src/modules/workspace/index.js +1101 -424
  106. package/dist/src/modules/workspace/index.js.map +1 -1
  107. package/dist/src/modules/workspace/observe.d.ts +288 -0
  108. package/dist/src/modules/workspace/observe.d.ts.map +1 -0
  109. package/dist/src/modules/workspace/observe.js +825 -0
  110. package/dist/src/modules/workspace/observe.js.map +1 -0
  111. package/dist/src/modules/workspace/reconcile.d.ts +238 -0
  112. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
  113. package/dist/src/modules/workspace/reconcile.js +909 -0
  114. package/dist/src/modules/workspace/reconcile.js.map +1 -0
  115. package/dist/src/modules/workspace/sync.d.ts +6 -59
  116. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/sync.js +6 -257
  118. package/dist/src/modules/workspace/sync.js.map +1 -1
  119. package/dist/src/modules/workspace/types.d.ts +77 -10
  120. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  121. package/dist/src/modules/workspace/types.js.map +1 -1
  122. package/dist/src/operator-log.d.ts +41 -0
  123. package/dist/src/operator-log.d.ts.map +1 -1
  124. package/dist/src/operator-log.js.map +1 -1
  125. package/dist/src/provider-waits.d.ts +216 -0
  126. package/dist/src/provider-waits.d.ts.map +1 -0
  127. package/dist/src/provider-waits.js +482 -0
  128. package/dist/src/provider-waits.js.map +1 -0
  129. package/dist/src/record-journal.d.ts +127 -0
  130. package/dist/src/record-journal.d.ts.map +1 -0
  131. package/dist/src/record-journal.js +202 -0
  132. package/dist/src/record-journal.js.map +1 -0
  133. package/dist/src/refusal-reactions.d.ts +14 -12
  134. package/dist/src/refusal-reactions.d.ts.map +1 -1
  135. package/dist/src/refusal-reactions.js +14 -12
  136. package/dist/src/refusal-reactions.js.map +1 -1
  137. package/dist/src/shutdown.d.ts +13 -0
  138. package/dist/src/shutdown.d.ts.map +1 -0
  139. package/dist/src/shutdown.js +46 -0
  140. package/dist/src/shutdown.js.map +1 -0
  141. package/dist/src/silent-heartbeat.d.ts +74 -0
  142. package/dist/src/silent-heartbeat.d.ts.map +1 -0
  143. package/dist/src/silent-heartbeat.js +77 -0
  144. package/dist/src/silent-heartbeat.js.map +1 -0
  145. package/dist/src/tool-presentation.d.ts +50 -0
  146. package/dist/src/tool-presentation.d.ts.map +1 -0
  147. package/dist/src/tool-presentation.js +235 -0
  148. package/dist/src/tool-presentation.js.map +1 -0
  149. package/dist/src/tool-result-guard.d.ts +142 -6
  150. package/dist/src/tool-result-guard.d.ts.map +1 -1
  151. package/dist/src/tool-result-guard.js +357 -36
  152. package/dist/src/tool-result-guard.js.map +1 -1
  153. package/dist/src/tool-result-history.d.ts +13 -0
  154. package/dist/src/tool-result-history.d.ts.map +1 -1
  155. package/dist/src/tool-result-history.js +21 -1
  156. package/dist/src/tool-result-history.js.map +1 -1
  157. package/dist/src/types/agent.d.ts +18 -2
  158. package/dist/src/types/agent.d.ts.map +1 -1
  159. package/dist/src/types/events.d.ts +16 -0
  160. package/dist/src/types/events.d.ts.map +1 -1
  161. package/dist/src/types/framework.d.ts +12 -0
  162. package/dist/src/types/framework.d.ts.map +1 -1
  163. package/dist/src/types/module.d.ts +26 -4
  164. package/dist/src/types/module.d.ts.map +1 -1
  165. package/dist/src/types/trace.d.ts +87 -3
  166. package/dist/src/types/trace.d.ts.map +1 -1
  167. package/dist/test/batched-wake-locus.test.d.ts +2 -0
  168. package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
  169. package/dist/test/batched-wake-locus.test.js +166 -0
  170. package/dist/test/batched-wake-locus.test.js.map +1 -0
  171. package/dist/test/code-execution.test.js +386 -2
  172. package/dist/test/code-execution.test.js.map +1 -1
  173. package/dist/test/component-defaults.test.d.ts +2 -0
  174. package/dist/test/component-defaults.test.d.ts.map +1 -0
  175. package/dist/test/component-defaults.test.js +54 -0
  176. package/dist/test/component-defaults.test.js.map +1 -0
  177. package/dist/test/conversation-routing.test.js +57 -1
  178. package/dist/test/conversation-routing.test.js.map +1 -1
  179. package/dist/test/discord-awareness-outbox.test.js +3 -0
  180. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  181. package/dist/test/ephemeral-settle.test.js +3 -0
  182. package/dist/test/ephemeral-settle.test.js.map +1 -1
  183. package/dist/test/event-gate.test.js +118 -0
  184. package/dist/test/event-gate.test.js.map +1 -1
  185. package/dist/test/explicit-prose-routing.test.js +975 -0
  186. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  187. package/dist/test/framework.test.js +160 -1
  188. package/dist/test/framework.test.js.map +1 -1
  189. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  190. package/dist/test/helpers/coalescing-fixture.js +3 -2
  191. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  192. package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
  193. package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
  194. package/dist/test/helpers/pytc-stop-rows.js +143 -0
  195. package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
  196. package/dist/test/inference-exhaustion-rewind.test.js +142 -14
  197. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  198. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  199. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  200. package/dist/test/inference-failure-bounds.test.js +1201 -0
  201. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  202. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  203. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  204. package/dist/test/late-events-after-stop.test.js +122 -0
  205. package/dist/test/late-events-after-stop.test.js.map +1 -0
  206. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  207. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  208. package/dist/test/mcpl-coalescing.test.js +97 -22
  209. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  210. package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
  211. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
  212. package/dist/test/mcpl-config-diagnostics.test.js +452 -0
  213. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
  214. package/dist/test/mcpl-empty-content.test.d.ts +2 -0
  215. package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
  216. package/dist/test/mcpl-empty-content.test.js +368 -0
  217. package/dist/test/mcpl-empty-content.test.js.map +1 -0
  218. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  219. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  220. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  221. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  222. package/dist/test/mcpl-reference-fields.test.js +69 -2
  223. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  224. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
  225. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  226. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
  227. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
  228. package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
  229. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
  230. package/dist/test/mcpl-ws-transport.test.js +103 -0
  231. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  232. package/dist/test/ops-alert.test.js +2 -1
  233. package/dist/test/ops-alert.test.js.map +1 -1
  234. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  235. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  236. package/dist/test/placed-reaction-baseline.test.js +312 -0
  237. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  238. package/dist/test/prose-stream-router.test.js +41 -0
  239. package/dist/test/prose-stream-router.test.js.map +1 -1
  240. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  241. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  242. package/dist/test/provider-waits.test.d.ts +2 -0
  243. package/dist/test/provider-waits.test.d.ts.map +1 -0
  244. package/dist/test/provider-waits.test.js +2196 -0
  245. package/dist/test/provider-waits.test.js.map +1 -0
  246. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  247. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  248. package/dist/test/reaction-baseline-platform.test.js +124 -0
  249. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  250. package/dist/test/record-journal.test.d.ts +6 -0
  251. package/dist/test/record-journal.test.d.ts.map +1 -0
  252. package/dist/test/record-journal.test.js +269 -0
  253. package/dist/test/record-journal.test.js.map +1 -0
  254. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  255. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  256. package/dist/test/save-recent-image.test.js +240 -2
  257. package/dist/test/save-recent-image.test.js.map +1 -1
  258. package/dist/test/shutdown-errors.test.d.ts +2 -0
  259. package/dist/test/shutdown-errors.test.d.ts.map +1 -0
  260. package/dist/test/shutdown-errors.test.js +662 -0
  261. package/dist/test/shutdown-errors.test.js.map +1 -0
  262. package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
  263. package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
  264. package/dist/test/silent-heartbeat-rows.test.js +451 -0
  265. package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
  266. package/dist/test/silent-heartbeat.test.js +49 -38
  267. package/dist/test/silent-heartbeat.test.js.map +1 -1
  268. package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
  269. package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
  270. package/dist/test/stop-with-work-in-flight.test.js +672 -0
  271. package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
  272. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  273. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  274. package/dist/test/surgery-marks-receipt.test.js +540 -0
  275. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  276. package/dist/test/tool-batch-hook.test.d.ts +8 -0
  277. package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
  278. package/dist/test/tool-batch-hook.test.js +275 -0
  279. package/dist/test/tool-batch-hook.test.js.map +1 -0
  280. package/dist/test/tool-presentation.test.d.ts +2 -0
  281. package/dist/test/tool-presentation.test.d.ts.map +1 -0
  282. package/dist/test/tool-presentation.test.js +268 -0
  283. package/dist/test/tool-presentation.test.js.map +1 -0
  284. package/dist/test/tool-result-guard.test.js +2545 -19
  285. package/dist/test/tool-result-guard.test.js.map +1 -1
  286. package/dist/test/tool-result-history.test.js +2 -1
  287. package/dist/test/tool-result-history.test.js.map +1 -1
  288. package/dist/test/turn-trigger-provenance.test.js +35 -0
  289. package/dist/test/turn-trigger-provenance.test.js.map +1 -1
  290. package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
  291. package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
  292. package/dist/test/unconvertible-tool-result.test.js +358 -0
  293. package/dist/test/unconvertible-tool-result.test.js.map +1 -0
  294. package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
  295. package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
  296. package/dist/test/workspace-materialize-freshness.test.js +252 -0
  297. package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
  298. package/dist/test/workspace-three-way.test.d.ts +12 -0
  299. package/dist/test/workspace-three-way.test.d.ts.map +1 -0
  300. package/dist/test/workspace-three-way.test.js +3194 -0
  301. package/dist/test/workspace-three-way.test.js.map +1 -0
  302. package/dist/tsconfig.tsbuildinfo +1 -1
  303. package/package.json +2 -2
  304. package/src/agent.ts +124 -45
  305. package/src/api/server.ts +43 -4
  306. package/src/code-execution/py-runner.ts +66 -9
  307. package/src/code-execution/runtime-py.ts +215 -38
  308. package/src/framework.ts +3191 -544
  309. package/src/gate/event-gate.ts +59 -7
  310. package/src/index.ts +18 -2
  311. package/src/mcpl/channel-registry.ts +24 -3
  312. package/src/mcpl/conversation-router.ts +34 -1
  313. package/src/mcpl/feature-set-manager.ts +36 -11
  314. package/src/mcpl/index.ts +4 -0
  315. package/src/mcpl/prose-stream-router.ts +79 -23
  316. package/src/mcpl/push-coalescer.ts +62 -13
  317. package/src/mcpl/push-handler.ts +41 -12
  318. package/src/mcpl/reference-fetcher.ts +18 -8
  319. package/src/mcpl/references.ts +57 -13
  320. package/src/mcpl/server-connection.ts +95 -13
  321. package/src/mcpl/server-registry.ts +24 -7
  322. package/src/mcpl/tool-lifecycle.ts +77 -2
  323. package/src/mcpl/tool-pattern-check.ts +106 -0
  324. package/src/mcpl/types.ts +15 -5
  325. package/src/mcpl/visible-content.ts +57 -0
  326. package/src/module-registry.ts +269 -25
  327. package/src/modules/workspace/branch-intent.ts +123 -0
  328. package/src/modules/workspace/disk-agreement.ts +325 -0
  329. package/src/modules/workspace/effects.ts +362 -0
  330. package/src/modules/workspace/index.ts +1098 -446
  331. package/src/modules/workspace/observe.ts +865 -0
  332. package/src/modules/workspace/reconcile.ts +1157 -0
  333. package/src/modules/workspace/sync.ts +6 -336
  334. package/src/modules/workspace/types.ts +75 -10
  335. package/src/operator-log.ts +30 -0
  336. package/src/provider-waits.ts +523 -0
  337. package/src/record-journal.ts +246 -0
  338. package/src/refusal-reactions.ts +14 -12
  339. package/src/shutdown.ts +49 -0
  340. package/src/silent-heartbeat.ts +141 -0
  341. package/src/tool-presentation.ts +188 -0
  342. package/src/tool-result-guard.ts +384 -34
  343. package/src/tool-result-history.ts +25 -1
  344. package/src/types/agent.ts +18 -2
  345. package/src/types/events.ts +16 -0
  346. package/src/types/framework.ts +12 -0
  347. package/src/types/module.ts +27 -4
  348. package/src/types/trace.ts +94 -3
@@ -0,0 +1,269 @@
1
+ /**
2
+ * RecordJournal — typed chronicle records that don't follow branch switches,
3
+ * replayed from the latest checkpoint, with the two durability barriers named.
4
+ */
5
+ import { describe, it } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { mkdtempSync, rmSync } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { JsStore } from '@animalabs/chronicle';
12
+ import { RecordJournal } from '../src/record-journal.js';
13
+ const KILL_FIXTURE = join(import.meta.dirname, 'fixtures/record-journal-kill.mjs');
14
+ function withStore(fn) {
15
+ const dir = mkdtempSync(join(tmpdir(), 'record-journal-'));
16
+ const path = join(dir, 'store');
17
+ const store = JsStore.openOrCreate({ path });
18
+ try {
19
+ return fn(store, path);
20
+ }
21
+ finally {
22
+ if (!store.isClosed())
23
+ store.close();
24
+ rmSync(dir, { recursive: true, force: true });
25
+ }
26
+ }
27
+ const entriesOf = (journal) => journal.load().entries.map((e) => e.entry.n);
28
+ describe('RecordJournal', () => {
29
+ it('an empty journal loads nothing', () => {
30
+ withStore((store) => {
31
+ const journal = new RecordJournal(store, { type: 'journal-test/entry' });
32
+ assert.deepStrictEqual(journal.load(), { snapshot: null, entries: [] });
33
+ assert.strictEqual(journal.entriesSinceCheckpoint, 0);
34
+ });
35
+ });
36
+ it('append returns where the record landed; a fresh load replays entries in append order', () => {
37
+ withStore((store) => {
38
+ const journal = new RecordJournal(store, { type: 'journal-test/entry' });
39
+ const a = journal.append({ n: 'a' });
40
+ const b = journal.append({ n: 'b' });
41
+ assert.match(a.id, /^\d+$/);
42
+ assert.ok(BigInt(b.id) > BigInt(a.id), 'ids increase in append order');
43
+ assert.strictEqual(typeof a.sequence, 'number');
44
+ const loaded = new RecordJournal(store, { type: 'journal-test/entry' }).load();
45
+ assert.deepStrictEqual(loaded.entries, [
46
+ { id: a.id, sequence: a.sequence, entry: { n: 'a' } },
47
+ { id: b.id, sequence: b.sequence, entry: { n: 'b' } },
48
+ ]);
49
+ });
50
+ });
51
+ it('entries from every branch load from any branch, in global order, through deleteBranch and reopen', () => {
52
+ withStore((store, path) => {
53
+ const journal = new RecordJournal(store, { type: 'journal-test/entry' });
54
+ journal.append({ n: 'main-0' });
55
+ const fork = store.currentSequence();
56
+ journal.append({ n: 'main-1' });
57
+ const main = store.currentBranch().name;
58
+ store.createBranchAt('rollback', main, fork);
59
+ store.switchBranch('rollback');
60
+ journal.append({ n: 'rollback-2' });
61
+ assert.deepStrictEqual(entriesOf(journal), ['main-0', 'main-1', 'rollback-2'], 'the rolled-back branch still sees what main appended after the fork');
62
+ store.switchBranch(main);
63
+ journal.append({ n: 'main-3' });
64
+ store.deleteBranch('rollback');
65
+ assert.deepStrictEqual(entriesOf(journal), ['main-0', 'main-1', 'rollback-2', 'main-3']);
66
+ store.close();
67
+ const reopened = JsStore.openOrCreate({ path });
68
+ try {
69
+ assert.deepStrictEqual(entriesOf(new RecordJournal(reopened, { type: 'journal-test/entry' })), ['main-0', 'main-1', 'rollback-2', 'main-3']);
70
+ }
71
+ finally {
72
+ reopened.close();
73
+ }
74
+ });
75
+ });
76
+ it('load returns the latest checkpoint and only the entries after it; other records do not disturb it', () => {
77
+ withStore((store) => {
78
+ store.registerState({ id: 'journal-test/tree', strategy: 'tree' });
79
+ const journal = new RecordJournal(store, { type: 'journal-test/entry' });
80
+ const other = new RecordJournal(store, { type: 'journal-test/other' });
81
+ journal.append({ n: 'a' });
82
+ other.append({ n: 'x' });
83
+ journal.append({ n: 'b' });
84
+ journal.checkpoint({ seen: ['a', 'b'] });
85
+ assert.strictEqual(journal.entriesSinceCheckpoint, 0);
86
+ store.treeSet('journal-test/tree', 'f.txt', { blobHash: 'f'.repeat(64), size: 1, mode: 0o644 });
87
+ journal.append({ n: 'c' });
88
+ other.append({ n: 'y' });
89
+ assert.strictEqual(journal.entriesSinceCheckpoint, 1);
90
+ const reader = new RecordJournal(store, { type: 'journal-test/entry' });
91
+ const first = reader.load();
92
+ assert.deepStrictEqual(first.snapshot, { seen: ['a', 'b'] });
93
+ assert.deepStrictEqual(first.entries.map((e) => e.entry.n), ['c']);
94
+ assert.strictEqual(reader.entriesSinceCheckpoint, 1);
95
+ // A checkpoint after load covers what was loaded; the newest one wins.
96
+ reader.checkpoint({ seen: ['a', 'b', 'c'] });
97
+ const second = new RecordJournal(store, { type: 'journal-test/entry' }).load();
98
+ assert.deepStrictEqual(second, { snapshot: { seen: ['a', 'b', 'c'] }, entries: [] });
99
+ assert.deepStrictEqual(entriesOf(new RecordJournal(store, { type: 'journal-test/other' })), ['x', 'y']);
100
+ });
101
+ });
102
+ it('names its two barriers: afterCommittedState syncs before the append, durable after it, neither by default', () => {
103
+ withStore((store) => {
104
+ const calls = [];
105
+ const recording = {
106
+ getRecordIdsByType: (type) => store.getRecordIdsByType(type),
107
+ getRecord: (id) => store.getRecord(id),
108
+ appendJson: (type, data) => { calls.push('append'); return store.appendJson(type, data); },
109
+ sync: () => { calls.push('sync'); store.sync(); },
110
+ };
111
+ const journal = new RecordJournal(recording, { type: 'journal-test/entry' });
112
+ journal.append({ n: 'plain' });
113
+ assert.deepStrictEqual(calls.splice(0), ['append'], 'a plain append does not sync');
114
+ journal.append({ n: 'asserting' }, { afterCommittedState: true });
115
+ assert.deepStrictEqual(calls.splice(0), ['sync', 'append']);
116
+ journal.append({ n: 'durable' }, { durable: true });
117
+ assert.deepStrictEqual(calls.splice(0), ['append', 'sync']);
118
+ journal.append({ n: 'both' }, { afterCommittedState: true, durable: true });
119
+ assert.deepStrictEqual(calls.splice(0), ['sync', 'append', 'sync']);
120
+ journal.checkpoint({ n: 1 }, { durable: true });
121
+ assert.deepStrictEqual(calls.splice(0), ['append', 'sync']);
122
+ });
123
+ });
124
+ it('after a hard kill, an afterCommittedState entry never survives without the state it asserts', () => {
125
+ withStore((store, path) => {
126
+ store.registerState({ id: 'journal-test/tree', strategy: 'tree' });
127
+ store.sync();
128
+ store.close();
129
+ const child = spawnSync(process.execPath, [KILL_FIXTURE, path, 'asserting'], { encoding: 'utf-8' });
130
+ assert.strictEqual(child.signal, 'SIGKILL', `the child died by SIGKILL (stderr: ${child.stderr})`);
131
+ const reopened = JsStore.openOrCreate({ path });
132
+ try {
133
+ try {
134
+ reopened.registerState({ id: 'journal-test/tree', strategy: 'tree' });
135
+ }
136
+ catch { /* exists */ }
137
+ const entries = new RecordJournal(reopened, { type: 'journal-test/entry' }).load().entries;
138
+ assert.strictEqual(entries.length, 1, 'the appended entry survived the kill');
139
+ assert.notStrictEqual(reopened.treeGet('journal-test/tree', 'committed.txt'), null, 'the state it asserts survived with it');
140
+ }
141
+ finally {
142
+ reopened.close();
143
+ }
144
+ });
145
+ });
146
+ it('a write that fails after reaching the store blocks further writes until load() replays it', () => {
147
+ withStore((store) => {
148
+ let failNextSync = false;
149
+ const flaky = {
150
+ getRecordIdsByType: (type) => store.getRecordIdsByType(type),
151
+ getRecord: (id) => store.getRecord(id),
152
+ appendJson: (type, data) => store.appendJson(type, data),
153
+ sync: () => {
154
+ if (failNextSync) {
155
+ failNextSync = false;
156
+ throw new Error('disk full');
157
+ }
158
+ store.sync();
159
+ },
160
+ };
161
+ const journal = new RecordJournal(flaky, { type: 'journal-test/entry' });
162
+ // The durable barrier fails after A reached the store: the caller never reduces A.
163
+ failNextSync = true;
164
+ assert.throws(() => journal.append({ n: 'A' }, { durable: true }), /disk full/);
165
+ assert.strictEqual(journal.needsReconcile, true);
166
+ // Without reconciliation, a later write and checkpoint would cover A unreduced.
167
+ assert.throws(() => journal.append({ n: 'B' }), /load\(\) and reduce its tail before writing again/);
168
+ assert.throws(() => journal.checkpoint({ seen: ['B'] }), /load\(\) and reduce its tail/);
169
+ const reconciled = journal.load();
170
+ assert.deepStrictEqual(reconciled.entries.map((e) => e.entry.n), ['A'], 'load replays the ambiguous record');
171
+ assert.strictEqual(journal.needsReconcile, false);
172
+ journal.append({ n: 'B' });
173
+ journal.checkpoint({ seen: ['A', 'B'] });
174
+ assert.deepStrictEqual(new RecordJournal(store, { type: 'journal-test/entry' }).load(), { snapshot: { seen: ['A', 'B'] }, entries: [] });
175
+ // A failure BEFORE the record reaches the store wrote nothing and leaves the journal usable.
176
+ failNextSync = true;
177
+ assert.throws(() => journal.append({ n: 'C' }, { afterCommittedState: true }), /disk full/);
178
+ assert.strictEqual(journal.needsReconcile, false);
179
+ journal.append({ n: 'D' });
180
+ assert.deepStrictEqual(new RecordJournal(store, { type: 'journal-test/entry' }).load().entries.map((e) => e.entry.n), ['D']);
181
+ });
182
+ });
183
+ it('an append that throws inside the store blocks writes for the life of the journal; a journal on the reopened store loads its record', () => {
184
+ withStore((store) => {
185
+ // As Chronicle's append can fail: the record is written to the log, the
186
+ // periodic sync then fails, and the record never reaches the index this
187
+ // process reads. Reopening the store indexes it from the log. `hidden`
188
+ // is this process's view of the store.
189
+ let failAppend = null;
190
+ const hidden = new Set();
191
+ const live = {
192
+ getRecordIdsByType: (type) => store.getRecordIdsByType(type).filter((id) => !hidden.has(id)),
193
+ getRecord: (id) => (hidden.has(id) ? null : store.getRecord(id)),
194
+ appendJson: (type, data) => {
195
+ if (failAppend === 'before-writing') {
196
+ failAppend = null;
197
+ throw new Error('EIO: write failed');
198
+ }
199
+ const record = store.appendJson(type, data);
200
+ if (failAppend === 'after-writing') {
201
+ failAppend = null;
202
+ hidden.add(record.id);
203
+ throw new Error('EIO: fsync failed');
204
+ }
205
+ return record;
206
+ },
207
+ sync: () => store.sync(),
208
+ };
209
+ const journal = new RecordJournal(live, { type: 'journal-test/entry' });
210
+ journal.append({ n: 'A' });
211
+ failAppend = 'after-writing';
212
+ assert.throws(() => journal.append({ n: 'B' }), /EIO: fsync failed/);
213
+ assert.strictEqual(journal.needsReconcile, false, 'no load() on this store can read B back');
214
+ assert.strictEqual(journal.needsReopen, true, 'what this view holds may lack B until a reopen');
215
+ assert.deepStrictEqual(journal.load().entries.map((e) => e.entry.n), ['A'], 'B is outside this view of the store');
216
+ // A checkpoint now would cover B, unreduced, so nothing more is written.
217
+ const blocked = /append failed inside the store \(EIO: fsync failed\).*until the store is reopened/;
218
+ assert.throws(() => journal.append({ n: 'C' }), blocked);
219
+ assert.throws(() => journal.checkpoint({ seen: ['A'] }), blocked);
220
+ assert.throws(() => journal.append({ n: 'C' }), blocked, 'load() does not lift it');
221
+ // A journal on the reopened store loads B with its tail, and writes again.
222
+ const reopened = new RecordJournal(store, { type: 'journal-test/entry' });
223
+ assert.deepStrictEqual(reopened.load().entries.map((e) => e.entry.n), ['A', 'B']);
224
+ reopened.append({ n: 'C' });
225
+ reopened.checkpoint({ seen: ['A', 'B', 'C'] });
226
+ assert.deepStrictEqual(new RecordJournal(store, { type: 'journal-test/entry' }).load(), { snapshot: { seen: ['A', 'B', 'C'] }, entries: [] });
227
+ // An append that fails before writing anything looks the same from
228
+ // here, so it blocks the same way; the reopened store has nothing new.
229
+ const second = new RecordJournal(live, { type: 'journal-test/entry' });
230
+ second.load();
231
+ failAppend = 'before-writing';
232
+ assert.throws(() => second.append({ n: 'X' }), /EIO: write failed/);
233
+ assert.throws(() => second.append({ n: 'D' }), /until the store is reopened/);
234
+ assert.deepStrictEqual(new RecordJournal(store, { type: 'journal-test/entry' }).load(), { snapshot: { seen: ['A', 'B', 'C'] }, entries: [] });
235
+ });
236
+ });
237
+ it('a durable checkpoint whose barrier throws blocks writes until load() replays it', () => {
238
+ withStore((store) => {
239
+ let failNextSync = false;
240
+ const flaky = {
241
+ getRecordIdsByType: (type) => store.getRecordIdsByType(type),
242
+ getRecord: (id) => store.getRecord(id),
243
+ appendJson: (type, data) => store.appendJson(type, data),
244
+ sync: () => {
245
+ if (failNextSync) {
246
+ failNextSync = false;
247
+ throw new Error('disk full');
248
+ }
249
+ store.sync();
250
+ },
251
+ };
252
+ const journal = new RecordJournal(flaky, { type: 'journal-test/entry' });
253
+ journal.append({ n: 'A' });
254
+ failNextSync = true;
255
+ assert.throws(() => journal.checkpoint({ seen: ['A'] }, { durable: true }), /disk full/);
256
+ assert.strictEqual(journal.needsReconcile, true);
257
+ assert.strictEqual(journal.needsReopen, false, 'the checkpoint is in this view of the store');
258
+ assert.throws(() => journal.append({ n: 'C' }), /load\(\) and reduce its tail/);
259
+ assert.deepStrictEqual(journal.load(), { snapshot: { seen: ['A'] }, entries: [] }, 'the checkpoint landed');
260
+ journal.append({ n: 'C' });
261
+ });
262
+ });
263
+ it('refuses a checkpoint type equal to the entry type', () => {
264
+ withStore((store) => {
265
+ assert.throws(() => new RecordJournal(store, { type: 't', checkpointType: 't' }), /distinct record types/);
266
+ });
267
+ });
268
+ });
269
+ //# sourceMappingURL=record-journal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-journal.test.js","sourceRoot":"","sources":["../../test/record-journal.test.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * The exported REFUSAL_REACTION_BASELINE must be exactly the set of markers
3
3
  * reactToRefusal can emit — every category's emoji plus the unknown-category
4
- * fallback, nothing more, nothing less. Host composition serializes this
5
- * export into DISCORD_SUPPRESSED_REACTIONS_BASELINE; if the emitted set and
6
- * the export could drift, a framework annotation could re-enter a resident's
4
+ * fallback, nothing more, nothing less. It is the refusal set only: the
5
+ * protective default the framework injects into DISCORD_SUPPRESSED_REACTIONS_BASELINE
6
+ * is framework.getPlacedReactionBaseline(), which adds the awareness markers
7
+ * (test/placed-reaction-baseline.test.ts). If the emitted refusal set and the
8
+ * export could drift, a framework annotation could re-enter a resident's
7
9
  * context as a reaction event (the 8/3 Mythos self-amplifying refusal loop).
8
10
  * The implementation shares one constant; these tests pin the contract so a
9
11
  * refactor that splits them fails here first.
@@ -59,6 +61,11 @@ describe('refusal-reaction baseline export', () => {
59
61
  // names — no stale entries suppressing markers nothing stamps anymore.
60
62
  assert.deepEqual(emitted, new Set(REFUSAL_REACTION_BASELINE));
61
63
  });
64
+ it('a category named like an inherited property takes the fallback', async () => {
65
+ for (const category of ['constructor', 'toString', '__proto__']) {
66
+ assert.equal(await emittedFor(category), REFUSAL_REACTION_FALLBACK, category);
67
+ }
68
+ });
62
69
  it('serialized baseline survives the Discord adapter env round-trip (comma-join)', () => {
63
70
  // Host composition joins with ','; discord-mcpl's parseSuppressionEnvTokens
64
71
  // splits on ',' and trims. Entries therefore must not contain commas or
@@ -1 +1 @@
1
- {"version":3,"file":"refusal-reaction-baseline.test.js","sourceRoot":"","sources":["../../test/refusal-reaction-baseline.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"refusal-reaction-baseline.test.js","sourceRoot":"","sources":["../../test/refusal-reaction-baseline.test.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,7 @@ import { AgentFramework } from '../src/index.js';
32
32
  import { WorkspaceModule } from '../src/modules/workspace/index.js';
33
33
  import { FALLBACK_MEDIA_TYPE, TOOL_IMAGE_REF_RE, ToolImageLedger, formatPreservedImageSlots, formatToolImagePlaceholder, normalizeMediaType, parseImagePlaceholders, splitPreservingImageSlots, } from '../src/tool-image-ledger.js';
34
34
  import { toolResultDataToHistoryString, truncateForHistory } from '../src/tool-result-history.js';
35
+ import { classifyBlock, referenceRegistry } from '../src/mcpl/references.js';
35
36
  import { createMockResponse, MockMembrane } from './helpers/mock-membrane.js';
36
37
  const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4////fwAJ+wP9KobjigAAAABJRU5ErkJggg==', 'base64');
37
38
  const GIF = Buffer.from('R0lGODdhAQABAIEAAP///wAAAAAAAAAAACwAAAAAAQABAAAIBAABBAQAOw==', 'base64');
@@ -140,6 +141,7 @@ async function startTurn(opts) {
140
141
  { type: 'image', source: { type: 'base64', data: GIF.toString('base64'), mediaType: 'image/gif' } },
141
142
  ]);
142
143
  opts.seedHistory?.(framework);
144
+ opts.workspace?.(workspace);
143
145
  framework.start();
144
146
  framework.pushEvent({
145
147
  type: 'external-message',
@@ -347,7 +349,142 @@ describe('save_recent_image provenance (issue #104)', () => {
347
349
  rmSync(h.tempDir, { recursive: true, force: true });
348
350
  }
349
351
  });
350
- it('an image-typed RFC-005 reference stub occupies a failing slot that points at fetch_reference', async () => {
352
+ it("a reference whose claimed type carries bidi controls is the image its stub shows, and its refusal shows the type without them", async () => {
353
+ // The stub shows `Image/PNG`, so after a restart the stub's text alone
354
+ // makes this a slot (matched without regard to case); the record must
355
+ // say the same, or the index slides.
356
+ const c = classifyBlock({ type: 'image', uri: 'https://example.invalid/sri-label.png', mimeType: '\u202EImage/PNG\u061C' });
357
+ assert.strictEqual(c.kind, 'reference');
358
+ const record = referenceRegistry.register(c.testimony);
359
+ const h = await startTurn({
360
+ prefix: 'sri-reference-label-',
361
+ responses: [[saveCall('call_save', { path: 'files/label.png', index: 0 })], done],
362
+ seedHistory: (framework) => {
363
+ framework.getAgent('prime').getContextManager().addMessage('user', [{
364
+ type: 'tool_result',
365
+ toolUseId: 'call_cam',
366
+ toolName: 'mcpl--vst--camera',
367
+ content: `[${record.refId}] label.png — Image/PNG, 1.2MB claimed — from tool result — fetch with fetch_reference`,
368
+ isError: false,
369
+ }]);
370
+ },
371
+ });
372
+ try {
373
+ const result = await waitForToolResult(h.framework, 'call_save');
374
+ assert.strictEqual(result.isError, true, `index 0 is the reference, not the older GIF: ${result.content}`);
375
+ assert.match(result.content, new RegExp(`index 0 is a reference \\(${record.refId}, image/png, from`));
376
+ assert.ok(!/[\u061C\u202E]/.test(result.content), result.content);
377
+ assert.strictEqual(await fileBytes(h, 'files/label.png'), null);
378
+ }
379
+ finally {
380
+ await h.framework.stop();
381
+ rmSync(h.tempDir, { recursive: true, force: true });
382
+ }
383
+ });
384
+ it('an expired reference refuses with its reason; one with a saved copy still points at fetch_reference', async () => {
385
+ const register = (name) => {
386
+ const c = classifyBlock({ type: 'image', uri: `https://example.invalid/${name}`, mimeType: 'image/png', expiresAt: '2001-01-01T00:00:00Z' });
387
+ assert.strictEqual(c.kind, 'reference');
388
+ return referenceRegistry.register(c.testimony);
389
+ };
390
+ const kept = register('sri-expired-kept.png');
391
+ kept.fetchedPath = 'files/refs/kept.png'; // saved before its expiry passed: fetch_reference serves it
392
+ const expired = register('sri-expired.png');
393
+ const h = await startTurn({
394
+ prefix: 'sri-reference-expired-',
395
+ responses: [
396
+ [saveCall('call_save0', { path: 'files/expired.png', index: 0 })],
397
+ [saveCall('call_save1', { path: 'files/kept.png', index: 1 })],
398
+ done,
399
+ ],
400
+ seedHistory: (framework) => {
401
+ framework.getAgent('prime').getContextManager().addMessage('user', [{
402
+ type: 'tool_result',
403
+ toolUseId: 'call_cam',
404
+ toolName: 'mcpl--vst--camera',
405
+ content: `[${kept.refId}] kept.png — image/png — from tool result — saved: files/refs/kept.png\n`
406
+ + `[${expired.refId}] expired.png — image/png — from tool result — expired: its expiresAt has passed`,
407
+ isError: false,
408
+ }]);
409
+ },
410
+ });
411
+ try {
412
+ const refused = await waitForToolResult(h.framework, 'call_save0');
413
+ assert.strictEqual(refused.isError, true);
414
+ assert.match(refused.content, new RegExp(`index 0 is a reference \\(${expired.refId}, image/png, from`));
415
+ assert.match(refused.content, /It can't be fetched either — expired: its expiresAt has passed\./);
416
+ assert.ok(!refused.content.includes('fetch_reference'), refused.content);
417
+ const keptRefusal = await waitForToolResult(h.framework, 'call_save1');
418
+ assert.strictEqual(keptRefusal.isError, true);
419
+ assert.match(keptRefusal.content, new RegExp(`index 1 is a reference \\(${kept.refId}, image/png, from`));
420
+ assert.match(keptRefusal.content, /Fetch it with fetch_reference and save the materialized file instead\./);
421
+ }
422
+ finally {
423
+ await h.framework.stop();
424
+ rmSync(h.tempDir, { recursive: true, force: true });
425
+ }
426
+ });
427
+ it("an attachment's media type is shown and stored as a label, and one with nothing left counts as absent", async () => {
428
+ // The store keeps a declared type only for bytes it can't sniff as a
429
+ // raster image, so these aren't images it knows. Oldest first: a type
430
+ // with controls in it, one that sanitizes to nothing, and a url-source
431
+ // image with no type at all (index 0: not inline, so not saveable).
432
+ const bytes = (tag) => Buffer.from(`not a raster image ${tag}`).toString('base64');
433
+ const h = await startTurn({
434
+ prefix: 'sri-attachment-type-',
435
+ responses: [[saveCall('call_save', { path: 'files/sent.bmp', index: 1, count: 2 })], done],
436
+ seedHistory: (framework) => {
437
+ framework.getAgent('prime').getContextManager().addMessage('user', [
438
+ { type: 'image', source: { type: 'base64', data: bytes('a'), mediaType: 'image/\u202Ebmp\u0085' } },
439
+ { type: 'image', source: { type: 'base64', data: bytes('b'), mediaType: '\u202E\u061C' } },
440
+ { type: 'image', source: { type: 'url', url: 'https://example.invalid/sri-url.png' } },
441
+ ]);
442
+ },
443
+ });
444
+ try {
445
+ const result = await waitForToolResult(h.framework, 'call_save');
446
+ assert.ok(!/[\u0085\u061C\u202E]/.test(result.content), result.content);
447
+ const receipts = saved(result);
448
+ assert.deepStrictEqual(receipts.map((r) => [r.imageIndex, r.mediaType, r.mimeType]), [
449
+ [1, 'image/png', 'image/png'],
450
+ [2, 'image/bmp', 'image/bmp'],
451
+ ]);
452
+ }
453
+ finally {
454
+ await h.framework.stop();
455
+ rmSync(h.tempDir, { recursive: true, force: true });
456
+ }
457
+ });
458
+ it('a reference with no record shows its type bounded as a stub would, and says to re-run the tool', async () => {
459
+ // A stub line the registry has no record of (after a restart, or quoted
460
+ // text), whose type is far longer than a stub would show.
461
+ const longType = `image/${'x'.repeat(300)}`;
462
+ const h = await startTurn({
463
+ prefix: 'sri-reference-unknown-',
464
+ responses: [[saveCall('call_save', { path: 'files/long.png', index: 0 })], done],
465
+ seedHistory: (framework) => {
466
+ framework.getAgent('prime').getContextManager().addMessage('user', [{
467
+ type: 'tool_result',
468
+ toolUseId: 'call_cam',
469
+ toolName: 'mcpl--vst--camera',
470
+ content: `[ref_3_cc02] long.png — ${longType} — from tool result — fetch with fetch_reference`,
471
+ isError: false,
472
+ }]);
473
+ },
474
+ });
475
+ try {
476
+ const result = await waitForToolResult(h.framework, 'call_save');
477
+ assert.strictEqual(result.isError, true);
478
+ assert.ok(result.content.includes(`(ref_3_cc02, image/${'x'.repeat(113)}…, from`), result.content);
479
+ assert.match(result.content, /holds no record of it .*If its tool can produce it again, re-run that tool for a fresh reference\./);
480
+ assert.ok(!result.content.includes('Fetch it with fetch_reference'), result.content);
481
+ }
482
+ finally {
483
+ await h.framework.stop();
484
+ rmSync(h.tempDir, { recursive: true, force: true });
485
+ }
486
+ });
487
+ it('an image-typed RFC-005 reference stub with no record still occupies a failing slot', async () => {
351
488
  const h = await startTurn({
352
489
  prefix: 'sri-reference-',
353
490
  responses: [
@@ -370,7 +507,7 @@ describe('save_recent_image provenance (issue #104)', () => {
370
507
  const result = await waitForToolResult(h.framework, 'call_save');
371
508
  assert.strictEqual(result.isError, true);
372
509
  assert.match(result.content, /index 0 is a reference \(ref_1_ab3d, image\/png, from mcpl--vst--camera \(call call_cam\)\)/);
373
- assert.match(result.content, /fetch_reference/);
510
+ assert.match(result.content, /holds no record of it/, 'no record here: fetch_reference could only call it unknown');
374
511
  assert.strictEqual(await fileBytes(h, 'files/cam.png'), null);
375
512
  // The text/plain reference is not an image slot: index 1 is the GIF.
376
513
  const gif = saved(await waitForToolResult(h.framework, 'call_save_gif'));
@@ -733,4 +870,105 @@ describe('ToolImageLedger', () => {
733
870
  ]);
734
871
  });
735
872
  });
873
+ it('a write that throws before its tree entry names the images already saved', async () => {
874
+ const h = await startTurn({
875
+ prefix: 'sri-throw-',
876
+ responses: [
877
+ [snapCall('call_snap')],
878
+ [saveCall('call_save', { path: 'files/images.png', index: 0, count: 2 })],
879
+ done,
880
+ ],
881
+ workspace: (workspace) => {
882
+ const write = workspace.writeBinary.bind(workspace);
883
+ let writes = 0;
884
+ workspace.writeBinary = async (...args) => {
885
+ if (++writes === 2)
886
+ throw new Error('injected tree exception');
887
+ return write(...args);
888
+ };
889
+ },
890
+ });
891
+ try {
892
+ const result = await waitForToolResult(h.framework, 'call_save');
893
+ assert.equal(result.isError, true);
894
+ assert.match(result.content, /injected tree exception/);
895
+ assert.match(result.content, /already saved: files\/images-0.png/);
896
+ }
897
+ finally {
898
+ await h.framework.stop();
899
+ rmSync(h.tempDir, { recursive: true, force: true });
900
+ }
901
+ });
902
+ it('a save whose write fails after its tree entry, through the workspace itself, is reported saved with its failure', async () => {
903
+ const h = await startTurn({
904
+ prefix: 'sri-tree-real-',
905
+ responses: [
906
+ [snapCall('call_snap')],
907
+ [saveCall('call_save', { path: 'files/images.png', index: 0, count: 1 })],
908
+ done,
909
+ ],
910
+ workspace: (workspace) => {
911
+ const ws = workspace;
912
+ const intents = [...ws.intents.values()][0];
913
+ intents.update = () => { throw new Error('injected intent exception'); };
914
+ },
915
+ });
916
+ try {
917
+ const result = await waitForToolResult(h.framework, 'call_save');
918
+ const files = saved(result);
919
+ assert.equal(files.length, 1);
920
+ assert.equal(files[0].inChronicle, true);
921
+ assert.match(String(files[0].diskCopyError), /the rest of the write failed: injected intent exception/);
922
+ assert.ok((await fileBytes(h, String(files[0].path)))?.equals(PNG));
923
+ }
924
+ finally {
925
+ await h.framework.stop();
926
+ rmSync(h.tempDir, { recursive: true, force: true });
927
+ }
928
+ });
929
+ for (const secondFails of [false, true]) {
930
+ it(`a tree-only image save ${secondFails ? 'appears in a later failure receipt' : 'succeeds with a disk-copy warning'}`, async () => {
931
+ const h = await startTurn({
932
+ prefix: 'sri-tree-only-',
933
+ responses: [
934
+ [snapCall('call_snap')],
935
+ [saveCall('call_save', { path: 'files/images.png', index: 0, count: 2 })],
936
+ done,
937
+ ],
938
+ workspace: (workspace) => {
939
+ const write = workspace.writeBinary.bind(workspace);
940
+ let writes = 0;
941
+ workspace.writeBinary = async (...args) => {
942
+ if (++writes === 2 && secondFails)
943
+ return { success: false, error: 'tree unavailable', isError: true };
944
+ const result = await write(...args);
945
+ assert.equal(result.success, true);
946
+ return { success: false, data: { inChronicle: true }, error: 'injected disk-copy failure', isError: true };
947
+ };
948
+ },
949
+ });
950
+ try {
951
+ const result = await waitForToolResult(h.framework, 'call_save');
952
+ if (secondFails) {
953
+ assert.equal(result.isError, true);
954
+ assert.match(result.content, /tree unavailable/);
955
+ assert.match(result.content, /already saved: files\/images-0.png/);
956
+ }
957
+ else {
958
+ const files = saved(result);
959
+ assert.equal(files.length, 2);
960
+ assert.equal(files[0].path, 'files/images-0.png');
961
+ assert.equal(files[0].inChronicle, true);
962
+ assert.match(String(files[0].diskCopyError), /injected disk-copy failure/);
963
+ assert.equal(files[0].size, PNG.byteLength);
964
+ assert.equal(files[0].mimeType, 'image/png');
965
+ }
966
+ assert.ok((await fileBytes(h, 'files/images-0.png'))?.equals(PNG));
967
+ }
968
+ finally {
969
+ await h.framework.stop();
970
+ rmSync(h.tempDir, { recursive: true, force: true });
971
+ }
972
+ });
973
+ }
736
974
  //# sourceMappingURL=save-recent-image.test.js.map