@animalabs/agent-framework 0.22.0 → 0.22.2

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 (418) hide show
  1. package/README.md +12 -5
  2. package/dist/src/agent.d.ts +192 -19
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +407 -66
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.d.ts +41 -0
  7. package/dist/src/api/mcp-server-core.d.ts.map +1 -0
  8. package/dist/src/api/mcp-server-core.js +593 -0
  9. package/dist/src/api/mcp-server-core.js.map +1 -0
  10. package/dist/src/api/mcp-server.d.ts +2 -36
  11. package/dist/src/api/mcp-server.d.ts.map +1 -1
  12. package/dist/src/api/mcp-server.js +15 -595
  13. package/dist/src/api/mcp-server.js.map +1 -1
  14. package/dist/src/api/server.d.ts +9 -1
  15. package/dist/src/api/server.d.ts.map +1 -1
  16. package/dist/src/api/server.js +84 -23
  17. package/dist/src/api/server.js.map +1 -1
  18. package/dist/src/api/types.d.ts +9 -1
  19. package/dist/src/api/types.d.ts.map +1 -1
  20. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  21. package/dist/src/code-execution/py-runner.js.map +1 -1
  22. package/dist/src/framework.d.ts +682 -103
  23. package/dist/src/framework.d.ts.map +1 -1
  24. package/dist/src/framework.js +3202 -718
  25. package/dist/src/framework.js.map +1 -1
  26. package/dist/src/gate/event-gate.d.ts.map +1 -1
  27. package/dist/src/gate/gate-script.d.ts.map +1 -1
  28. package/dist/src/gate/gate-script.js.map +1 -1
  29. package/dist/src/index.d.ts +4 -4
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +2 -2
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  34. package/dist/src/mcpl/channel-registry.d.ts +26 -7
  35. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  36. package/dist/src/mcpl/channel-registry.js +102 -22
  37. package/dist/src/mcpl/channel-registry.js.map +1 -1
  38. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  39. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  40. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  41. package/dist/src/mcpl/errors.js.map +1 -1
  42. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  43. package/dist/src/mcpl/feature-set-manager.js +4 -3
  44. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  45. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  46. package/dist/src/mcpl/index.d.ts +2 -0
  47. package/dist/src/mcpl/index.d.ts.map +1 -1
  48. package/dist/src/mcpl/index.js.map +1 -1
  49. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  50. package/dist/src/mcpl/log-value.d.ts +54 -0
  51. package/dist/src/mcpl/log-value.d.ts.map +1 -0
  52. package/dist/src/mcpl/log-value.js +109 -0
  53. package/dist/src/mcpl/log-value.js.map +1 -0
  54. package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
  55. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  56. package/dist/src/mcpl/prose-stream-router.js +4 -4
  57. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  58. package/dist/src/mcpl/push-coalescer.d.ts +38 -3
  59. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  60. package/dist/src/mcpl/push-coalescer.js +81 -16
  61. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  62. package/dist/src/mcpl/push-handler.d.ts +26 -3
  63. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  64. package/dist/src/mcpl/push-handler.js +87 -23
  65. package/dist/src/mcpl/push-handler.js.map +1 -1
  66. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  67. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  68. package/dist/src/mcpl/reference-fetcher.js +20 -9
  69. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  70. package/dist/src/mcpl/references.d.ts +29 -6
  71. package/dist/src/mcpl/references.d.ts.map +1 -1
  72. package/dist/src/mcpl/references.js +57 -16
  73. package/dist/src/mcpl/references.js.map +1 -1
  74. package/dist/src/mcpl/refusal-lines.d.ts +37 -0
  75. package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
  76. package/dist/src/mcpl/refusal-lines.js +61 -0
  77. package/dist/src/mcpl/refusal-lines.js.map +1 -0
  78. package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
  79. package/dist/src/mcpl/server-connection.d.ts +46 -3
  80. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  81. package/dist/src/mcpl/server-connection.js +98 -15
  82. package/dist/src/mcpl/server-connection.js.map +1 -1
  83. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  84. package/dist/src/mcpl/tool-classes.d.ts +1 -1
  85. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  86. package/dist/src/mcpl/tool-classes.js +3 -2
  87. package/dist/src/mcpl/tool-classes.js.map +1 -1
  88. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  89. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  90. package/dist/src/mcpl/transport.d.ts +23 -5
  91. package/dist/src/mcpl/transport.d.ts.map +1 -1
  92. package/dist/src/mcpl/transport.js +103 -10
  93. package/dist/src/mcpl/transport.js.map +1 -1
  94. package/dist/src/mcpl/types.d.ts +30 -26
  95. package/dist/src/mcpl/types.d.ts.map +1 -1
  96. package/dist/src/mcpl/types.js.map +1 -1
  97. package/dist/src/module-registry.d.ts +9 -3
  98. package/dist/src/module-registry.d.ts.map +1 -1
  99. package/dist/src/module-registry.js +3 -2
  100. package/dist/src/module-registry.js.map +1 -1
  101. package/dist/src/modules/api/index.d.ts.map +1 -1
  102. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  103. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  104. package/dist/src/modules/discord/index.d.ts.map +1 -1
  105. package/dist/src/modules/discord/index.js +4 -2
  106. package/dist/src/modules/discord/index.js.map +1 -1
  107. package/dist/src/modules/health/index.d.ts.map +1 -1
  108. package/dist/src/modules/health/index.js.map +1 -1
  109. package/dist/src/modules/history/index.d.ts.map +1 -1
  110. package/dist/src/modules/history/index.js.map +1 -1
  111. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  112. package/dist/src/modules/history/semantic.js.map +1 -1
  113. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  114. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  115. package/dist/src/modules/workspace/disk-agreement.d.ts +22 -4
  116. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/disk-agreement.js +63 -25
  118. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  119. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  120. package/dist/src/modules/workspace/index.d.ts +8 -4
  121. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  122. package/dist/src/modules/workspace/index.js +85 -16
  123. package/dist/src/modules/workspace/index.js.map +1 -1
  124. package/dist/src/modules/workspace/observe.d.ts +56 -11
  125. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  126. package/dist/src/modules/workspace/observe.js +288 -43
  127. package/dist/src/modules/workspace/observe.js.map +1 -1
  128. package/dist/src/modules/workspace/reconcile.d.ts +31 -1
  129. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  130. package/dist/src/modules/workspace/reconcile.js +63 -12
  131. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  132. package/dist/src/modules/workspace/types.d.ts +27 -2
  133. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  134. package/dist/src/modules/workspace/types.js.map +1 -1
  135. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  136. package/dist/src/modules/workspace/watcher.js.map +1 -1
  137. package/dist/src/operator-log.d.ts +41 -0
  138. package/dist/src/operator-log.d.ts.map +1 -1
  139. package/dist/src/operator-log.js.map +1 -1
  140. package/dist/src/provider-waits.d.ts +216 -0
  141. package/dist/src/provider-waits.d.ts.map +1 -0
  142. package/dist/src/provider-waits.js +482 -0
  143. package/dist/src/provider-waits.js.map +1 -0
  144. package/dist/src/queue.d.ts.map +1 -1
  145. package/dist/src/record-journal.d.ts.map +1 -1
  146. package/dist/src/record-journal.js.map +1 -1
  147. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  148. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  149. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  150. package/dist/src/recovery/offline-branch.js +29 -24
  151. package/dist/src/recovery/offline-branch.js.map +1 -1
  152. package/dist/src/recovery/recover-cli.js.map +1 -1
  153. package/dist/src/refusal-reactions.d.ts +14 -12
  154. package/dist/src/refusal-reactions.d.ts.map +1 -1
  155. package/dist/src/refusal-reactions.js +14 -12
  156. package/dist/src/refusal-reactions.js.map +1 -1
  157. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
  158. package/dist/src/runtime/liveness-watchdog.js.map +1 -1
  159. package/dist/src/shutdown.d.ts.map +1 -1
  160. package/dist/src/shutdown.js.map +1 -1
  161. package/dist/src/stored-removal.d.ts +73 -0
  162. package/dist/src/stored-removal.d.ts.map +1 -0
  163. package/dist/src/stored-removal.js +100 -0
  164. package/dist/src/stored-removal.js.map +1 -0
  165. package/dist/src/thinking-token-stamp.js.map +1 -1
  166. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  167. package/dist/src/tool-image-ledger.js.map +1 -1
  168. package/dist/src/tool-presentation.d.ts.map +1 -1
  169. package/dist/src/tool-presentation.js.map +1 -1
  170. package/dist/src/tool-result-guard.d.ts +30 -13
  171. package/dist/src/tool-result-guard.d.ts.map +1 -1
  172. package/dist/src/tool-result-guard.js +71 -25
  173. package/dist/src/tool-result-guard.js.map +1 -1
  174. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  175. package/dist/src/tune-out/coordinator.js.map +1 -1
  176. package/dist/src/tune-out/tools.d.ts +1 -1
  177. package/dist/src/tune-out/tools.d.ts.map +1 -1
  178. package/dist/src/types/agent.d.ts +42 -6
  179. package/dist/src/types/agent.d.ts.map +1 -1
  180. package/dist/src/types/events.d.ts +12 -0
  181. package/dist/src/types/events.d.ts.map +1 -1
  182. package/dist/src/types/framework.d.ts +10 -0
  183. package/dist/src/types/framework.d.ts.map +1 -1
  184. package/dist/src/types/module.d.ts +20 -8
  185. package/dist/src/types/module.d.ts.map +1 -1
  186. package/dist/src/types/trace.d.ts +75 -13
  187. package/dist/src/types/trace.d.ts.map +1 -1
  188. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  189. package/dist/test/agent-runtime-settings.test.js +1139 -3
  190. package/dist/test/agent-runtime-settings.test.js.map +1 -1
  191. package/dist/test/batched-wake-locus.test.js.map +1 -1
  192. package/dist/test/channel-label-history.test.js.map +1 -1
  193. package/dist/test/code-execution.test.js.map +1 -1
  194. package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
  195. package/dist/test/conversation-router.test.js.map +1 -1
  196. package/dist/test/conversation-routing.test.js +240 -6
  197. package/dist/test/conversation-routing.test.js.map +1 -1
  198. package/dist/test/discord-awareness-outbox.test.js +107 -0
  199. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  200. package/dist/test/discord-typing-keepalive.test.d.ts +2 -0
  201. package/dist/test/discord-typing-keepalive.test.d.ts.map +1 -0
  202. package/dist/test/discord-typing-keepalive.test.js +77 -0
  203. package/dist/test/discord-typing-keepalive.test.js.map +1 -0
  204. package/dist/test/ephemeral-settle.test.js.map +1 -1
  205. package/dist/test/event-gate-passthrough.test.js.map +1 -1
  206. package/dist/test/event-gate-policy-mutation.test.js.map +1 -1
  207. package/dist/test/event-gate-rate-limit.test.js.map +1 -1
  208. package/dist/test/event-gate-script.test.js.map +1 -1
  209. package/dist/test/event-gate-selfwake.test.js.map +1 -1
  210. package/dist/test/event-gate-tags.test.js.map +1 -1
  211. package/dist/test/event-gate.test.js.map +1 -1
  212. package/dist/test/explicit-prose-routing.test.js +363 -44
  213. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  214. package/dist/test/framework.test.js +160 -1
  215. package/dist/test/framework.test.js.map +1 -1
  216. package/dist/test/helpers/coalescing-fixture.d.ts +2 -0
  217. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  218. package/dist/test/helpers/coalescing-fixture.js +11 -4
  219. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  220. package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
  221. package/dist/test/helpers/mock-membrane.js.map +1 -1
  222. package/dist/test/hide-then-turn.test.d.ts +2 -0
  223. package/dist/test/hide-then-turn.test.d.ts.map +1 -0
  224. package/dist/test/hide-then-turn.test.js +101 -0
  225. package/dist/test/hide-then-turn.test.js.map +1 -0
  226. package/dist/test/history-module-overview.test.js.map +1 -1
  227. package/dist/test/history-module-semantic.test.js.map +1 -1
  228. package/dist/test/history-module.test.js.map +1 -1
  229. package/dist/test/history-semantic-review-findings.test.js.map +1 -1
  230. package/dist/test/host-quiesce.test.js +125 -0
  231. package/dist/test/host-quiesce.test.js.map +1 -1
  232. package/dist/test/inference-exhaustion-rewind.test.js +220 -14
  233. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  234. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  235. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  236. package/dist/test/inference-failure-bounds.test.js +1202 -0
  237. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  238. package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -1
  239. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  240. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  241. package/dist/test/late-events-after-stop.test.js +122 -0
  242. package/dist/test/late-events-after-stop.test.js.map +1 -0
  243. package/dist/test/liveness-watchdog.test.js.map +1 -1
  244. package/dist/test/maintenance-timer.test.js +1564 -2
  245. package/dist/test/maintenance-timer.test.js.map +1 -1
  246. package/dist/test/mcp-server-bin.test.d.ts +2 -0
  247. package/dist/test/mcp-server-bin.test.d.ts.map +1 -0
  248. package/dist/test/mcp-server-bin.test.js +142 -0
  249. package/dist/test/mcp-server-bin.test.js.map +1 -0
  250. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  251. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  252. package/dist/test/mcpl-child-env.test.js +1 -1
  253. package/dist/test/mcpl-child-env.test.js.map +1 -1
  254. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +2 -0
  255. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +1 -0
  256. package/dist/test/mcpl-coalesced-content-parity.test.js +532 -0
  257. package/dist/test/mcpl-coalesced-content-parity.test.js.map +1 -0
  258. package/dist/test/mcpl-coalescing.test.js +78 -16
  259. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  260. package/dist/test/mcpl-config-diagnostics.test.js +6 -3
  261. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -1
  262. package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
  263. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  264. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  265. package/dist/test/mcpl-reconnect-events.test.js.map +1 -1
  266. package/dist/test/mcpl-reconnect-reregistration.test.js +2 -0
  267. package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
  268. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  269. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  270. package/dist/test/mcpl-reference-fields.test.js +69 -2
  271. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  272. package/dist/test/mcpl-rejection-log-lines.test.d.ts +2 -0
  273. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +1 -0
  274. package/dist/test/mcpl-rejection-log-lines.test.js +349 -0
  275. package/dist/test/mcpl-rejection-log-lines.test.js.map +1 -0
  276. package/dist/test/mcpl-stderr-tail.test.d.ts +2 -0
  277. package/dist/test/mcpl-stderr-tail.test.d.ts.map +1 -0
  278. package/dist/test/mcpl-stderr-tail.test.js +210 -0
  279. package/dist/test/mcpl-stderr-tail.test.js.map +1 -0
  280. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +1 -1
  281. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  282. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -1
  283. package/dist/test/mcpl-tool-lifecycle.test.js.map +1 -1
  284. package/dist/test/mcpl-transport-close.test.d.ts +2 -0
  285. package/dist/test/mcpl-transport-close.test.d.ts.map +1 -0
  286. package/dist/test/mcpl-transport-close.test.js +373 -0
  287. package/dist/test/mcpl-transport-close.test.js.map +1 -0
  288. package/dist/test/mcpl-transport-spawn.test.d.ts +2 -0
  289. package/dist/test/mcpl-transport-spawn.test.d.ts.map +1 -0
  290. package/dist/test/mcpl-transport-spawn.test.js +58 -0
  291. package/dist/test/mcpl-transport-spawn.test.js.map +1 -0
  292. package/dist/test/mcpl-ws-transport.test.js +104 -0
  293. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  294. package/dist/test/offline-recovery-branch.test.js +344 -0
  295. package/dist/test/offline-recovery-branch.test.js.map +1 -1
  296. package/dist/test/operator-surgery.test.js +197 -0
  297. package/dist/test/operator-surgery.test.js.map +1 -1
  298. package/dist/test/ops-alert.test.js +2 -1
  299. package/dist/test/ops-alert.test.js.map +1 -1
  300. package/dist/test/overbudget-drain-breaker.test.js +1 -0
  301. package/dist/test/overbudget-drain-breaker.test.js.map +1 -1
  302. package/dist/test/pack-installs-alone.test.d.ts +2 -0
  303. package/dist/test/pack-installs-alone.test.d.ts.map +1 -0
  304. package/dist/test/pack-installs-alone.test.js +284 -0
  305. package/dist/test/pack-installs-alone.test.js.map +1 -0
  306. package/dist/test/physical-window-restart.test.d.ts +6 -4
  307. package/dist/test/physical-window-restart.test.d.ts.map +1 -1
  308. package/dist/test/physical-window-restart.test.js +275 -10
  309. package/dist/test/physical-window-restart.test.js.map +1 -1
  310. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  311. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  312. package/dist/test/placed-reaction-baseline.test.js +312 -0
  313. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  314. package/dist/test/present-while-acting.test.js +949 -6
  315. package/dist/test/present-while-acting.test.js.map +1 -1
  316. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  317. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  318. package/dist/test/provider-waits.test.d.ts +2 -0
  319. package/dist/test/provider-waits.test.d.ts.map +1 -0
  320. package/dist/test/provider-waits.test.js +2259 -0
  321. package/dist/test/provider-waits.test.js.map +1 -0
  322. package/dist/test/puppet-tool-call.test.js +2 -0
  323. package/dist/test/puppet-tool-call.test.js.map +1 -1
  324. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  325. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  326. package/dist/test/reaction-baseline-platform.test.js +124 -0
  327. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  328. package/dist/test/record-journal.test.js.map +1 -1
  329. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  330. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  331. package/dist/test/refusal-rewind.test.js +9 -0
  332. package/dist/test/refusal-rewind.test.js.map +1 -1
  333. package/dist/test/refusal-single-call.test.js.map +1 -1
  334. package/dist/test/release-bump.test.d.ts +2 -0
  335. package/dist/test/release-bump.test.d.ts.map +1 -0
  336. package/dist/test/release-bump.test.js +248 -0
  337. package/dist/test/release-bump.test.js.map +1 -0
  338. package/dist/test/release-changelog.test.js.map +1 -1
  339. package/dist/test/release-holds.test.d.ts +2 -0
  340. package/dist/test/release-holds.test.d.ts.map +1 -0
  341. package/dist/test/release-holds.test.js +456 -0
  342. package/dist/test/release-holds.test.js.map +1 -0
  343. package/dist/test/same-round-think-policy.test.js.map +1 -1
  344. package/dist/test/save-recent-image.test.js +138 -2
  345. package/dist/test/save-recent-image.test.js.map +1 -1
  346. package/dist/test/shutdown-errors.test.js.map +1 -1
  347. package/dist/test/silent-heartbeat-rows.test.js.map +1 -1
  348. package/dist/test/stop-with-host-operations.test.d.ts +20 -0
  349. package/dist/test/stop-with-host-operations.test.d.ts.map +1 -0
  350. package/dist/test/stop-with-host-operations.test.js +1174 -0
  351. package/dist/test/stop-with-host-operations.test.js.map +1 -0
  352. package/dist/test/stop-with-work-in-flight.test.js +162 -50
  353. package/dist/test/stop-with-work-in-flight.test.js.map +1 -1
  354. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  355. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  356. package/dist/test/surgery-marks-receipt.test.js +540 -0
  357. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  358. package/dist/test/tool-batch-hook.test.js.map +1 -1
  359. package/dist/test/tool-result-guard.test.js +352 -16
  360. package/dist/test/tool-result-guard.test.js.map +1 -1
  361. package/dist/test/tool-result-spill.test.js.map +1 -1
  362. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +1 -1
  363. package/dist/test/trunk-channel-routing.test.js +330 -7
  364. package/dist/test/trunk-channel-routing.test.js.map +1 -1
  365. package/dist/test/tune-out-e2e.test.js.map +1 -1
  366. package/dist/test/unconvertible-tool-result.test.js.map +1 -1
  367. package/dist/test/unsent-compiles.test.d.ts +2 -0
  368. package/dist/test/unsent-compiles.test.d.ts.map +1 -0
  369. package/dist/test/unsent-compiles.test.js +139 -0
  370. package/dist/test/unsent-compiles.test.js.map +1 -0
  371. package/dist/test/workspace-branch-continuation.test.js.map +1 -1
  372. package/dist/test/workspace-materialize-freshness.test.js.map +1 -1
  373. package/dist/test/workspace-read-text.test.js.map +1 -1
  374. package/dist/test/workspace-three-way.test.js +1022 -7
  375. package/dist/test/workspace-three-way.test.js.map +1 -1
  376. package/dist/test/workspace-watcher.test.js.map +1 -1
  377. package/dist/test/xml-round-persist.test.js.map +1 -1
  378. package/dist/tsconfig.tsbuildinfo +1 -1
  379. package/package.json +8 -6
  380. package/src/agent.ts +415 -67
  381. package/src/api/mcp-server-core.ts +681 -0
  382. package/src/api/mcp-server.ts +16 -685
  383. package/src/api/server.ts +87 -26
  384. package/src/api/types.ts +9 -1
  385. package/src/framework.ts +3389 -782
  386. package/src/index.ts +9 -4
  387. package/src/mcpl/channel-registry.ts +126 -31
  388. package/src/mcpl/feature-set-manager.ts +4 -3
  389. package/src/mcpl/index.ts +2 -0
  390. package/src/mcpl/log-value.ts +116 -0
  391. package/src/mcpl/prose-stream-router.ts +6 -6
  392. package/src/mcpl/push-coalescer.ts +76 -12
  393. package/src/mcpl/push-handler.ts +95 -23
  394. package/src/mcpl/reference-fetcher.ts +18 -8
  395. package/src/mcpl/references.ts +57 -13
  396. package/src/mcpl/refusal-lines.ts +59 -0
  397. package/src/mcpl/server-connection.ts +125 -15
  398. package/src/mcpl/tool-classes.ts +3 -2
  399. package/src/mcpl/transport.ts +105 -14
  400. package/src/mcpl/types.ts +5 -1
  401. package/src/module-registry.ts +17 -9
  402. package/src/modules/discord/index.ts +4 -2
  403. package/src/modules/workspace/disk-agreement.ts +74 -18
  404. package/src/modules/workspace/index.ts +85 -15
  405. package/src/modules/workspace/observe.ts +295 -43
  406. package/src/modules/workspace/reconcile.ts +90 -12
  407. package/src/modules/workspace/types.ts +26 -1
  408. package/src/operator-log.ts +30 -0
  409. package/src/provider-waits.ts +523 -0
  410. package/src/recovery/offline-branch.ts +32 -28
  411. package/src/refusal-reactions.ts +14 -12
  412. package/src/stored-removal.ts +126 -0
  413. package/src/tool-result-guard.ts +72 -24
  414. package/src/types/agent.ts +42 -6
  415. package/src/types/events.ts +12 -0
  416. package/src/types/framework.ts +10 -0
  417. package/src/types/module.ts +20 -8
  418. package/src/types/trace.ts +76 -14
