@animalabs/agent-framework 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +60 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +92 -12
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +14 -12
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/engine.d.ts +177 -0
  10. package/dist/src/code-execution/engine.d.ts.map +1 -0
  11. package/dist/src/code-execution/engine.js +494 -0
  12. package/dist/src/code-execution/engine.js.map +1 -0
  13. package/dist/src/code-execution/index.d.ts +5 -10
  14. package/dist/src/code-execution/index.d.ts.map +1 -1
  15. package/dist/src/code-execution/index.js +5 -9
  16. package/dist/src/code-execution/index.js.map +1 -1
  17. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  18. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  19. package/dist/src/code-execution/isolate/calls.js +145 -0
  20. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  21. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  22. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  24. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  26. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-main.js +132 -0
  28. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  30. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child.js +315 -0
  32. package/dist/src/code-execution/isolate/child.js.map +1 -0
  33. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  34. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/frames.js +210 -0
  36. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  37. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  38. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/output.js +143 -0
  40. package/dist/src/code-execution/isolate/output.js.map +1 -0
  41. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  42. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/prelude.js +401 -0
  44. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  45. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  46. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/protocol.js +150 -0
  48. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  49. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  50. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/render.js +214 -0
  52. package/dist/src/code-execution/isolate/render.js.map +1 -0
  53. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  54. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/runner.js +854 -0
  56. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  57. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  58. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/scripts.js +357 -0
  60. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  61. package/dist/src/code-execution/quickjs.wasm +0 -0
  62. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  63. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  64. package/dist/src/code-execution/tool-definition.js +176 -142
  65. package/dist/src/code-execution/tool-definition.js.map +1 -1
  66. package/dist/src/deadline-timer.d.ts +3 -0
  67. package/dist/src/deadline-timer.d.ts.map +1 -1
  68. package/dist/src/deadline-timer.js +1 -1
  69. package/dist/src/deadline-timer.js.map +1 -1
  70. package/dist/src/framework.d.ts +1027 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4093 -2238
  73. package/dist/src/framework.js.map +1 -1
  74. package/dist/src/gate/event-gate.d.ts +28 -1
  75. package/dist/src/gate/event-gate.d.ts.map +1 -1
  76. package/dist/src/gate/event-gate.js +88 -10
  77. package/dist/src/gate/event-gate.js.map +1 -1
  78. package/dist/src/held-writes.d.ts +16 -3
  79. package/dist/src/held-writes.d.ts.map +1 -1
  80. package/dist/src/held-writes.js +16 -3
  81. package/dist/src/held-writes.js.map +1 -1
  82. package/dist/src/index.d.ts +6 -6
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +9 -8
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/mcpl/channel-registry.d.ts +471 -75
  87. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  88. package/dist/src/mcpl/channel-registry.js +1673 -337
  89. package/dist/src/mcpl/channel-registry.js.map +1 -1
  90. package/dist/src/mcpl/conversation-router.d.ts +12 -7
  91. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  92. package/dist/src/mcpl/conversation-router.js +12 -7
  93. package/dist/src/mcpl/conversation-router.js.map +1 -1
  94. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  95. package/dist/src/mcpl/convert-block.js +9 -5
  96. package/dist/src/mcpl/convert-block.js.map +1 -1
  97. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  98. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  99. package/dist/src/mcpl/inbound-source.js +130 -0
  100. package/dist/src/mcpl/inbound-source.js.map +1 -1
  101. package/dist/src/mcpl/index.d.ts +2 -1
  102. package/dist/src/mcpl/index.d.ts.map +1 -1
  103. package/dist/src/mcpl/index.js +3 -2
  104. package/dist/src/mcpl/index.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +6 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  107. package/dist/src/mcpl/log-value.js +8 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -1
  109. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  110. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  111. package/dist/src/mcpl/modern-connection.js +168 -53
  112. package/dist/src/mcpl/modern-connection.js.map +1 -1
  113. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  114. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  115. package/dist/src/mcpl/prose-grammar.js +4 -5
  116. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  117. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  118. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  119. package/dist/src/mcpl/protocol-family.js +173 -16
  120. package/dist/src/mcpl/protocol-family.js.map +1 -1
  121. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  122. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  123. package/dist/src/mcpl/push-coalescer.js +227 -50
  124. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  125. package/dist/src/mcpl/push-handler.d.ts +6 -1
  126. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-handler.js +4 -1
  128. package/dist/src/mcpl/push-handler.js.map +1 -1
  129. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  130. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  131. package/dist/src/mcpl/reference-fetcher.js +4 -4
  132. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  133. package/dist/src/mcpl/server-connection.d.ts +50 -21
  134. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  135. package/dist/src/mcpl/server-connection.js +158 -74
  136. package/dist/src/mcpl/server-connection.js.map +1 -1
  137. package/dist/src/mcpl/tags.d.ts +24 -0
  138. package/dist/src/mcpl/tags.d.ts.map +1 -1
  139. package/dist/src/mcpl/tags.js +33 -0
  140. package/dist/src/mcpl/tags.js.map +1 -1
  141. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  142. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  143. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  144. package/dist/src/mcpl/transport.d.ts +55 -7
  145. package/dist/src/mcpl/transport.d.ts.map +1 -1
  146. package/dist/src/mcpl/transport.js +163 -34
  147. package/dist/src/mcpl/transport.js.map +1 -1
  148. package/dist/src/mcpl/types.d.ts +22 -6
  149. package/dist/src/mcpl/types.d.ts.map +1 -1
  150. package/dist/src/mcpl/types.js.map +1 -1
  151. package/dist/src/module-registry.d.ts +4 -2
  152. package/dist/src/module-registry.d.ts.map +1 -1
  153. package/dist/src/module-registry.js +100 -13
  154. package/dist/src/module-registry.js.map +1 -1
  155. package/dist/src/modules/discord/index.d.ts.map +1 -1
  156. package/dist/src/modules/discord/index.js +8 -13
  157. package/dist/src/modules/discord/index.js.map +1 -1
  158. package/dist/src/modules/history/index.d.ts.map +1 -1
  159. package/dist/src/modules/history/index.js +27 -4
  160. package/dist/src/modules/history/index.js.map +1 -1
  161. package/dist/src/modules/history/semantic.d.ts +15 -3
  162. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  163. package/dist/src/modules/history/semantic.js +27 -5
  164. package/dist/src/modules/history/semantic.js.map +1 -1
  165. package/dist/src/modules/index.d.ts +1 -1
  166. package/dist/src/modules/index.d.ts.map +1 -1
  167. package/dist/src/modules/index.js.map +1 -1
  168. package/dist/src/modules/unknown-tool.d.ts +4 -1
  169. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  170. package/dist/src/modules/unknown-tool.js +4 -1
  171. package/dist/src/modules/unknown-tool.js.map +1 -1
  172. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  173. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  174. package/dist/src/modules/workspace/branch-intent.js +3 -1
  175. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  176. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  177. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  178. package/dist/src/modules/workspace/edit-plan.js +794 -0
  179. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  180. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  181. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-text.js +584 -0
  183. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  184. package/dist/src/modules/workspace/effects.d.ts +5 -4
  185. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/effects.js +78 -25
  187. package/dist/src/modules/workspace/effects.js.map +1 -1
  188. package/dist/src/modules/workspace/index.d.ts +484 -61
  189. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/index.js +2173 -645
  191. package/dist/src/modules/workspace/index.js.map +1 -1
  192. package/dist/src/modules/workspace/observe.d.ts +213 -38
  193. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/observe.js +416 -36
  195. package/dist/src/modules/workspace/observe.js.map +1 -1
  196. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/reconcile.js +15 -8
  198. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  199. package/dist/src/modules/workspace/text.d.ts +32 -0
  200. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  201. package/dist/src/modules/workspace/text.js +67 -0
  202. package/dist/src/modules/workspace/text.js.map +1 -0
  203. package/dist/src/modules/workspace/types.d.ts +210 -6
  204. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/types.js.map +1 -1
  206. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  207. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  208. package/dist/src/modules/workspace/watcher.js.map +1 -1
  209. package/dist/src/operator-log.d.ts +52 -24
  210. package/dist/src/operator-log.d.ts.map +1 -1
  211. package/dist/src/operator-log.js.map +1 -1
  212. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  213. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  214. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  215. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  216. package/dist/src/recovery/offline-branch.d.ts +26 -1
  217. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  218. package/dist/src/recovery/offline-branch.js +164 -39
  219. package/dist/src/recovery/offline-branch.js.map +1 -1
  220. package/dist/src/recovery/recover-cli.js +23 -4
  221. package/dist/src/recovery/recover-cli.js.map +1 -1
  222. package/dist/src/send-tools.d.ts +11 -0
  223. package/dist/src/send-tools.d.ts.map +1 -0
  224. package/dist/src/send-tools.js +11 -0
  225. package/dist/src/send-tools.js.map +1 -0
  226. package/dist/src/tool-result-guard.d.ts +4 -3
  227. package/dist/src/tool-result-guard.d.ts.map +1 -1
  228. package/dist/src/tool-result-guard.js +5 -4
  229. package/dist/src/tool-result-guard.js.map +1 -1
  230. package/dist/src/tool-result-history.d.ts +1 -1
  231. package/dist/src/tool-result-history.js +1 -1
  232. package/dist/src/tune-out/coordinator.d.ts +21 -0
  233. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  234. package/dist/src/tune-out/coordinator.js +139 -37
  235. package/dist/src/tune-out/coordinator.js.map +1 -1
  236. package/dist/src/types/agent.d.ts +4 -0
  237. package/dist/src/types/agent.d.ts.map +1 -1
  238. package/dist/src/types/events.d.ts +62 -9
  239. package/dist/src/types/events.d.ts.map +1 -1
  240. package/dist/src/types/framework.d.ts +62 -58
  241. package/dist/src/types/framework.d.ts.map +1 -1
  242. package/dist/src/types/module.d.ts +40 -8
  243. package/dist/src/types/module.d.ts.map +1 -1
  244. package/dist/src/types/trace.d.ts +111 -10
  245. package/dist/src/types/trace.d.ts.map +1 -1
  246. package/dist/src/usage/types.d.ts +16 -0
  247. package/dist/src/usage/types.d.ts.map +1 -1
  248. package/dist/src/usage/usage-tracker.d.ts +14 -1
  249. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  250. package/dist/src/usage/usage-tracker.js +81 -41
  251. package/dist/src/usage/usage-tracker.js.map +1 -1
  252. package/package.json +6 -5
  253. package/src/agent.ts +93 -12
  254. package/src/api/server.ts +14 -12
  255. package/src/code-execution/engine.ts +595 -0
  256. package/src/code-execution/index.ts +5 -17
  257. package/src/code-execution/isolate/calls.ts +199 -0
  258. package/src/code-execution/isolate/child-entry.ts +5 -0
  259. package/src/code-execution/isolate/child-main.ts +132 -0
  260. package/src/code-execution/isolate/child.ts +327 -0
  261. package/src/code-execution/isolate/frames.ts +209 -0
  262. package/src/code-execution/isolate/output.ts +161 -0
  263. package/src/code-execution/isolate/prelude.ts +409 -0
  264. package/src/code-execution/isolate/protocol.ts +240 -0
  265. package/src/code-execution/isolate/render.ts +230 -0
  266. package/src/code-execution/isolate/runner.ts +911 -0
  267. package/src/code-execution/isolate/scripts.ts +479 -0
  268. package/src/code-execution/quickjs.wasm +0 -0
  269. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  270. package/src/code-execution/tool-definition.ts +236 -167
  271. package/src/deadline-timer.ts +1 -1
  272. package/src/framework.ts +4468 -2418
  273. package/src/gate/event-gate.ts +78 -11
  274. package/src/held-writes.ts +17 -3
  275. package/src/index.ts +30 -22
  276. package/src/mcpl/channel-registry.ts +1825 -368
  277. package/src/mcpl/conversation-router.ts +12 -7
  278. package/src/mcpl/convert-block.ts +9 -5
  279. package/src/mcpl/inbound-source.ts +131 -0
  280. package/src/mcpl/index.ts +4 -2
  281. package/src/mcpl/log-value.ts +9 -0
  282. package/src/mcpl/modern-connection.ts +162 -53
  283. package/src/mcpl/prose-grammar.ts +4 -5
  284. package/src/mcpl/protocol-family.ts +199 -21
  285. package/src/mcpl/push-coalescer.ts +306 -62
  286. package/src/mcpl/push-handler.ts +10 -2
  287. package/src/mcpl/reference-fetcher.ts +4 -4
  288. package/src/mcpl/server-connection.ts +180 -79
  289. package/src/mcpl/tags.ts +36 -0
  290. package/src/mcpl/tool-result-normalize.ts +2 -1
  291. package/src/mcpl/transport.ts +162 -33
  292. package/src/mcpl/types.ts +23 -6
  293. package/src/module-registry.ts +92 -13
  294. package/src/modules/discord/index.ts +8 -13
  295. package/src/modules/history/index.ts +25 -3
  296. package/src/modules/history/semantic.ts +29 -6
  297. package/src/modules/index.ts +1 -0
  298. package/src/modules/unknown-tool.ts +4 -1
  299. package/src/modules/workspace/branch-intent.ts +3 -1
  300. package/src/modules/workspace/edit-plan.ts +954 -0
  301. package/src/modules/workspace/edit-text.ts +618 -0
  302. package/src/modules/workspace/effects.ts +70 -19
  303. package/src/modules/workspace/index.ts +2247 -642
  304. package/src/modules/workspace/observe.ts +465 -56
  305. package/src/modules/workspace/reconcile.ts +14 -7
  306. package/src/modules/workspace/text.ts +68 -0
  307. package/src/modules/workspace/types.ts +211 -6
  308. package/src/modules/workspace/watcher.ts +3 -2
  309. package/src/operator-log.ts +47 -19
  310. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  311. package/src/recovery/offline-branch.ts +189 -55
  312. package/src/recovery/recover-cli.ts +25 -4
  313. package/src/send-tools.ts +11 -0
  314. package/src/tool-result-guard.ts +6 -5
  315. package/src/tool-result-history.ts +1 -1
  316. package/src/tune-out/coordinator.ts +163 -42
  317. package/src/types/agent.ts +4 -0
  318. package/src/types/events.ts +62 -9
  319. package/src/types/framework.ts +62 -58
  320. package/src/types/module.ts +40 -8
  321. package/src/types/trace.ts +121 -15
  322. package/src/usage/types.ts +16 -0
  323. package/src/usage/usage-tracker.ts +86 -43
  324. package/dist/src/code-execution/py-runner.d.ts +0 -191
  325. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  326. package/dist/src/code-execution/py-runner.js +0 -655
  327. package/dist/src/code-execution/py-runner.js.map +0 -1
  328. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  329. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  330. package/dist/src/code-execution/runtime-py.js +0 -677
  331. package/dist/src/code-execution/runtime-py.js.map +0 -1
  332. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  333. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  334. package/dist/src/mcpl/prose-stream-router.js +0 -291
  335. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  336. package/src/code-execution/py-runner.ts +0 -791
  337. package/src/code-execution/runtime-py.ts +0 -677
  338. package/src/mcpl/prose-stream-router.ts +0 -268
@@ -1,13 +1,19 @@
1
1
  import { JsStore } from '@animalabs/chronicle';
2
+ import type { SurgeryMarkerReceipt } from '../operator-log.js';
2
3
  import { ContextManager, continuesBody, type BodyShard } from '@animalabs/context-manager';
3
4
  import { removeStoredInterval } from '../stored-removal.js';
4
5
  import { firstFreeBranchName } from '../branch-names.js';
5
6
  import {
6
7
  DEFAULT_DISCORD_AWARENESS_EMOJI,
7
8
  DiscordAwarenessOutbox,
9
+ boundDiscordAwarenessText,
8
10
  defaultDiscordAwarenessOutboxPath,
11
+ checkedDiscordAwarenessMarks,
9
12
  extractDiscordAwarenessRefs,
13
+ selectDiscordAwarenessRefs,
14
+ type DiscordAwarenessMarks,
10
15
  type DiscordAwarenessRef,
16
+ type DiscordMessageMetadataCarrier,
11
17
  } from './discord-awareness-outbox.js';
12
18
 
13
19
  export interface OfflineRecoveryBranchOptions {
@@ -29,8 +35,14 @@ export interface OfflineRecoveryBranchOptions {
29
35
  namespace?: string;
30
36
  /** Use for old message records that do not carry metadata.serverId. */
31
37
  discordServerId?: string;
38
+ /** The awareness ledger (default: under the store). */
32
39
  outboxPath?: string;
33
40
  emoji?: string;
41
+ /**
42
+ * Awareness marks on the removed Discord messages: an explicit publication
43
+ * choice, `none` by default. The recovery itself stays local either way.
44
+ */
45
+ marks?: DiscordAwarenessMarks;
34
46
  dryRun?: boolean;
35
47
  }
36
48
 
@@ -40,9 +52,27 @@ export interface OfflineRecoveryBranchResult {
40
52
  targetBranch: string;
41
53
  messagesRemoved: number;
42
54
  messagesSuppressed: number;
55
+ /** Removed messages that carry a Discord address. */
56
+ discordAddressable: number;
57
+ /** The publication scope recorded for this recovery. */
58
+ marksScope: 'none' | 'addressed' | 'all';
59
+ /** Marks queued for delivery (planned, for a dry run); not delivered. */
43
60
  discordMarkersQueued: number;
61
+ /** What became of the marks once the branch was made (absent on a dry
62
+ * run): scheduling only, never Discord acceptance. */
63
+ markers?: SurgeryMarkerReceipt;
64
+ /** The refs that will be marked (empty unless marks were chosen). */
44
65
  refs: DiscordAwarenessRef[];
66
+ /** Removed addressable messages the marks choice leaves unmarked (outside
67
+ * its scope); `markers` says whether the chosen ones are scheduled. */
68
+ unmarked: number;
69
+ /** Authorized refs (marks.refs) this recovery does not remove. */
70
+ notRemoved: number;
71
+ /** The awareness ledger the batch is recorded in. */
45
72
  outboxPath: string;
73
+ /** Set when a suppression-only batch's resume record couldn't be closed:
74
+ * what of it is left, as the host's surgeries report it. */
75
+ suppressionRecord?: string;
46
76
  }
47
77
 
48
78
  /**
@@ -65,6 +95,10 @@ export async function createOfflineRecoveryBranch(
65
95
  if (count !== undefined && (!Number.isFinite(count) || count < 1)) {
66
96
  throw new Error('messages must be a positive integer');
67
97
  }
98
+ // The marks choice is checked as the live methods check it, before the
99
+ // store is opened: a malformed one would otherwise mark every removed
100
+ // message.
101
+ const marks = checkedDiscordAwarenessMarks(options.marks);
68
102
 
69
103
  const store = JsStore.openOrCreate({ path: options.storePath });
70
104
  let contextManager: ContextManager | null = null;
@@ -84,7 +118,7 @@ export async function createOfflineRecoveryBranch(
84
118
  let target: StoredWindowMessage;
85
119
  let targetIndex: number;
86
120
  let removedCount: number;
87
- let refs: DiscordAwarenessRef[];
121
+ let carriers: DiscordMessageMetadataCarrier[];
88
122
 
89
123
  if (options.messageId) {
90
124
  // Search backward in bounded windows: recovery anchors are normally near
@@ -101,12 +135,7 @@ export async function createOfflineRecoveryBranch(
101
135
  target = located.message;
102
136
  targetIndex = located.index;
103
137
  removedCount = total - located.index - 1;
104
- refs = collectDiscordRefs(
105
- contextManager,
106
- located.index + 1,
107
- total,
108
- options.discordServerId,
109
- );
138
+ carriers = collectDiscordCarriers(contextManager, located.index + 1, total);
110
139
  } else if (options.contextId) {
111
140
  const located = findInternalMessageFromTail(contextManager, total, options.contextId.trim());
112
141
  if (!located) {
@@ -115,12 +144,7 @@ export async function createOfflineRecoveryBranch(
115
144
  target = located.message;
116
145
  targetIndex = located.index;
117
146
  removedCount = total - located.index - 1;
118
- refs = collectDiscordRefs(
119
- contextManager,
120
- located.index + 1,
121
- total,
122
- options.discordServerId,
123
- );
147
+ carriers = collectDiscordCarriers(contextManager, located.index + 1, total);
124
148
  } else {
125
149
  // Count mode remains for compatibility. Read only the target plus suffix,
126
150
  // avoiding full-history materialization and attachment blob inflation.
@@ -135,7 +159,7 @@ export async function createOfflineRecoveryBranch(
135
159
  target = countTarget;
136
160
  targetIndex = total - count! - 1;
137
161
  removedCount = count!;
138
- refs = extractDiscordAwarenessRefs(discarded, options.discordServerId);
162
+ carriers = discarded.map(addressingOnly);
139
163
  }
140
164
 
141
165
  validateAnchorBoundary(contextManager, targetIndex, total, target);
@@ -145,7 +169,6 @@ export async function createOfflineRecoveryBranch(
145
169
  targetIndex,
146
170
  options.suppressMessageIds ?? [],
147
171
  options.suppressRanges ?? [],
148
- options.discordServerId,
149
172
  );
150
173
  validateRemovalIntegrity(
151
174
  contextManager,
@@ -160,7 +183,10 @@ export async function createOfflineRecoveryBranch(
160
183
  ...suppression.intervals,
161
184
  ],
162
185
  );
163
- refs = dedupeDiscordRefs([...refs, ...suppression.refs]);
186
+ carriers = [...carriers, ...suppression.carriers];
187
+ const addressable = extractDiscordAwarenessRefs(carriers, options.discordServerId).length;
188
+ const selection = selectDiscordAwarenessRefs(carriers, marks, options.discordServerId);
189
+ const refs = selection.refs;
164
190
 
165
191
  const sourceBranch = store.currentBranch().name;
166
192
  const held = new Set(store.listBranches().map((branch) => branch.name));
@@ -169,6 +195,10 @@ export async function createOfflineRecoveryBranch(
169
195
  if (targetBranch === sourceBranch) {
170
196
  throw new Error('Recovery branch name must differ from the active source branch');
171
197
  }
198
+ // A known collision is refused before anything is prepared.
199
+ if (store.listBranches().some((branch) => branch.name === targetBranch)) {
200
+ throw new Error(`Recovery branch ${targetBranch} already exists; choose another --branch`);
201
+ }
172
202
  const outboxPath = options.outboxPath
173
203
  ?? defaultDiscordAwarenessOutboxPath(options.storePath);
174
204
 
@@ -178,33 +208,44 @@ export async function createOfflineRecoveryBranch(
178
208
  targetBranch,
179
209
  messagesRemoved: removedCount,
180
210
  messagesSuppressed: suppression.messageCount,
211
+ discordAddressable: addressable,
212
+ marksScope: marks === 'none' ? 'none' : marks.scope,
181
213
  discordMarkersQueued: refs.length,
182
214
  refs,
215
+ unmarked: selection.unmarked,
216
+ notRemoved: selection.notRemoved,
183
217
  outboxPath,
184
218
  };
185
219
  if (options.dryRun) return result;
186
220
 
187
- const outbox = new DiscordAwarenessOutbox(outboxPath);
188
- const batch = outbox.prepare({
221
+ // The host is stopped, so this process is the ledger's only writer.
222
+ const outbox = new DiscordAwarenessOutbox(outboxPath, { store });
223
+ const batch = await outbox.prepare({
189
224
  agentName: options.agentName,
190
225
  sourceBranch,
191
226
  targetBranch,
192
227
  refs,
228
+ scope: marks === 'none' ? 'none' : marks.scope,
229
+ unmarked: selection.unmarked,
230
+ notRemoved: selection.notRemoved,
193
231
  emoji: options.emoji ?? DEFAULT_DISCORD_AWARENESS_EMOJI,
194
232
  // Merely seeing targetBranch active does not prove branch-local
195
- // suppressions finished. Only the recovery operation may activate this
196
- // batch after every removal below succeeds.
197
- activationPolicy: suppression.intervals.length > 0 ? 'explicit' : 'target-branch',
233
+ // suppressions finished: a start on it applies them again while the
234
+ // batch is prepared. Only the recovery may activate it, after every
235
+ // removal below succeeds.
198
236
  suppressionIntervals: suppression.intervals.map((interval) => ({
199
237
  fromId: String(interval.fromId),
200
238
  toId: String(interval.toId),
201
239
  })),
202
240
  });
203
241
 
204
- // branchAt uses the target message's origin sequence. No message content is
205
- // compiled or submitted to Membrane during this operation.
206
- const createdBranch = contextManager.branchAt(target.id, targetBranch);
242
+ let createdBranch: string | undefined;
207
243
  try {
244
+ // branchAt uses the target message's origin sequence. No message
245
+ // content is compiled or submitted to Membrane during this operation.
246
+ // Creating the branch is part of the body: a failure here settles the
247
+ // batch like a failed switch or redaction.
248
+ createdBranch = contextManager.branchAt(target.id, targetBranch);
208
249
  await contextManager.switchBranch(createdBranch);
209
250
  // Each interval is removed by its messages' ids, so the order doesn't
210
251
  // change the result. removeStoredInterval takes a single message outside
@@ -213,18 +254,109 @@ export async function createOfflineRecoveryBranch(
213
254
  for (const interval of [...suppression.intervals].reverse()) {
214
255
  removeStoredInterval(contextManager, interval.fromId, interval.toId);
215
256
  }
216
- if (batch) outbox.activate(batch.id);
217
257
  } catch (error) {
218
258
  // A partially suppressed branch is not safe to boot into. Preserve it
219
- // for diagnosis, but leave Chronicle on the untouched source branch and
220
- // keep the explicit outbox batch non-deliverable.
221
- if (store.currentBranch().name === createdBranch) {
222
- await contextManager.switchBranch(sourceBranch);
259
+ // for diagnosis, but put Chronicle back on the untouched source branch.
260
+ // Once the source is confirmed, the batch is retired (discard syncs the
261
+ // restored state before it records that): its surgery never happened.
262
+ // If it can't be confirmed, the batch stays prepared as the body's
263
+ // record: a start on its target branch finishes a suppression's
264
+ // removals, since its body is in effect there, and a start retires the
265
+ // batch, none of its marks sent. The error says which, and names the
266
+ // branch.
267
+ const failure = error instanceof Error ? error.message : String(error);
268
+ let restore = '';
269
+ try {
270
+ if (store.currentBranch().name !== sourceBranch) await contextManager.switchBranch(sourceBranch);
271
+ } catch (restoreError) {
272
+ restore = `; restoring ${sourceBranch} failed: ${boundDiscordAwarenessText(
273
+ restoreError instanceof Error ? restoreError.message : String(restoreError),
274
+ )}`;
275
+ }
276
+ const sourceConfirmed = store.currentBranch().name === sourceBranch;
277
+ let retire = '';
278
+ // A batch with marks never sends them once kept: the recovery, run
279
+ // again, chooses them again.
280
+ const redo = batch && batch.refs.length > 0
281
+ ? '. To apply its marks, run the recovery again onto a new --branch, choosing them again' : '';
282
+ if (batch) {
283
+ if (!sourceConfirmed) {
284
+ // The recovery starts from the store's current branch, which this
285
+ // one couldn't put back: a run from there would start from the fork.
286
+ const redoFromSource = batch.refs.length > 0
287
+ ? `. To apply its marks, redo it from ${sourceBranch}, choosing them again: the recovery starts from the ` +
288
+ `store's current branch, which isn't ${sourceBranch} now, so start the host, switch to ${sourceBranch} ` +
289
+ 'through the host API\'s branch.switch, stop the host, and run the recovery again onto a new --branch'
290
+ : '';
291
+ retire = `; awareness batch ${batch.id} kept, since the source branch could not be confirmed: ` +
292
+ (suppression.intervals.length > 0
293
+ ? `a start on ${targetBranch} finishes its removals and retires it`
294
+ : 'the next start retires it') + `, none of its marks sent${redoFromSource}`;
295
+ } else {
296
+ try {
297
+ await outbox.discard(batch.id);
298
+ retire = `; awareness batch ${batch.id} retired, none of its marks sent${redo}`;
299
+ } catch (discardError) {
300
+ // None of its marks is ever sent: a start retires it. A write that
301
+ // fails leaves the batch as it was, prepared, as in the host's
302
+ // retireAfterRestore. A suppression's removals stay pending for
303
+ // its target branch: a start on a branch with that name finishes
304
+ // them.
305
+ const detail = boundDiscordAwarenessText(
306
+ discardError instanceof Error ? discardError.message : String(discardError),
307
+ );
308
+ const kept = `; awareness batch ${batch.id} could not be retired (${detail}); it stays prepared, none of ` +
309
+ 'its marks to be sent';
310
+ if (suppression.intervals.length === 0) {
311
+ retire = `${kept}, until the next start retires it${redo}`;
312
+ } else {
313
+ let forked = true;
314
+ try {
315
+ forked = store.listBranches().some((branch) => branch.name === targetBranch);
316
+ } catch { /* unknown: say what a start on an existing branch would do */ }
317
+ retire = `${kept}. Its removals stay pending for ${targetBranch}: a start on a branch with that ` +
318
+ 'name would finish them there and retire the batch' + (forked
319
+ ? `, and ${targetBranch} is kept, so delete it unless you want that, through the host API's ` +
320
+ 'branch.delete once the host runs'
321
+ : '') + redo;
322
+ }
323
+ }
324
+ }
223
325
  }