@@ -1,5 +1,6 @@
1
1
  import { JsStore } from '@animalabs/chronicle';
2
- import { ContextManager } from '@animalabs/context-manager';
2
+ import { ContextManager, continuesBody, type BodyShard } from '@animalabs/context-manager';
3
+ import { removeStoredInterval } from '../stored-removal.js';
3
4
  import {
4
5
  DEFAULT_DISCORD_AWARENESS_EMOJI,
5
6
  DiscordAwarenessOutbox,
@@ -202,11 +203,12 @@ export async function createOfflineRecoveryBranch(
202
203
  const createdBranch = contextManager.branchAt(target.id, targetBranch);
203
204
  try {
204
205
  await contextManager.switchBranch(createdBranch);
205
- // Work from newest to oldest so earlier redactions never perturb the
206
- // live positions used by later range endpoints.
206
+ // Each interval is removed by its messages' ids, so the order doesn't
207
+ // change the result. removeStoredInterval takes a single message outside
208
+ // any body through removeMessage, and a shard or a longer interval as a
209
+ // range, which refuses one that splits a body.
207
210
  for (const interval of [...suppression.intervals].reverse()) {
208
- if (interval.fromId === interval.toId) contextManager.removeMessage(interval.fromId);
209
- else contextManager.removeMessages(interval.fromId, interval.toId);
211
+ removeStoredInterval(contextManager, interval.fromId, interval.toId);
210
212
  }
211
213
  if (batch) outbox.activate(batch.id);
212
214
  } catch (error) {
@@ -238,9 +240,7 @@ interface SuppressionInterval {
238
240
 
239
241
  interface MessageLocation {
240
242
  first: number;
241
- last: number;
242
243
  firstId: StoredWindowMessage['id'];
243
- lastId: StoredWindowMessage['id'];
244
244
  /** Usually one run; multiple runs tolerate duplicate external metadata. */
245
245
  runs: SuppressionInterval[];
246
246
  }
@@ -299,14 +299,15 @@ function validateAnchorBoundary(
299
299
  total: number,
300
300
  target: StoredWindowMessage,
301
301
  ): void {
302
- const groupId = (target as StoredWindowMessage & { bodyGroupId?: string }).bodyGroupId;
303
- if (!groupId || targetIndex + 1 >= total) return;
302
+ if (targetIndex + 1 >= total) return;
304
303
  const next = contextManager.getMessageWindow(targetIndex + 1, 1, {
305
304
  resolveBlobs: false,
306
- }).messages[0] as (StoredWindowMessage & { bodyGroupId?: string }) | undefined;
307
- if (next?.bodyGroupId === groupId) {
305
+ }).messages[0];
306
+ // A body continues only into a later shard of the same ingestion: a
307
+ // reposted document shares its group id but starts a body of its own.
308
+ if (continuesBody(target as BodyShard, next as BodyShard | undefined)) {
308
309
  throw new Error(
309
- `Recovery anchor ${String(target.id)} splits body group ${groupId}; choose its final shard.`,
310
+ `Recovery anchor ${String(target.id)} splits body group ${(target as BodyShard).bodyGroupId}; choose its final shard.`,
310
311
  );
311
312
  }
312
313
  }
@@ -321,7 +322,9 @@ function validateRemovalIntegrity(
321
322
  index >= interval.start && index <= interval.end);
322
323
  const toolUses = new Map<string, boolean>();
323
324
  const toolResults = new Map<string, boolean>();
324
- const groupMembership = new Map<string, boolean>();
325
+ // The message before, in store order: a selection splits a body when a
326
+ // message continuing the one before it lands on the other side.
327
+ let previous: { message: BodyShard; removed: boolean } | null = null;
325
328
 
326
329
  for (let offset = 0; offset < total; offset += RECOVERY_SCAN_WINDOW) {
327
330
  const messages = contextManager.getMessageWindow(
@@ -332,16 +335,14 @@ function validateRemovalIntegrity(
332
335
  for (let i = 0; i < messages.length; i++) {
333
336
  const index = offset + i;
334
337
  const isRemoved = removed(index);
335
- const message = messages[i] as StoredWindowMessage & { bodyGroupId?: string };
336
- if (message.bodyGroupId) {
337
- const prior = groupMembership.get(message.bodyGroupId);
338
- if (prior !== undefined && prior !== isRemoved) {
339
- throw new Error(
340
- `Recovery selection splits body group ${message.bodyGroupId}; adjust the anchor or suppression boundary.`,
341
- );
342
- }
343
- groupMembership.set(message.bodyGroupId, isRemoved);
338
+ const message = messages[i];
339
+ const shard = message as BodyShard;
340
+ if (previous && previous.removed !== isRemoved && continuesBody(previous.message, shard)) {
341
+ throw new Error(
342
+ `Recovery selection splits body group ${shard.bodyGroupId}; adjust the anchor or suppression boundary.`,
343
+ );
344
344
  }
345
+ previous = { message: shard, removed: isRemoved };
345
346
  for (const rawBlock of message.content) {
346
347
  const block = rawBlock as { type?: unknown; id?: unknown; toolUseId?: unknown };
347
348
  if (block.type === 'tool_use' && typeof block.id === 'string') {
@@ -402,9 +403,7 @@ function buildSuppressionPlan(
402
403
  };
403
404
  locations.set(externalId, {
404
405
  first: index,
405
- last: index,
406
406
  firstId: messages[i].id,
407
- lastId: messages[i].id,
408
407
  runs: [run],
409
408
  });
410
409
  } else {
@@ -420,8 +419,6 @@ function buildSuppressionPlan(
420
419
  toId: messages[i].id,
421
420
  });
422
421
  }
423
- location.last = index;
424
- location.lastId = messages[i].id;
425
422
  }
426
423
  }
427
424
  }
@@ -444,12 +441,19 @@ function buildSuppressionPlan(
444
441
  const to = locations.get(range.toMessageId)!;
445
442
  const earlier = from.first <= to.first ? from : to;
446
443
  const later = from.first <= to.first ? to : from;
444
+ // What's between the two messages, after the earlier one began and
445
+ // before the later one began: from the earlier one's first stored copy
446
+ // through the later one's first. Each end is every copy of it besides,
447
+ // as a single suppression is, so a copy stored outside that span (a
448
+ // write cut short and retried later) is taken on its own, and nothing
449
+ // else stored after the later one began is taken.
447
450
  intervals.push({
448
451
  start: earlier.first,
449
- end: later.last,
452
+ end: later.first,
450
453
  fromId: earlier.firstId,
451
- toId: later.lastId,
454
+ toId: later.firstId,
452
455
  });
456
+ for (const end of [from, to]) intervals.push(...end.runs.map((run) => ({ ...run })));
453
457
  }
454
458
 
455
459
  intervals.sort((a, b) => a.start - b.start || a.end - b.end);
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * Refusal-reaction markers — the single source of truth for the emoji the
3
3
  * framework places on a message when inference is refused (see
4
- * AgentFramework.reactToRefusal), exported so host composition can derive
5
- * a protective suppression baseline from the exact set the framework emits.
4
+ * AgentFramework.reactToRefusal).
6
5
  *
7
6
  * These annotations are placed by the framework on the resident's account
8
7
  * and must never re-enter a resident's context as reaction events — that is
9
8
  * the self-amplifying loop behind the 8/3 Mythos incident. The Discord
10
- * adapter suppresses them when its operator config or the host-injected
11
- * `DISCORD_SUPPRESSED_REACTIONS_BASELINE` names them; keeping the emitted
12
- * set and the exported baseline as one constant is what makes drift between
13
- * "what we stamp" and "what we suppress" structurally impossible, rather
14
- * than a promise kept in two files.
9
+ * adapter suppresses them when its operator config or
10
+ * `DISCORD_SUPPRESSED_REACTIONS_BASELINE` names them. That variable's
11
+ * protective default, which the framework injects into stdio children, is
12
+ * `framework.getPlacedReactionBaseline()` on an instance: these refusal
13
+ * markers plus the awareness markers. REFUSAL_REACTION_BASELINE below is the
14
+ * refusal set only. Keeping the emitted refusal set and the exported one as
15
+ * one constant is what makes drift between "what we stamp" and "what we
16
+ * suppress" structurally impossible, rather than a promise kept in two
17
+ * files.
15
18
  */
16
19
 
17
20
  /** Refusal category → Discord reaction emoji. Unknown categories get the
@@ -27,11 +30,10 @@ export const REFUSAL_REACTIONS: Readonly<Record<string, string>> = {
27
30
  /** Marker used when the refusal category has no dedicated emoji. */
28
31
  export const REFUSAL_REACTION_FALLBACK = '🛑';
29
32
 
30
- /** Every marker the framework can emit — the category map plus the
31
- * fallback, deduplicated, in stable declaration order. This IS the
32
- * protective baseline: host composition serializes it (comma-joined) into
33
- * `DISCORD_SUPPRESSED_REACTIONS_BASELINE` for adapters that render
34
- * reactions. */
33
+ /** Every refusal marker the framework can emit: the category map plus the
34
+ * fallback, deduplicated in stable declaration order. For the deployment's
35
+ * complete protective default, including awareness markers, use
36
+ * `framework.getPlacedReactionBaseline()` on an instance. */
35
37
  export const REFUSAL_REACTION_BASELINE: readonly string[] = [
36
38
  ...new Set([...Object.values(REFUSAL_REACTIONS), REFUSAL_REACTION_FALLBACK]),
37
39
  ];
@@ -0,0 +1,126 @@
1
+ import { bodyBoundsIn, type BodyShard, type ContextManager, type MessageId } from '@animalabs/context-manager';
2
+
3
+ type StoredLike = BodyShard & { id: MessageId };
4
+ type Removals = Pick<ContextManager, 'removeMessage' | 'removeMessages'>;
5
+
6
+ /** Minimal read surface the live-surgery helpers need from a ContextManager. */
7
+ export interface SurgeryReadableCm {
8
+ getMessageCount(): number;
9
+ getMessageWindow(
10
+ offset: number,
11
+ limit: number,
12
+ opts?: { resolveBlobs?: boolean; alignToBodyGroups?: boolean },
13
+ ): { messages: Array<{ id: unknown; bodyGroupId?: string }>; startIndex: number };
14
+ }
15
+
16
+ const SURGERY_SCAN_WINDOW = 500;
17
+
18
+ /**
19
+ * Slot index of `messageId` on the active branch, or -1. Scans from the tail
20
+ * in blob-free windows: operator targets are almost always recent, and a
21
+ * full `getAllMessages()` re-inflates every attachment on the branch.
22
+ */
23
+ export function locateMessageIndex(cm: SurgeryReadableCm, messageId: string): number {
24
+ const hit = locateMessageIndices(cm, new Set([messageId])).get(messageId);
25
+ return hit ? hit.index : -1;
26
+ }
27
+
28
+ /** Tail-first scan resolving many ids at once; stops as soon as all are found. */
29
+ export function locateMessageIndices(
30
+ cm: SurgeryReadableCm,
31
+ ids: Set<string>,
32
+ ): Map<string, { index: number; bodyGroupId?: string }> {
33
+ const found = new Map<string, { index: number; bodyGroupId?: string }>();
34
+ const total = cm.getMessageCount();
35
+ for (let end = total; end > 0 && found.size < ids.size; end -= SURGERY_SCAN_WINDOW) {
36
+ const start = Math.max(0, end - SURGERY_SCAN_WINDOW);
37
+ const { messages, startIndex } = cm.getMessageWindow(start, end - start, { resolveBlobs: false });
38
+ for (let i = messages.length - 1; i >= 0; i--) {
39
+ const id = String(messages[i].id);
40
+ if (ids.has(id) && !found.has(id)) {
41
+ found.set(id, {
42
+ index: startIndex + i,
43
+ ...(messages[i].bodyGroupId ? { bodyGroupId: messages[i].bodyGroupId } : {}),
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return found;
49
+ }
50
+
51
+ /**
52
+ * Inclusive slot range of the body-group run containing `index` (the slot
53
+ * itself when it is not sharded). Delegates to the store's own
54
+ * `alignToBodyGroups` edge walk — one contiguity assumption, not two.
55
+ */
56
+ export function bodyGroupRun(
57
+ cm: SurgeryReadableCm,
58
+ index: number,
59
+ ): { from: number; to: number; fromId: string; toId: string; group: boolean } {
60
+ const win = cm.getMessageWindow(index, 1, { alignToBodyGroups: true, resolveBlobs: false });
61
+ const from = win.startIndex;
62
+ const to = win.startIndex + win.messages.length - 1;
63
+ return {
64
+ from,
65
+ to,
66
+ fromId: String(win.messages[0].id),
67
+ toId: String(win.messages[win.messages.length - 1].id),
68
+ group: win.messages[0].bodyGroupId !== undefined,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Remove the stored messages `first`..`last` (inclusive, in store order).
74
+ *
75
+ * A single message outside any body goes through `removeMessage`.
76
+ * context-manager's `removeMessage` refuses any message that carries a body
77
+ * group id, a one-shard body's included, so a shard, and any longer interval,
78
+ * goes through `removeMessages`, which refuses an interval that splits a
79
+ * body.
80
+ */
81
+ export function removeStoredMessages(cm: Removals, first: StoredLike, last: StoredLike): void {
82
+ if (first.id === last.id && !first.bodyGroupId) {
83
+ cm.removeMessage(first.id);
84
+ return;
85
+ }
86
+ cm.removeMessages(first.id, last.id);
87
+ }
88
+
89
+ /**
90
+ * Remove one stored message by its id, with the rest of its body when it's
91
+ * in one: a module or a coalescer removes a message it added by the id
92
+ * `addMessage` returned, which for a long message stored in shards is its
93
+ * first shard's.
94
+ */
95
+ export function removeStoredMessage(cm: ContextManager, id: MessageId): void {
96
+ const message = cm.getMessage(id) as StoredLike | null;
97
+ if (!message?.bodyGroupId) {
98
+ cm.removeMessage(id);
99
+ return;
100
+ }
101
+ // Found in blob-free windows: a full read of the branch would inflate every
102
+ // attachment on it, once for each message a sweep removes.
103
+ const index = locateMessageIndex(cm, String(id));
104
+ if (index < 0) {
105
+ cm.removeMessage(id);
106
+ return;
107
+ }
108
+ const run = bodyGroupRun(cm, index);
109
+ cm.removeMessages(run.fromId as MessageId, run.toId as MessageId);
110
+ }
111
+
112
+ /** removeStoredMessages for an interval known by its ids. */
113
+ export function removeStoredInterval(cm: ContextManager, fromId: MessageId, toId: MessageId): void {
114
+ const first = cm.getMessage(fromId) as StoredLike | null;
115
+ removeStoredMessages(cm, first ?? { id: fromId }, { id: toId });
116
+ }
117
+
118
+ /**
119
+ * The first and last positions (inclusive) of the body that holds
120
+ * `messages[index]`, read the way context-manager 0.14.0 reads bodies
121
+ * (`continuesBody`, pairwise in store order). A message outside any body is a
122
+ * body of its own.
123
+ */
124
+ export function bodyRangeIn(messages: readonly unknown[], index: number): { from: number; to: number } {
125
+ return bodyBoundsIn(messages as readonly (BodyShard | null | undefined)[], index);
126
+ }
@@ -4,6 +4,8 @@ import type { ContextManager, MessageId, MessageMetadata } from '@animalabs/cont
4
4
  import type { ContentBlock, NormalizedMessage, ToolResult } from '@animalabs/membrane';
5
5
  import type { CompletedToolCall } from './types/index.js';
6
6
  import { isStateExistsError } from './module-registry.js';
7
+ import { toolResultDataToHistoryString } from './tool-result-history.js';
8
+ import { safeSlice } from './safe-slice.js';
7
9
 
8
10
  export const TOOL_RESULT_GUARD_NOTICE = 'Tool result withheld by the guard. The tool has already executed.';
9
11
  export const TOOL_RESULT_GUARD_AUDIT_STATE = 'framework/tool-result-guard';
@@ -52,31 +54,60 @@ export interface WithheldHost {
52
54
 
53
55
  /**
54
56
  * The stub a withheld result settles to: #159's neutral notice, then where
55
- * the original is. It names no refusal and no category (#159 keeps those in
56
- * operational logs), only the place, so reading it back is the agent's
57
- * deliberate choice (agent-framework #277). The place holds the result as
58
- * stored, which for one over the inline cap is its capped text, whose own
59
- * note names the file with the whole output; so the stub says "its result",
60
- * not its full result. A failed write's own error text
61
- * stays in the audit record and the logs, not here. The audit
62
- * record is said to keep the original only when it was saved: a staged
63
- * record whose save failed is still queued in memory, and a restart before
64
- * its retry loses it.
57
+ * the original is, then, when its tool marked any, the fields kept from its
58
+ * result (`kept`, from keptWhenWithheld). It names no refusal and no
59
+ * category (#159 keeps those in operational logs), so reading the rest back
60
+ * is the agent's deliberate choice (agent-framework #277). The place holds
61
+ * the result as stored, which for one over the inline cap is its capped
62
+ * text, whose own note names the file with the whole output; so the stub
63
+ * says "its result", not its full result. A failed write's own error text
64
+ * stays in the audit record and the logs, not here. The audit record is
65
+ * said to keep the original only when it was saved: a staged record whose
66
+ * save failed is still queued in memory, and a restart before its retry
67
+ * loses it.
65
68
  */
66
- export function withheldResultNotice(spill: WithheldSpill, auditDurable: boolean): string {
69
+ export function withheldResultNotice(spill: WithheldSpill, auditDurable: boolean, kept?: string): string {
67
70
  const audit = `the guard's audit record (${TOOL_RESULT_GUARD_AUDIT_STATE}), for an operator`;
68
- const kept = (only: string) => auditDurable
71
+ const keptIn = (only: string) => auditDurable
69
72
  ? `is kept ${only}in ${audit}`
70
73
  : `went ${only}to ${audit}, though saving that record had failed when the result arrived`;
71
- if (!spill || 'skipped' in spill) return `${TOOL_RESULT_GUARD_NOTICE} Its result ${kept('')}.`;
72
- if (spill.unfinished) {
73
- return `${TOOL_RESULT_GUARD_NOTICE} Its result was still being written to workspace file ${spill.path} ` +
74
- `when this note was made; if that file is missing, it ${kept('')}.`;
75
- }
76
- if (spill.error === undefined) {
77
- return `${TOOL_RESULT_GUARD_NOTICE} Its result is in workspace file ${spill.path}.`;
74
+ const place = !spill || 'skipped' in spill
75
+ ? `Its result ${keptIn('')}.`
76
+ : spill.unfinished
77
+ ? `Its result was still being written to workspace file ${spill.path} when this note was made; ` +
78
+ `if that file is missing, it ${keptIn('')}.`
79
+ : spill.error === undefined
80
+ ? `Its result is in workspace file ${spill.path}.`
81
+ : `Writing its result to workspace file ${spill.path} failed, so it ${keptIn('only ')}.`;
82
+ const fields = kept === undefined ? '' : ` Kept from its result: ${kept}`;
83
+ return `${TOOL_RESULT_GUARD_NOTICE} ${place}${fields}`;
84
+ }
85
+
86
+ /**
87
+ * What a withheld result's stub keeps of it: the fields of its data that its
88
+ * tool marked as saying what it acted on (ToolResult.keepWhenWithheld), in
89
+ * the order marked, serialized as a stored result is. Past the inline cap
90
+ * they're cut, with a note that says so and where the whole result is, since
91
+ * a later field can then be cut away entirely. Undefined when it keeps
92
+ * nothing, as for a failed result (agent-framework #277). A write's file
93
+ * list is evidence of what it did, rarely what drew a refusal, and keeping it
94
+ * is what shows a clobbered file when the receipt is withheld.
95
+ */
96
+ export function keptWhenWithheld(result: CompletedToolCall['result'], maxChars: number | undefined): string | undefined {
97
+ const { data, keepWhenWithheld: keys } = result;
98
+ if (!result.success || result.isError || !keys?.length || !data || typeof data !== 'object' || Array.isArray(data)) {
99
+ return undefined;
78
100
  }
79
- return `${TOOL_RESULT_GUARD_NOTICE} Writing its result to workspace file ${spill.path} failed, so it ${kept('only ')}.`;
101
+ const fields = data as Record<string, unknown>;
102
+ const kept = Object.fromEntries(keys.filter((key) => Object.hasOwn(fields, key)).map((key) => [key, fields[key]]));
103
+ if (Object.keys(kept).length === 0) return undefined;
104
+ const whole = toolResultDataToHistoryString(kept);
105
+ if (!maxChars || whole.length <= maxChars) return whole;
106
+ // Never half a surrogate pair: the stub goes to the provider as text, so
107
+ // the cut can be one character short of the cap.
108
+ const head = safeSlice(whole, 0, maxChars);
109
+ return `${head} [cut at the inline cap: ${head.length} of these fields' ${whole.length} ` +
110
+ 'characters; the whole result is where this note says]';
80
111
  }
81
112
 
82
113
  interface PendingBatch {
@@ -89,6 +120,9 @@ interface PendingBatch {
89
120
  submitted: boolean;
90
121
  /** False when the audit sync failed: originals then never go on the wire. */
91
122
  durable: boolean;
123
+ /** What each result's stub keeps if the batch is withheld, by its index in
124
+ * content: the fields its tool marked (keptWhenWithheld). */
125
+ kept: Map<number, string>;
92
126
  }
93
127
 
94
128
  interface AuditOperation {
@@ -280,11 +314,15 @@ export class ToolResultGuard {
280
314
  : JSON.parse(json);
281
315
  }
282
316
 
317
+ /** `inlineCap` is the cap the caller stored `content` under; what a stub
318
+ * keeps of a withheld result is cut at it too. */
283
319
  storeResults(
284
320
  content: ContentBlock[],
285
321
  wireResults: ToolResult[],
286
322
  originals: CompletedToolCall[],
287
- metadata?: MessageMetadata,
323
+ metadata: MessageMetadata | undefined,
324
+ /** The cap the results were stored under: what a stub keeps is cut there too. */
325
+ inlineCap: number | undefined,
288
326
  ): MessageId {
289
327
  if (!this.enabled) return this.cm.addMessage('user', content, metadata);
290
328
  if (this.pending) {
@@ -312,13 +350,22 @@ export class ToolResultGuard {
312
350
  const withheld: ContentBlock[] = content.map((block) => block.type === 'tool_result'
313
351
  ? { type: 'tool_result', toolUseId: block.toolUseId, content: TOOL_RESULT_GUARD_NOTICE, isError: block.isError }
314
352
  : block);
353
+ // What each stub keeps if the batch is withheld, matched to its tool's
354
+ // result by tool use id, keyed by index as the stubs and files are.
355
+ const results = new Map(originals.map((call) => [call.id, call.result]));
356
+ const kept = new Map<number, string>();
357
+ for (const [index, block] of content.entries()) {
358
+ const result = block.type === 'tool_result' ? results.get(block.toolUseId) : undefined;
359
+ const text = result ? keptWhenWithheld(result, inlineCap) : undefined;
360
+ if (text !== undefined) kept.set(index, text);
361
+ }
315
362
  // Compression hold (CM >= 0.12): placed BEFORE onNewMessage fires, so no
316
363
  // chunk is ever summarized from the placeholder; released after the
317
364
  // settlement edit. Holds are in-memory only — a reopened manager has none,
318
365
  // which is correct since a reopened guard has no pending batch.
319
366
  const messageId = this.cm.addMessage('user', withheld, metadata, undefined, { holdCompression: true });
320
367
  this.pending = { id, messageId, content, withheld, wireResults,
321
- branch: this.cm.currentBranch().name, submitted: false, durable: false };
368
+ branch: this.cm.currentBranch().name, submitted: false, durable: false, kept };
322
369
  // Durability barrier: the audit must reach Chronicle's chain heads before
323
370
  // the originals can go to a provider. On a failed sync the batch fails
324
371
  // CLOSED: the placeholders go on the wire instead (the turn continues,
@@ -452,7 +499,8 @@ export class ToolResultGuard {
452
499
  /**
453
500
  * Turn a withheld batch's notices into stubs that say where each original
454
501
  * is (agent-framework #277): written to the workspace when a writable mount
455
- * takes it, else kept in the audit record. Its own unedited placeholder is
502
+ * takes it, else kept in the audit record. A stub also keeps the file
503
+ * lists its tool marked. Its own unedited placeholder is
456
504
  * the only thing it edits: an operator's later edit, removal or rollback
457
505
  * stands.
458
506
  *
@@ -540,7 +588,7 @@ export class ToolResultGuard {
540
588
  spills.set(index, spill);
541
589
  }
542
590
  const annotated: ContentBlock[] = pending.withheld.map((block, index) => block.type === 'tool_result'
543
- ? { ...block, content: withheldResultNotice(spills.get(index) ?? null, pending.durable) }
591
+ ? { ...block, content: withheldResultNotice(spills.get(index) ?? null, pending.durable, pending.kept.get(index)) }
544
592
  : block);
545
593
  try {
546
594
  this.append({
@@ -53,11 +53,14 @@ export interface AgentConfig {
53
53
  /**
54
54
  * The model's PHYSICAL context window (provider hard cap, e.g. 200000).
55
55
  * When set, the framework projects each continuation round's real size
56
- * (cache-inclusive input of the prior round + the blocks about to be
57
- * appended + reserve for response) and restarts the stream through a fresh
58
- * compile instead of dispatching a request the provider will 400
59
- * (issue #92: a legal compile can walk past the physical window mid-turn).
60
- * Unset → no projection; only the maxStreamTokens restart applies.
56
+ * (cache-inclusive input of the latest call that reported its prompt +
57
+ * that call's output + what the stream has grown by since, if later calls
58
+ * reported none + the blocks about to be appended + reserve for response)
59
+ * and restarts the stream through a fresh compile instead of dispatching a
60
+ * request the provider will 400 (issue #92: a legal compile can walk past
61
+ * the physical window mid-turn). The projection runs once a call on the
62
+ * stream has reported its prompt. Unset → no projection; only the
63
+ * maxStreamTokens restart applies.
61
64
  */
62
65
  physicalWindowTokens?: number;
63
66
 
@@ -223,7 +226,20 @@ export interface AgentRuntimeSettingsPatch {
223
226
  }
224
227
 
225
228
  export interface AgentRuntimeSettingsSnapshot {
229
+ /**
230
+ * The budget the agent is set to: while a lower one converges, its target,
231
+ * and while a persisted one the agent couldn't apply is kept, that one.
232
+ */
226
233
  contextBudgetTokens: number;
234
+ /**
235
+ * The budget compiles plan at now: the live budget, or the default when the
236
+ * recipe sets none (the context manager's own default is the same). It
237
+ * equals `contextBudgetTokens` except while a lower budget converges, when
238
+ * it is still the budget the descent started from (context-manager #162),
239
+ * and while a budget the agent couldn't apply is kept, when it is the
240
+ * budget the agent runs at instead.
241
+ */
242
+ effectiveContextBudgetTokens: number;
227
243
  tailTokens?: number;
228
244
  transitionPaceTokens?: number;
229
245
  sameRoundThinkTextPolicy: SameRoundThinkTextPolicy;
@@ -233,7 +249,27 @@ export interface AgentRuntimeSettingsSnapshot {
233
249
  }
234
250
 
235
251
  /** Persisted values differ from recipe defaults; omitted keys inherit config. */
236
- export type AgentRuntimeSettingsOverrides = AgentRuntimeSettingsPatch;
252
+ export type AgentRuntimeSettingsOverrides = AgentRuntimeSettingsPatch & {
253
+ /**
254
+ * The record's own `contextBudgetTokens`, written when a compile at the
255
+ * agent's live budget fits at it, with nothing about the budget changed
256
+ * while it ran (a paced descent's settle included): the store fit that
257
+ * budget. A compile the context manager refuses at it as over budget
258
+ * withdraws it, unless a compile at it that started later has already
259
+ * ended.
260
+ * When it equals `contextBudgetTokens`, a restore applies that budget at
261
+ * once, rather than pacing a descent again from the recipe's budget
262
+ * (context-manager #162), if a preview at boot says it still fits; if not,
263
+ * a budget below the recipe's restores paced, and if a compile at one that
264
+ * the boot applied at once is refused, the agent restores it paced then.
265
+ * Since the store fit it when the field was written, the planner can fold
266
+ * to it at once even if its carried frontier was lost. Any other value is
267
+ * stale (the budget changed since, or an earlier release carried the field
268
+ * forward), and a budget below the recipe's restores paced, as it does
269
+ * without the field. Never part of a patch.
270
+ */
271
+ contextBudgetReachedTokens?: number;
272
+ };
237
273
 
238
274
  /**
239
275
  * Result of running inference.
@@ -199,6 +199,18 @@ export interface ToolResult {
199
199
  * Adapted from Anarchid/agent-framework@mcpl-module-proto.
200
200
  */
201
201
  endTurn?: boolean;
202
+ /**
203
+ * Fields of `data` that say what the tool acted on, such as the paths a
204
+ * write wrote, removed or took from disk, the path it deleted, or a mount
205
+ * whose root it accepted, in the order they should be kept. When the
206
+ * tool-result guard withholds this result, these fields stay in its stub,
207
+ * so the agent still sees what the tool did; the result as stored goes
208
+ * where every withheld original goes (agent-framework #277). Past the
209
+ * inline cap they're cut, in this order, so a later field can be cut away
210
+ * entirely; put the short, telling ones first. Only data's own fields can
211
+ * be kept, and a failed result (success false) keeps nothing.
212
+ */
213
+ keepWhenWithheld?: string[];
202
214
  }
203
215
 
204
216
  /**
@@ -302,6 +302,10 @@ export interface ContextMaintenanceAgentRun {
302
302
  pendingAfter?: string;
303
303
  progressBefore?: Record<string, unknown>;
304
304
  progressAfter?: Record<string, unknown>;
305
+ /**
306
+ * The agent's failure in this pass: a tick that threw, or its readiness
307
+ * that couldn't be read, before the ticks or after them.
308
+ */
305
309
  error?: string;
306
310
  }
307
311
 
@@ -323,6 +327,12 @@ export interface ContextMaintenanceSnapshot {
323
327
  agentName: string;
324
328
  ready: boolean;
325
329
  pending?: string;
330
+ /**
331
+ * Why the agent's readiness can't be read: the context manager refuses
332
+ * while its strategy's initialization is pending or has failed, and a
333
+ * strategy can refuse on its own. `ready` is false then.
334
+ */
335
+ error?: string;
326
336
  progress?: Record<string, unknown>;
327
337
  }>;
328
338
  }
@@ -217,14 +217,19 @@ export interface ModuleContext {
217
217
  getLogLength(name: string): number;
218
218
 
219
219
  /**
220
- * Process queue for pushing events from external listeners.
220
+ * Process queue for pushing events from external listeners. Once stop()
221
+ * has begun, a push is dropped with a log line naming this module, as
222
+ * pushEvent()'s is.
221
223
  * @deprecated Use pushEvent() instead — it also emits process:received traces.
222
224
  */
223
225
  readonly queue: ProcessQueue;
224
226
 
225
227
  /**
226
228
  * Push a process event to the queue and emit a process:received trace.
227
- * Preferred over direct queue.push() for proper observability.
229
+ * Preferred over direct queue.push() for proper observability. Once stop()
230
+ * has begun, nothing will process the event: it's dropped with a log line
231
+ * naming this module, untraced, rather than thrown into the listener or
232
+ * stop() that pushed it.
228
233
  */
229
234
  pushEvent(event: ProcessEvent): void;
230
235
 
@@ -316,10 +321,14 @@ export interface ModuleContext {
316
321
  *
317
322
  * Modules use this to react to agent lifecycle without polling — e.g. an
318
323
  * adapter that wants to show a "typing" indicator can bracket it between
319
- * `inference:started` and the terminal events (`inference:turn_ended`,
320
- * `inference:aborted`, `inference:failed`, `inference:exhausted`), all of
321
- * which carry `agentName`. The terminal events fire once per turn, after any
322
- * tool-call cycles, so a single start/stop bracket spans the whole turn.
324
+ * `inference:started` and the terminal events: `inference:completed` (the
325
+ * normal end), `inference:turn_ended` (a tool result ended the turn),
326
+ * `inference:aborted`, `inference:failed` and `inference:exhausted`, all of
327
+ * which carry `agentName`. A turn's tool-call cycles stay inside its
328
+ * stream, so one bracket spans them. A context-budget restart emits
329
+ * `inference:started` again with no terminal event for the stream it
330
+ * replaced, and a failing turn can emit `inference:failed` and then
331
+ * `inference:exhausted`, so a bracket should allow a repeated start or end.
323
332
  *
324
333
  * Returns an unsubscribe function. Call it in the module's `stop()` to avoid
325
334
  * leaking listeners across teardown/recreate (e.g. session switch).
@@ -370,14 +379,17 @@ export interface ProcessState {
370
379
  getActiveTools(): ToolDefinition[];
371
380
 
372
381
  /**
373
- * Queue for emitting follow-up events.
382
+ * Queue for emitting follow-up events. Once stop() has begun, a push is
383
+ * dropped with a log line naming the module, as pushEvent()'s is.
374
384
  * @deprecated Use pushEvent() instead — it also emits process:received traces.
375
385
  */
376
386
  readonly queue: ProcessQueue;
377
387
 
378
388
  /**
379
389
  * Push a process event to the queue and emit a process:received trace.
380
- * Preferred over direct queue.push() for proper observability.
390
+ * Preferred over direct queue.push() for proper observability. Once stop()
391
+ * has begun, the event is dropped with a log line naming the module,
392
+ * untraced.
381
393
  */
382
394
  pushEvent(event: ProcessEvent): void;
383
395
  }