224
- throw error;
326
+ // A partly suppressed branch is unsafe to start on, unless its batch was
327
+ // kept because the source couldn't be confirmed: a start on it then
328
+ // completes the suppression, as the batch's note says.
329
+ const kept = createdBranch === undefined ? ''
330
+ : suppression.intervals.length === 0 ? `; branch ${createdBranch} is kept for diagnosis`
331
+ : batch && !sourceConfirmed ? `; branch ${createdBranch} is kept: it may be partly suppressed`
332
+ : `; branch ${createdBranch} is kept for diagnosis: it may be partly suppressed, so don't start on it, and ` +
333
+ 'delete it once it has been looked at (the host API\'s branch.delete, once the host runs)';
334
+ if (!restore && !retire && !kept) throw error;
335
+ throw new Error(`${failure}${restore}${kept}${retire}`, { cause: error });
225
336
  }
226
337
 
227
- return result;
338
+ // The body change landed. Marker bookkeeping is reported apart from it
339
+ // and never undoes it.
340
+ const facts = {
341
+ scope: (marks === 'none' ? 'none' : marks.scope) as SurgeryMarkerReceipt['scope'],
342
+ unmarked: selection.unmarked,
343
+ notRemoved: selection.notRemoved,
344
+ };
345
+ let markers: SurgeryMarkerReceipt = { ...facts, status: 'none', queued: 0 };
346
+ let suppressionRecord: string | undefined;
347
+ if (batch) {
348
+ const settled = await outbox.settleActivation(batch.id);
349
+ if (batch.refs.length > 0) {
350
+ markers = settled.status === 'queued'
351
+ ? { ...facts, status: 'queued', queued: settled.queued, batchId: batch.id }
352
+ : { ...facts, status: 'not-scheduled', queued: 0, error: settled.error };
353
+ } else if (settled.status === 'not-scheduled') {
354
+ // A suppression-only batch carries no marks, and its activation
355
+ // closes the body's resume record: if that failed, say what became of it.
356
+ suppressionRecord = `the resume record (awareness batch ${batch.id}, no marks) could not be closed: ${settled.error}`;
357
+ }
358
+ }
359
+ return { ...result, discordMarkersQueued: markers.queued, markers, ...(suppressionRecord ? { suppressionRecord } : {}) };
228
360
  } finally {
229
361
  contextManager?.close();
230
362
  store.close();
@@ -372,8 +504,7 @@ function buildSuppressionPlan(
372
504
  targetIndex: number,
373
505
  rawMessageIds: string[],
374
506
  rawRanges: Array<{ fromMessageId: string; toMessageId: string }>,
375
- forcedServerId?: string,
376
- ): { intervals: SuppressionInterval[]; refs: DiscordAwarenessRef[]; messageCount: number } {
507
+ ): { intervals: SuppressionInterval[]; carriers: DiscordMessageMetadataCarrier[]; messageCount: number } {
377
508
  const messageIds = rawMessageIds.map(normalizeDiscordMessageId);
378
509
  const ranges = rawRanges.map((range) => ({
379
510
  fromMessageId: normalizeDiscordMessageId(range.fromMessageId),
@@ -383,7 +514,7 @@ function buildSuppressionPlan(
383
514
  ...messageIds,
384
515
  ...ranges.flatMap((range) => [range.fromMessageId, range.toMessageId]),
385
516
  ]);
386
- if (wanted.size === 0) return { intervals: [], refs: [], messageCount: 0 };
517
+ if (wanted.size === 0) return { intervals: [], carriers: [], messageCount: 0 };
387
518
 
388
519
  const locations = new Map<string, MessageLocation>();
389
520
  for (let offset = 0; offset <= targetIndex; offset += RECOVERY_SCAN_WINDOW) {
@@ -473,45 +604,48 @@ function buildSuppressionPlan(
473
604
  }
474
605
  }
475
606
 
476
- const refs: DiscordAwarenessRef[] = [];
607
+ const carriers: DiscordMessageMetadataCarrier[] = [];
477
608
  for (const interval of merged) {
478
- refs.push(...collectDiscordRefs(
479
- contextManager,
480
- interval.start,
481
- interval.end + 1,
482
- forcedServerId,
483
- ));
609
+ carriers.push(...collectDiscordCarriers(contextManager, interval.start, interval.end + 1));
484
610
  }
485
611
  return {
486
612
  intervals: merged,
487
- refs: dedupeDiscordRefs(refs),
613
+ carriers,
488
614
  messageCount: merged.reduce((sum, interval) => sum + interval.end - interval.start + 1, 0),
489
615
  };
490
616
  }
491
617
 
492
- function collectDiscordRefs(
618
+ /**
619
+ * The addressing metadata of removed messages, read in bounded windows: only
620
+ * the Discord address and the MCPL tags (for `addressed` selection) are kept,
621
+ * never content. A sharded message may repeat its metadata across adjacent
622
+ * records; selection dedupes by address.
623
+ */
624
+ function collectDiscordCarriers(
493
625
  contextManager: ContextManager,
494
626
  start: number,
495
627
  end: number,
496
- forcedServerId?: string,
497
- ): DiscordAwarenessRef[] {
498
- const refs: DiscordAwarenessRef[] = [];
628
+ ): DiscordMessageMetadataCarrier[] {
629
+ const carriers: DiscordMessageMetadataCarrier[] = [];
499
630
  for (let offset = start; offset < end; offset += RECOVERY_SCAN_WINDOW) {
500
631
  const messages = contextManager.getMessageWindow(
501
632
  offset,
502
633
  Math.min(RECOVERY_SCAN_WINDOW, end - offset),
503
634
  { resolveBlobs: false },
504
635
  ).messages;
505
- refs.push(...extractDiscordAwarenessRefs(messages, forcedServerId));
636
+ carriers.push(...messages.map(addressingOnly));
506
637
  }
507
- // A sharded message may repeat Discord metadata across adjacent records.
508
- return dedupeDiscordRefs(refs);
638
+ return carriers;
509
639
  }
510
640
 
511
- function dedupeDiscordRefs(refs: DiscordAwarenessRef[]): DiscordAwarenessRef[] {
512
- const deduped = new Map<string, DiscordAwarenessRef>();
513
- for (const ref of refs) {
514
- deduped.set(`${ref.serverId}\0${ref.channelId}\0${ref.messageId}`, ref);
515
- }
516
- return [...deduped.values()];
641
+ function addressingOnly(message: { metadata?: Record<string, unknown> }): DiscordMessageMetadataCarrier {
642
+ const metadata = message.metadata ?? {};
643
+ return {
644
+ metadata: {
645
+ serverId: metadata.serverId,
646
+ channelId: metadata.channelId,
647
+ messageId: metadata.messageId,
648
+ tags: metadata.tags,
649
+ },
650
+ };
517
651
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createOfflineRecoveryBranch } from './offline-branch.js';
4
+ import type { DiscordAwarenessMarks } from './discord-awareness-outbox.js';
4
5
 
5
6
  interface CliOptions {
6
7
  storePath?: string;
@@ -15,6 +16,7 @@ interface CliOptions {
15
16
  discordServerId?: string;
16
17
  outboxPath?: string;
17
18
  emoji?: string;
19
+ marks: 'none' | 'addressed' | 'all';
18
20
  dryRun: boolean;
19
21
  }
20
22
 
@@ -22,9 +24,12 @@ function usage(): string {
22
24
  return `Usage:
23
25
  agent-framework-recover --store <path> --agent <name> <anchor> [options]
24
26
 
25
- Creates and activates a Chronicle recovery branch while the normal host is down,
26
- then queues Discord reactions for addressable messages removed from awareness.
27
- Message content is never compiled or written to the recovery outbox.
27
+ Creates and activates a Chronicle recovery branch while the normal host is
28
+ down. The recovery is local to the resident: by default no Discord reaction is
29
+ placed. --marks chooses to mark removed Discord messages with an awareness
30
+ reaction (queued here, delivered when the host and its Discord MCPL server
31
+ next connect). Message content is never compiled or written to the awareness
32
+ ledger.
28
33
 
29
34
  Options:
30
35
  --message-id <id|link> Last Discord message the agent should still see
@@ -35,6 +40,10 @@ Options:
35
40
  --branch <name> Recovery branch name (generated by default)
36
41
  --namespace <namespace> Context namespace (default: agents/<agent>)
37
42
  --discord-server <id> Override/inject the Discord MCPL server id
43
+ --marks <none|addressed|all>
44
+ Awareness marks on removed Discord messages
45
+ (default none; addressed = mentions, replies to
46
+ the bot and DMs; all = every addressable message)
38
47
  --outbox <path> Override the branch-independent outbox path
39
48
  --emoji <emoji> Marker reaction (default: 💤)
40
49
  --dry-run Inspect counts/addresses without writing
@@ -44,7 +53,7 @@ The agent host must be stopped before running this command.`;
44
53
  }
45
54
 
46
55
  function parseArgs(args: string[]): CliOptions {
47
- const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [] };
56
+ const options: CliOptions = { dryRun: false, suppressMessageIds: [], suppressRanges: [], marks: 'none' };
48
57
  for (let i = 0; i < args.length; i++) {
49
58
  const arg = args[i];
50
59
  const value = () => {
@@ -80,6 +89,14 @@ function parseArgs(args: string[]): CliOptions {
80
89
  case '--discord-server': options.discordServerId = value(); break;
81
90
  case '--outbox': options.outboxPath = value(); break;
82
91
  case '--emoji': options.emoji = value(); break;
92
+ case '--marks': {
93
+ const marks = value();
94
+ if (marks !== 'none' && marks !== 'addressed' && marks !== 'all') {
95
+ throw new Error('--marks must be none, addressed or all');
96
+ }
97
+ options.marks = marks;
98
+ break;
99
+ }
83
100
  case '--dry-run': options.dryRun = true; break;
84
101
  case '--help':
85
102
  case '-h':
@@ -101,6 +118,9 @@ async function main(): Promise<void> {
101
118
  throw new Error(`--store, --agent, and one anchor (--message-id, --context-id, or --messages) are required\n\n${usage()}`);
102
119
  }
103
120
 
121
+ // A CLI choice and its application are the same moment (the host is
122
+ // stopped), so the scope is evaluated now rather than frozen to refs.
123
+ const marks: DiscordAwarenessMarks = options.marks === 'none' ? 'none' : { scope: options.marks };
104
124
  const result = await createOfflineRecoveryBranch({
105
125
  storePath: options.storePath,
106
126
  agentName: options.agentName,
@@ -114,6 +134,7 @@ async function main(): Promise<void> {
114
134
  discordServerId: options.discordServerId,
115
135
  outboxPath: options.outboxPath,
116
136
  emoji: options.emoji,
137
+ marks,
117
138
  dryRun: options.dryRun,
118
139
  });
119
140
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tools whose call delivers the resident's message to a channel or person,
3
+ * by bare name (an MCPL server's `<prefix>--` stripped). The framework reads
4
+ * a call of one as an explicit send that silences the round's auto-routed
5
+ * prose; the history module's semantic index reads its message argument as
6
+ * what the resident wrote to someone, whether or not it was delivered.
7
+ */
8
+ export const SEND_TOOLS = new Set(['channel_publish', 'send_message', 'reply_message', 'send_dm']);
9
+
10
+ /** Strip the `server--` MCPL prefix from a tool name. */
11
+ export const bareToolName = (n: string): string => n.split('--').pop()!;
@@ -18,7 +18,7 @@ export const TOOL_RESULT_GUARD_AUDIT_STATE = 'framework/tool-result-guard';
18
18
  * left the stub's branch when the file would have been committed
19
19
  * (`branch_changed`), or because the batch used its deadline before this
20
20
  * write could start (`batch_deadline`); or null when no file is written at all (no writable
21
- * workspace, or the host is stopping). */
21
+ * mount without an include, or the host is stopping). */
22
22
  export type WithheldSpill =
23
23
  | { path: string; error?: string; unfinished?: true }
24
24
  | { skipped: 'workspace_unanswered' | 'branch_changed' | 'batch_deadline' }
@@ -497,8 +497,9 @@ export class ToolResultGuard {
497
497
 
498
498
  /**
499
499
  * Turn a withheld batch's notices into stubs that say where each original
500
- * is (agent-framework #277): written to the workspace when a writable mount
501
- * takes it, else kept in the audit record. A stub also keeps the file
500
+ * is (agent-framework #277): written to the workspace when it has a
501
+ * writable mount without an include to take it, else kept in the audit
502
+ * record. A stub also keeps the file
502
503
  * lists its tool marked. Its own unedited placeholder is
503
504
  * the only thing it edits: an operator's later edit, removal or rollback
504
505
  * stands.
@@ -596,8 +597,8 @@ export class ToolResultGuard {
596
597
  if (block.type !== 'tool_result') return [];
597
598
  if (thrown.has(index)) return [{ toolUseId: block.toolUseId, error: thrown.get(index)! }];
598
599
  const spill = spills.get(index) ?? null;
599
- // No file was written: no writable workspace, or the framework
600
- // was stopping.
600
+ // No file was written: no writable mount without an include,
601
+ // or the framework was stopping.
601
602
  if (spill === null) return [{ toolUseId: block.toolUseId, skipped: 'no_workspace_file' }];
602
603
  if ('skipped' in spill) return [{ toolUseId: block.toolUseId, skipped: spill.skipped }];
603
604
  return [{ toolUseId: block.toolUseId, path: spill.path,
@@ -49,7 +49,7 @@ export interface HistorySerializeOptions {
49
49
 
50
50
  /**
51
51
  * House-safe default inline cap (chars) for tool results, error results, and
52
- * background-script wake payloads (issue #89: a resident should never eat a
52
+ * script wake payloads (issue #89: a resident should never eat a
53
53
  * 42k blob they didn't ask for). Durable per-residence value comes from
54
54
  * `FrameworkConfig.toolResultInlineMaxChars`; a per-agent value from
55
55
  * agent_settings `tool_result_inline_max_chars`.