@animalabs/agent-framework 0.22.1 → 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 (359) hide show
  1. package/README.md +1 -1
  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 +404 -65
  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 +50 -26
  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 +393 -113
  23. package/dist/src/framework.d.ts.map +1 -1
  24. package/dist/src/framework.js +1748 -582
  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 +2 -2
  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/inference-router.d.ts.map +1 -1
  47. package/dist/src/mcpl/log-value.d.ts +54 -0
  48. package/dist/src/mcpl/log-value.d.ts.map +1 -0
  49. package/dist/src/mcpl/log-value.js +109 -0
  50. package/dist/src/mcpl/log-value.js.map +1 -0
  51. package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
  52. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  53. package/dist/src/mcpl/prose-stream-router.js +4 -4
  54. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  55. package/dist/src/mcpl/push-coalescer.d.ts +38 -3
  56. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  57. package/dist/src/mcpl/push-coalescer.js +81 -16
  58. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  59. package/dist/src/mcpl/push-handler.d.ts +26 -3
  60. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  61. package/dist/src/mcpl/push-handler.js +87 -23
  62. package/dist/src/mcpl/push-handler.js.map +1 -1
  63. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  64. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  65. package/dist/src/mcpl/references.d.ts +2 -2
  66. package/dist/src/mcpl/references.d.ts.map +1 -1
  67. package/dist/src/mcpl/references.js.map +1 -1
  68. package/dist/src/mcpl/refusal-lines.d.ts +37 -0
  69. package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
  70. package/dist/src/mcpl/refusal-lines.js +61 -0
  71. package/dist/src/mcpl/refusal-lines.js.map +1 -0
  72. package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
  73. package/dist/src/mcpl/server-connection.d.ts +18 -2
  74. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  75. package/dist/src/mcpl/server-connection.js +66 -11
  76. package/dist/src/mcpl/server-connection.js.map +1 -1
  77. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  78. package/dist/src/mcpl/tool-classes.d.ts +1 -1
  79. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  80. package/dist/src/mcpl/tool-classes.js +3 -2
  81. package/dist/src/mcpl/tool-classes.js.map +1 -1
  82. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  83. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  84. package/dist/src/mcpl/transport.d.ts +23 -5
  85. package/dist/src/mcpl/transport.d.ts.map +1 -1
  86. package/dist/src/mcpl/transport.js +103 -10
  87. package/dist/src/mcpl/transport.js.map +1 -1
  88. package/dist/src/mcpl/types.d.ts +30 -26
  89. package/dist/src/mcpl/types.d.ts.map +1 -1
  90. package/dist/src/mcpl/types.js.map +1 -1
  91. package/dist/src/module-registry.d.ts.map +1 -1
  92. package/dist/src/module-registry.js.map +1 -1
  93. package/dist/src/modules/api/index.d.ts.map +1 -1
  94. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  95. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  96. package/dist/src/modules/discord/index.d.ts.map +1 -1
  97. package/dist/src/modules/discord/index.js +4 -2
  98. package/dist/src/modules/discord/index.js.map +1 -1
  99. package/dist/src/modules/health/index.d.ts.map +1 -1
  100. package/dist/src/modules/health/index.js.map +1 -1
  101. package/dist/src/modules/history/index.d.ts.map +1 -1
  102. package/dist/src/modules/history/index.js.map +1 -1
  103. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  104. package/dist/src/modules/history/semantic.js.map +1 -1
  105. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  106. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  107. package/dist/src/modules/workspace/disk-agreement.d.ts +22 -4
  108. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  109. package/dist/src/modules/workspace/disk-agreement.js +63 -25
  110. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  111. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  112. package/dist/src/modules/workspace/index.d.ts +8 -4
  113. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  114. package/dist/src/modules/workspace/index.js +39 -12
  115. package/dist/src/modules/workspace/index.js.map +1 -1
  116. package/dist/src/modules/workspace/observe.d.ts +43 -8
  117. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  118. package/dist/src/modules/workspace/observe.js +264 -39
  119. package/dist/src/modules/workspace/observe.js.map +1 -1
  120. package/dist/src/modules/workspace/reconcile.d.ts +17 -1
  121. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  122. package/dist/src/modules/workspace/reconcile.js +57 -14
  123. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  124. package/dist/src/modules/workspace/types.d.ts +27 -2
  125. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  126. package/dist/src/modules/workspace/types.js.map +1 -1
  127. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  128. package/dist/src/modules/workspace/watcher.js.map +1 -1
  129. package/dist/src/operator-log.d.ts.map +1 -1
  130. package/dist/src/operator-log.js.map +1 -1
  131. package/dist/src/provider-waits.d.ts.map +1 -1
  132. package/dist/src/provider-waits.js.map +1 -1
  133. package/dist/src/queue.d.ts.map +1 -1
  134. package/dist/src/record-journal.d.ts.map +1 -1
  135. package/dist/src/record-journal.js.map +1 -1
  136. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  137. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  138. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  139. package/dist/src/recovery/offline-branch.js +29 -24
  140. package/dist/src/recovery/offline-branch.js.map +1 -1
  141. package/dist/src/recovery/recover-cli.js.map +1 -1
  142. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
  143. package/dist/src/runtime/liveness-watchdog.js.map +1 -1
  144. package/dist/src/shutdown.d.ts.map +1 -1
  145. package/dist/src/shutdown.js.map +1 -1
  146. package/dist/src/stored-removal.d.ts +73 -0
  147. package/dist/src/stored-removal.d.ts.map +1 -0
  148. package/dist/src/stored-removal.js +100 -0
  149. package/dist/src/stored-removal.js.map +1 -0
  150. package/dist/src/thinking-token-stamp.js.map +1 -1
  151. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  152. package/dist/src/tool-image-ledger.js.map +1 -1
  153. package/dist/src/tool-presentation.d.ts.map +1 -1
  154. package/dist/src/tool-presentation.js.map +1 -1
  155. package/dist/src/tool-result-guard.d.ts.map +1 -1
  156. package/dist/src/tool-result-guard.js.map +1 -1
  157. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  158. package/dist/src/tune-out/coordinator.js.map +1 -1
  159. package/dist/src/tune-out/tools.d.ts +1 -1
  160. package/dist/src/tune-out/tools.d.ts.map +1 -1
  161. package/dist/src/types/agent.d.ts +42 -6
  162. package/dist/src/types/agent.d.ts.map +1 -1
  163. package/dist/src/types/framework.d.ts +10 -0
  164. package/dist/src/types/framework.d.ts.map +1 -1
  165. package/dist/src/types/module.d.ts +8 -4
  166. package/dist/src/types/module.d.ts.map +1 -1
  167. package/dist/src/types/trace.d.ts +61 -10
  168. package/dist/src/types/trace.d.ts.map +1 -1
  169. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  170. package/dist/test/agent-runtime-settings.test.js +1139 -3
  171. package/dist/test/agent-runtime-settings.test.js.map +1 -1
  172. package/dist/test/batched-wake-locus.test.js.map +1 -1
  173. package/dist/test/channel-label-history.test.js.map +1 -1
  174. package/dist/test/code-execution.test.js.map +1 -1
  175. package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
  176. package/dist/test/conversation-router.test.js.map +1 -1
  177. package/dist/test/conversation-routing.test.js +240 -6
  178. package/dist/test/conversation-routing.test.js.map +1 -1
  179. package/dist/test/discord-awareness-outbox.test.js +104 -0
  180. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  181. package/dist/test/discord-typing-keepalive.test.d.ts +2 -0
  182. package/dist/test/discord-typing-keepalive.test.d.ts.map +1 -0
  183. package/dist/test/discord-typing-keepalive.test.js +77 -0
  184. package/dist/test/discord-typing-keepalive.test.js.map +1 -0
  185. package/dist/test/ephemeral-settle.test.js.map +1 -1
  186. package/dist/test/event-gate-passthrough.test.js.map +1 -1
  187. package/dist/test/event-gate-policy-mutation.test.js.map +1 -1
  188. package/dist/test/event-gate-rate-limit.test.js.map +1 -1
  189. package/dist/test/event-gate-script.test.js.map +1 -1
  190. package/dist/test/event-gate-selfwake.test.js.map +1 -1
  191. package/dist/test/event-gate-tags.test.js.map +1 -1
  192. package/dist/test/event-gate.test.js.map +1 -1
  193. package/dist/test/explicit-prose-routing.test.js +363 -42
  194. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  195. package/dist/test/framework.test.js.map +1 -1
  196. package/dist/test/helpers/coalescing-fixture.d.ts +2 -0
  197. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  198. package/dist/test/helpers/coalescing-fixture.js +8 -2
  199. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  200. package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
  201. package/dist/test/helpers/mock-membrane.js.map +1 -1
  202. package/dist/test/hide-then-turn.test.d.ts +2 -0
  203. package/dist/test/hide-then-turn.test.d.ts.map +1 -0
  204. package/dist/test/hide-then-turn.test.js +101 -0
  205. package/dist/test/hide-then-turn.test.js.map +1 -0
  206. package/dist/test/history-module-overview.test.js.map +1 -1
  207. package/dist/test/history-module-semantic.test.js.map +1 -1
  208. package/dist/test/history-module.test.js.map +1 -1
  209. package/dist/test/history-semantic-review-findings.test.js.map +1 -1
  210. package/dist/test/host-quiesce.test.js +125 -0
  211. package/dist/test/host-quiesce.test.js.map +1 -1
  212. package/dist/test/inference-exhaustion-rewind.test.js +81 -3
  213. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  214. package/dist/test/inference-failure-bounds.test.js +1 -0
  215. package/dist/test/inference-failure-bounds.test.js.map +1 -1
  216. package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -1
  217. package/dist/test/liveness-watchdog.test.js.map +1 -1
  218. package/dist/test/maintenance-timer.test.js +1564 -2
  219. package/dist/test/maintenance-timer.test.js.map +1 -1
  220. package/dist/test/mcp-server-bin.test.d.ts +2 -0
  221. package/dist/test/mcp-server-bin.test.d.ts.map +1 -0
  222. package/dist/test/mcp-server-bin.test.js +142 -0
  223. package/dist/test/mcp-server-bin.test.js.map +1 -0
  224. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  225. package/dist/test/mcpl-child-env.test.js +1 -1
  226. package/dist/test/mcpl-child-env.test.js.map +1 -1
  227. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +2 -0
  228. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +1 -0
  229. package/dist/test/mcpl-coalesced-content-parity.test.js +532 -0
  230. package/dist/test/mcpl-coalesced-content-parity.test.js.map +1 -0
  231. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  232. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -1
  233. package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
  234. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  235. package/dist/test/mcpl-reconnect-events.test.js.map +1 -1
  236. package/dist/test/mcpl-reconnect-reregistration.test.js +2 -0
  237. package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
  238. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  239. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  240. package/dist/test/mcpl-rejection-log-lines.test.d.ts +2 -0
  241. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +1 -0
  242. package/dist/test/mcpl-rejection-log-lines.test.js +349 -0
  243. package/dist/test/mcpl-rejection-log-lines.test.js.map +1 -0
  244. package/dist/test/mcpl-stderr-tail.test.d.ts +2 -0
  245. package/dist/test/mcpl-stderr-tail.test.d.ts.map +1 -0
  246. package/dist/test/mcpl-stderr-tail.test.js +210 -0
  247. package/dist/test/mcpl-stderr-tail.test.js.map +1 -0
  248. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +1 -1
  249. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  250. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -1
  251. package/dist/test/mcpl-tool-lifecycle.test.js.map +1 -1
  252. package/dist/test/mcpl-transport-close.test.d.ts +2 -0
  253. package/dist/test/mcpl-transport-close.test.d.ts.map +1 -0
  254. package/dist/test/mcpl-transport-close.test.js +373 -0
  255. package/dist/test/mcpl-transport-close.test.js.map +1 -0
  256. package/dist/test/mcpl-transport-spawn.test.d.ts +2 -0
  257. package/dist/test/mcpl-transport-spawn.test.d.ts.map +1 -0
  258. package/dist/test/mcpl-transport-spawn.test.js +58 -0
  259. package/dist/test/mcpl-transport-spawn.test.js.map +1 -0
  260. package/dist/test/mcpl-ws-transport.test.js +1 -0
  261. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  262. package/dist/test/offline-recovery-branch.test.js +344 -0
  263. package/dist/test/offline-recovery-branch.test.js.map +1 -1
  264. package/dist/test/operator-surgery.test.js +197 -0
  265. package/dist/test/operator-surgery.test.js.map +1 -1
  266. package/dist/test/overbudget-drain-breaker.test.js +1 -0
  267. package/dist/test/overbudget-drain-breaker.test.js.map +1 -1
  268. package/dist/test/pack-installs-alone.test.d.ts +2 -0
  269. package/dist/test/pack-installs-alone.test.d.ts.map +1 -0
  270. package/dist/test/pack-installs-alone.test.js +284 -0
  271. package/dist/test/pack-installs-alone.test.js.map +1 -0
  272. package/dist/test/physical-window-restart.test.d.ts +6 -4
  273. package/dist/test/physical-window-restart.test.d.ts.map +1 -1
  274. package/dist/test/physical-window-restart.test.js +275 -10
  275. package/dist/test/physical-window-restart.test.js.map +1 -1
  276. package/dist/test/placed-reaction-baseline.test.js.map +1 -1
  277. package/dist/test/present-while-acting.test.js +949 -6
  278. package/dist/test/present-while-acting.test.js.map +1 -1
  279. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  280. package/dist/test/provider-waits.test.js +67 -4
  281. package/dist/test/provider-waits.test.js.map +1 -1
  282. package/dist/test/puppet-tool-call.test.js +2 -0
  283. package/dist/test/puppet-tool-call.test.js.map +1 -1
  284. package/dist/test/reaction-baseline-platform.test.js.map +1 -1
  285. package/dist/test/record-journal.test.js.map +1 -1
  286. package/dist/test/refusal-rewind.test.js +9 -0
  287. package/dist/test/refusal-rewind.test.js.map +1 -1
  288. package/dist/test/refusal-single-call.test.js.map +1 -1
  289. package/dist/test/release-bump.test.d.ts +2 -0
  290. package/dist/test/release-bump.test.d.ts.map +1 -0
  291. package/dist/test/release-bump.test.js +248 -0
  292. package/dist/test/release-bump.test.js.map +1 -0
  293. package/dist/test/release-changelog.test.js.map +1 -1
  294. package/dist/test/release-holds.test.d.ts +2 -0
  295. package/dist/test/release-holds.test.d.ts.map +1 -0
  296. package/dist/test/release-holds.test.js +456 -0
  297. package/dist/test/release-holds.test.js.map +1 -0
  298. package/dist/test/same-round-think-policy.test.js.map +1 -1
  299. package/dist/test/shutdown-errors.test.js.map +1 -1
  300. package/dist/test/silent-heartbeat-rows.test.js.map +1 -1
  301. package/dist/test/stop-with-host-operations.test.d.ts +20 -0
  302. package/dist/test/stop-with-host-operations.test.d.ts.map +1 -0
  303. package/dist/test/stop-with-host-operations.test.js +1174 -0
  304. package/dist/test/stop-with-host-operations.test.js.map +1 -0
  305. package/dist/test/stop-with-work-in-flight.test.js +162 -50
  306. package/dist/test/stop-with-work-in-flight.test.js.map +1 -1
  307. package/dist/test/surgery-marks-receipt.test.js.map +1 -1
  308. package/dist/test/tool-batch-hook.test.js.map +1 -1
  309. package/dist/test/tool-result-guard.test.js +131 -9
  310. package/dist/test/tool-result-guard.test.js.map +1 -1
  311. package/dist/test/tool-result-spill.test.js.map +1 -1
  312. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +1 -1
  313. package/dist/test/trunk-channel-routing.test.js +330 -7
  314. package/dist/test/trunk-channel-routing.test.js.map +1 -1
  315. package/dist/test/tune-out-e2e.test.js.map +1 -1
  316. package/dist/test/unconvertible-tool-result.test.js.map +1 -1
  317. package/dist/test/unsent-compiles.test.d.ts +2 -0
  318. package/dist/test/unsent-compiles.test.d.ts.map +1 -0
  319. package/dist/test/unsent-compiles.test.js +139 -0
  320. package/dist/test/unsent-compiles.test.js.map +1 -0
  321. package/dist/test/workspace-branch-continuation.test.js.map +1 -1
  322. package/dist/test/workspace-materialize-freshness.test.js.map +1 -1
  323. package/dist/test/workspace-read-text.test.js.map +1 -1
  324. package/dist/test/workspace-three-way.test.js +907 -7
  325. package/dist/test/workspace-three-way.test.js.map +1 -1
  326. package/dist/test/workspace-watcher.test.js.map +1 -1
  327. package/dist/test/xml-round-persist.test.js.map +1 -1
  328. package/dist/tsconfig.tsbuildinfo +1 -1
  329. package/package.json +8 -6
  330. package/src/agent.ts +411 -66
  331. package/src/api/mcp-server-core.ts +681 -0
  332. package/src/api/mcp-server.ts +16 -685
  333. package/src/api/server.ts +51 -29
  334. package/src/api/types.ts +9 -1
  335. package/src/framework.ts +1899 -641
  336. package/src/index.ts +2 -2
  337. package/src/mcpl/channel-registry.ts +126 -31
  338. package/src/mcpl/feature-set-manager.ts +4 -3
  339. package/src/mcpl/log-value.ts +116 -0
  340. package/src/mcpl/prose-stream-router.ts +6 -6
  341. package/src/mcpl/push-coalescer.ts +76 -12
  342. package/src/mcpl/push-handler.ts +95 -23
  343. package/src/mcpl/refusal-lines.ts +59 -0
  344. package/src/mcpl/server-connection.ts +70 -11
  345. package/src/mcpl/tool-classes.ts +3 -2
  346. package/src/mcpl/transport.ts +105 -14
  347. package/src/mcpl/types.ts +5 -1
  348. package/src/modules/discord/index.ts +4 -2
  349. package/src/modules/workspace/disk-agreement.ts +74 -18
  350. package/src/modules/workspace/index.ts +41 -11
  351. package/src/modules/workspace/observe.ts +265 -39
  352. package/src/modules/workspace/reconcile.ts +71 -14
  353. package/src/modules/workspace/types.ts +26 -1
  354. package/src/recovery/offline-branch.ts +32 -28
  355. package/src/stored-removal.ts +126 -0
  356. package/src/types/agent.ts +42 -6
  357. package/src/types/framework.ts +10 -0
  358. package/src/types/module.ts +8 -4
  359. package/src/types/trace.ts +62 -11
package/src/agent.ts CHANGED
@@ -20,6 +20,9 @@ import {
20
20
  export interface ActivationRequestOptions {
21
21
  /** The silent heartbeat tick this request answers, if any. */
22
22
  silentHeartbeat?: SilentHeartbeatTick;
23
+ /** A request that will never be sent (a preview): compile it as a dry
24
+ * run (see `compileWithInjections` for what that leaves alone). */
25
+ dryRun?: boolean;
23
26
  }
24
27
 
25
28
  export interface StartStreamResult {
@@ -120,15 +123,38 @@ export class Agent {
120
123
  * closure) so the next activation can close whatever its predecessor left
121
124
  * open — see failOpenKvSubmissions. */
122
125
  private kvOpenQueue: Array<{ submissionId: string; wireReceipt: CacheWireReceipt }> | null = null;
126
+ /** The current stream's fresh input so far, as membrane's usage event
127
+ * reports it (cumulative across the tool loop): what the maxStreamTokens
128
+ * budget checks. */
123
129
  lastStreamInputTokens = 0;
124
- /** Real prefix size of the last usage event: fresh + cache creation +
125
- * cache read. THE window-shaped number — `lastStreamInputTokens` alone
126
- * omits cached tokens, which are most of a warm stream's window. */
130
+ /** Real prompt size of the stream's latest provider call that reported a
131
+ * prompt, which is what the provider billed it: fresh + cache creation +
132
+ * cache read. A call that reports none leaves it as it was (see
133
+ * lastStreamUnreportedTokens). Membrane's usage events are cumulative, so
134
+ * this is the call's share of its event (cumulativeDelta): the whole event
135
+ * for a stream's first call, and the event's own value where a counter
136
+ * went back. THE window-shaped number — `lastStreamInputTokens` alone
137
+ * omits cached tokens, which are most of a warm stream's window. 0 until a
138
+ * call on this stream reports a prompt, and the physical-window
139
+ * projection runs only once one has. */
127
140
  lastStreamRealInputTokens = 0;
128
- /** Output tokens of the last usage event — the prior round's generated
129
- * thinking/text/tool_use, which becomes part of the NEXT round's input
130
- * and so belongs in the physical-window projection. */
141
+ /** Output tokens of the same call as lastStreamRealInputTokens, or
142
+ * maxTokens, the bound on them, when it reported none (a call that ends at
143
+ * a tool boundary wrote a tool_use, so a 0 is a count left unreported) —
144
+ * the prior round's generated thinking/text/tool_use, which becomes part
145
+ * of the NEXT round's input and so belongs in the physical-window
146
+ * projection. */
131
147
  lastStreamOutputTokens = 0;
148
+ /** What the stream has grown by since the call lastStreamRealInputTokens
149
+ * describes, for the physical-window projection: the blocks appended at
150
+ * each boundary the stream continued past (counted only for an agent with
151
+ * physicalWindowTokens, whose projection is this field's one reader), and
152
+ * the output of each call that reported no prompt (its reported output,
153
+ * or maxTokens, the bound on it, when it reported none either). After
154
+ * calls that report no prompt, the next request holds the prompt and
155
+ * output of the call lastStreamRealInputTokens describes, plus this. 0 at
156
+ * stream start and whenever a call reports a prompt. */
157
+ lastStreamUnreportedTokens = 0;
132
158
  maxStreamTokens: number;
133
159
  /** Provider hard context cap (see AgentConfig.physicalWindowTokens). */
134
160
  readonly physicalWindowTokens?: number;
@@ -141,7 +167,52 @@ export class Agent {
141
167
  private readonly configuredTransitionPaceTokens?: number;
142
168
  private readonly configuredSameRoundThinkTextPolicy?: SameRoundThinkTextPolicy;
143
169
  private contextBudgetTargetTokens?: number;
144
- private runtimeSettingsOverrides: AgentRuntimeSettingsOverrides = {};
170
+ private runtimeSettingsOverrides: AgentRuntimeSettingsPatch = {};
171
+ /**
172
+ * Counts changes of the live budget (an update, a reset, a restore, a
173
+ * settle). A compile speaks for the budget it planned at only if the live
174
+ * budget didn't change while it ran, and what it showed stands only until
175
+ * the next change.
176
+ */
177
+ private liveBudgetEpoch = 0;
178
+ /**
179
+ * Counts changes of the descent (each arm of one, the same target included,
180
+ * a reset, a cancel, a settle). A settle proves its target only to a
181
+ * compile that began under that same descent.
182
+ */
183
+ private descentEpoch = 0;
184
+ /** The live budget the last compile planned at, whether it fit, and the live budget's epoch then. */
185
+ private lastCompiledBudget?: { budget: number; fit: boolean; epoch: number };
186
+ /** Counts compiles at the live budget as they start. */
187
+ private compilesStarted = 0;
188
+ /** The start numbers of the compiles at the live budget still running. */
189
+ private readonly compilesRunning = new Set<number>();
190
+ /**
191
+ * For each budget, the start of the latest-started compile that has ended
192
+ * there with a fit or a refusal, whether or not what it showed was
193
+ * credited. Of two compiles at one budget, the later-started one planned
194
+ * at the newer store, so an earlier-started one that ends after it is
195
+ * ignored. That rests on the context manager selecting at the start of
196
+ * `compile`, before anything it awaits, so start order is selection order;
197
+ * a context manager that awaits before its select breaks it. (Injections
198
+ * are added after the select, so compiles with and without them plan
199
+ * alike.) An entry is
200
+ * kept only while a compile that started before it is still running, the
201
+ * only kind it can turn away, so the map is empty whenever no compile
202
+ * runs.
203
+ */
204
+ private readonly latestEndedStart = new Map<number, number>();
205
+ /** The last outcome the listener recorded; a failed record is told again, and so is any after a change. */
206
+ private recordedCompiledBudget?: { budget: number; fit: boolean; epoch: number };
207
+ /**
208
+ * A boot restore applied a proven budget below the live one at once. Until
209
+ * the budget is next written (an update that carries one, or a reset of
210
+ * it) or a compile at it fits, a refusal of a compile at it restores it
211
+ * paced instead.
212
+ */
213
+ private restoredAtOnce = false;
214
+ /** Told when that changes (the framework records it beside the persisted budget). */
215
+ private compiledBudgetListener?: (budget: number, fit: boolean) => boolean;
145
216
  private contextManager: ContextManager;
146
217
  private membrane: Membrane;
147
218
 
@@ -280,10 +351,16 @@ export class Agent {
280
351
  getRuntimeSettings(): AgentRuntimeSettingsSnapshot {
281
352
  const hot = this.getHotContextSettings();
282
353
  return {
354
+ // The budget it's set to: the override, where there is one. That is
355
+ // the target of a descent toward it, or else the live budget, except
356
+ // while it's kept unapplied: at boot (keepRuntimeSettings), or after a
357
+ // refused at-once budget couldn't be restored paced (compileAtLiveBudget).
283
358
  contextBudgetTokens:
359
+ this.runtimeSettingsOverrides.contextBudgetTokens ??
284
360
  this.contextBudgetTargetTokens ??
285
361
  this.contextBudgetTokens ??
286
362
  DEFAULT_CONTEXT_BUDGET_TOKENS,
363
+ effectiveContextBudgetTokens: this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS,
287
364
  ...(hot ? { tailTokens: hot.tailTokens } : {}),
288
365
  ...(hot?.transitionPaceTokens !== undefined
289
366
  ? { transitionPaceTokens: hot.transitionPaceTokens }
@@ -307,6 +384,179 @@ export class Agent {
307
384
  return { ...this.runtimeSettingsOverrides };
308
385
  }
309
386
 
387
+ /**
388
+ * Set (or clear) the one listener told when a compile at the live budget
389
+ * comes out differently from the last: it fit at a budget the last one
390
+ * didn't plan at (after a settle, the target), or the context manager
391
+ * refused a budget the last one fit at.
392
+ */
393
+ onContextBudgetCompiled(listener: ((budget: number, fit: boolean) => boolean) | undefined): void {
394
+ this.compiledBudgetListener = listener;
395
+ }
396
+
397
+ /**
398
+ * The live budget the last compile planned at and fit, if it fit and the
399
+ * live budget hasn't changed since.
400
+ */
401
+ getProvenContextBudgetTokens(): number | undefined {
402
+ const last = this.lastCompiledBudget;
403
+ return last?.fit && last.epoch === this.liveBudgetEpoch ? last.budget : undefined;
404
+ }
405
+
406
+ /**
407
+ * Set the live budget and the descent target, counting a change of each.
408
+ * `armed` counts a descent armed here as a change even when its target is
409
+ * the one already set: a compile that began under the earlier arm isn't
410
+ * credited with this one's settle.
411
+ */
412
+ private setContextBudget(budget: number | undefined, target: number | undefined, armed = false): void {
413
+ if (budget !== this.contextBudgetTokens) this.liveBudgetEpoch++;
414
+ if (armed || target !== this.contextBudgetTargetTokens) this.descentEpoch++;
415
+ this.contextBudgetTokens = budget;
416
+ this.contextBudgetTargetTokens = target;
417
+ }
418
+
419
+ /**
420
+ * Claim the place of the latest compile to end at `budget` for the one that
421
+ * started as the `started`th, and say whether it got it: a compile that
422
+ * started before one that has already ended there is ignored, whichever
423
+ * finishes last.
424
+ */
425
+ private claimLatestAt(budget: number, started: number): boolean {
426
+ if (started < (this.latestEndedStart.get(budget) ?? 0)) return false;
427
+ this.latestEndedStart.set(budget, started);
428
+ return true;
429
+ }
430
+
431
+ /**
432
+ * Note what a compile at `budget`, planned at the live budget's `epoch`,
433
+ * showed. It speaks for the live budget only if that hasn't changed since;
434
+ * the listener is told either way, so a refusal withdraws the record's
435
+ * marker for the budget it planned at whatever changed meanwhile.
436
+ */
437
+ private noteCompiledBudget(budget: number, fit: boolean, epoch: number): void {
438
+ if (epoch === this.liveBudgetEpoch) this.lastCompiledBudget = { budget, fit, epoch };
439
+ const recorded = this.recordedCompiledBudget;
440
+ if (recorded?.budget === budget && recorded.fit === fit && recorded.epoch === epoch) return;
441
+ // A listener that couldn't record it returns false: nothing is assumed
442
+ // recorded then, so the next compile tells it again whatever it shows.
443
+ this.recordedCompiledBudget = this.compiledBudgetListener?.(budget, fit) !== false ? { budget, fit, epoch } : undefined;
444
+ }
445
+
446
+ /** Compile at the live budget (no explicit one), noting whether it fit. */
447
+ private async compileAtLiveBudget(compile: () => Promise<CompileResult>): Promise<CompileResult> {
448
+ const started = ++this.compilesStarted;
449
+ this.compilesRunning.add(started);
450
+ try {
451
+ return await this.compileAtLiveBudgetNoting(compile, started);
452
+ } finally {
453
+ this.compilesRunning.delete(started);
454
+ const earliest = Math.min(...this.compilesRunning);
455
+ for (const [budget, start] of this.latestEndedStart) {
456
+ if (start < earliest) this.latestEndedStart.delete(budget);
457
+ }
458
+ }
459
+ }
460
+
461
+ /** The compile at the live budget that started as the `started`th, and what it showed. */
462
+ private async compileAtLiveBudgetNoting(compile: () => Promise<CompileResult>, started: number): Promise<CompileResult> {
463
+ // The budget this compile plans at, and the descent under way when it
464
+ // started: a change of either while it runs is no part of what it shows.
465
+ const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
466
+ const epoch = this.liveBudgetEpoch;
467
+ const descent = this.descentEpoch;
468
+ let result: CompileResult;
469
+ try {
470
+ result = await compile();
471
+ } catch (error) {
472
+ if ((error as { name?: unknown } | null)?.name !== 'OverBudgetError') throw error;
473
+ // A refusal shows the budget it planned at doesn't fit, whatever
474
+ // changed while it ran: the record's marker for it is withdrawn, and
475
+ // nothing is credited, unless a compile that started later has already
476
+ // ended at that budget.
477
+ if (this.claimLatestAt(live, started)) this.noteCompiledBudget(live, false, epoch);
478
+ // A budget a boot restore applied at once, refused at its first test,
479
+ // before anything wrote the budget: restore it paced instead, as the
480
+ // boot would have without its proof, and compile again from the
481
+ // recipe's budget. (While restoredAtOnce holds, the live budget is the
482
+ // one the boot applied: every write of the budget clears it, and no
483
+ // compile runs before the boot's restore.)
484
+ if (!this.restoredAtOnce) throw error;
485
+ this.restoredAtOnce = false;
486
+ // The persisted budget stays in the overrides, so if it can't be
487
+ // restored paced, no later write erases it.
488
+ this.setContextBudget(this.configuredContextBudgetTokens, undefined);
489
+ let outcome: string;
490
+ try {
491
+ this.updateRuntimeSettings({ contextBudgetTokens: live });
492
+ outcome = 'restored it as a paced descent from the recipe\'s budget';
493
+ } catch (fallback) {
494
+ outcome = `couldn't restore it paced (${fallback instanceof Error ? fallback.message : String(fallback)}), ` +
495
+ `so it compiles at the recipe's budget, with contextBudgetTokens=${live} kept set`;
496
+ }
497
+ console.error(
498
+ `[agent-settings] ${this.name}: the context manager refused contextBudgetTokens=${live}, which the boot ` +
499
+ `applied at once; ${outcome}`,
500
+ );
501
+ return this.compileAtLiveBudget(compile);
502
+ }
503
+ this.restoredAtOnce = false;
504
+ // A settle, here, of the descent this compile began under proves its
505
+ // target: the frontier fits the window prepared for it. Without a settle,
506
+ // the compile proves the budget it planned at. Either only if the live
507
+ // budget didn't change while it ran; a settle of a descent armed while it
508
+ // ran proves nothing (the next compile at the target does).
509
+ // A compile that ends here with a fit takes its place in the start order
510
+ // at the budget it planned at, credited or not; a settle credits its
511
+ // target only from the latest compile to end there.
512
+ const latest = this.claimLatestAt(live, started);
513
+ const sameLive = this.liveBudgetEpoch === epoch;
514
+ const sameDescent = this.descentEpoch === descent;
515
+ const settled = this.settleRuntimeSettingsTransition();
516
+ if (latest && sameLive && settled && sameDescent && this.claimLatestAt(this.contextBudgetTokens!, started)) {
517
+ this.noteCompiledBudget(this.contextBudgetTokens!, true, this.liveBudgetEpoch);
518
+ } else if (latest && sameLive && !settled) this.noteCompiledBudget(live, true, epoch);
519
+ return result;
520
+ }
521
+
522
+ /**
523
+ * The budget compiles plan at, the path a patch's budget takes from it, and
524
+ * whether a descent is in flight, from the agent's own state alone: what a
525
+ * runtime-settings preview can still say while the context manager refuses
526
+ * its queries.
527
+ * @internal For the framework's runtime-settings preview; not a host API.
528
+ */
529
+ runtimeBudgetPath(patch?: AgentRuntimeSettingsPatch): {
530
+ liveBudgetTokens: number;
531
+ path: 'immediate' | 'paced' | 'none';
532
+ transition: 'stable' | 'converging';
533
+ } {
534
+ const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
535
+ const budget = patch?.contextBudgetTokens;
536
+ return {
537
+ liveBudgetTokens: live,
538
+ path: budget === undefined ? 'none' : budget >= live || patch?.immediate ? 'immediate' : 'paced',
539
+ transition: this.contextBudgetTargetTokens === undefined ? 'stable' : 'converging',
540
+ };
541
+ }
542
+
543
+ /**
544
+ * Whether the context manager answers its queries, as its readiness says.
545
+ * It refuses them while its strategy's initialization is pending or has
546
+ * failed, until a switchBranch or setStrategy succeeds, and a strategy can
547
+ * refuse its readiness on its own, as the autobiographical one does for a
548
+ * branch it hasn't reloaded.
549
+ * @internal For the framework's runtime-settings preview; not a host API.
550
+ */
551
+ contextManagerAnswers(): boolean {
552
+ try {
553
+ this.contextManager.isReady();
554
+ return true;
555
+ } catch {
556
+ return false;
557
+ }
558
+ }
559
+
310
560
  /**
311
561
  * Read-only mirror of `updateRuntimeSettings`' budget semantics: derive what
312
562
  * the next compile would actually plan at if `patch` were applied, WITHOUT
@@ -323,11 +573,17 @@ export class Agent {
323
573
  * - `path: 'none'` — patch absent or budget untouched.
324
574
  *
325
575
  * `overrides` uses strategy-config names (`recentWindowTokens`,
326
- * `kvStableReachTokens`) suitable for `previewContext`. `preparedWindowTokens`
327
- * is deliberately never mapped: it is strategy instance state, not config,
328
- * and cannot be simulated by a preview. A `kvStableReachTokens` override is
329
- * omitted while a prepared window is in flight — the live runtime pace
330
- * shadows the config value there, so the override would not be observed.
576
+ * `kvStableReachTokens`) suitable for `previewContext`. The reach is the one
577
+ * the next compile plans at (context-manager's kv-stable picker): a
578
+ * descent's pace while a window is prepared for it, one this patch arms (a
579
+ * paced decrease) or one in flight that it leaves alone (a patch without a
580
+ * budget), and the configured reach otherwise. Config is a preview's only
581
+ * lever: `preparedWindowTokens` and the live runtime pace are strategy
582
+ * instance state, which a preview can't set. So a preview taken during a
583
+ * descent plans under that descent's prepared window, and at its live pace
584
+ * whenever the strategy carries one, which the picker reads before the
585
+ * configured reach. A boot restore's preflight never meets this: a new
586
+ * strategy has no window.
331
587
  */
332
588
  planRuntimeSettings(patch?: AgentRuntimeSettingsPatch): {
333
589
  effectiveBudgetTokens: number;
@@ -343,34 +599,33 @@ export class Agent {
343
599
  this.validateRuntimeSettingsPatch(patch);
344
600
  }
345
601
  const hot = this.getHotContextSettings();
346
- const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
602
+ const { liveBudgetTokens: live, path } = this.runtimeBudgetPath(patch);
603
+ const budget = patch?.contextBudgetTokens;
347
604
 
348
605
  const overrides: Record<string, unknown> = {};
349
606
  if (patch?.tailTokens !== undefined) {
350
607
  overrides.recentWindowTokens = patch.tailTokens;
351
608
  }
352
- if (
353
- patch?.transitionPaceTokens !== undefined &&
354
- hot?.preparedWindowTokens === undefined
355
- ) {
356
- overrides.kvStableReachTokens = patch.transitionPaceTokens;
609
+ // A window is prepared at the next compile when this patch arms a
610
+ // descent, or leaves one in flight alone; an immediate change clears it.
611
+ const prepared = hot !== null && (path === 'paced' || (path === 'none' && hot.preparedWindowTokens !== undefined));
612
+ const reach = prepared
613
+ ? patch?.transitionPaceTokens ?? hot.transitionPaceTokens ?? DEFAULT_TRANSITION_PACE_TOKENS
614
+ : this.configuredTransitionPaceTokens;
615
+ if (reach !== undefined && reach !== this.configuredTransitionPaceTokens) {
616
+ overrides.kvStableReachTokens = reach;
357
617
  }
358
618
 
359
- if (patch?.contextBudgetTokens === undefined) {
619
+ if (budget === undefined) {
360
620
  return { effectiveBudgetTokens: live, liveBudgetTokens: live, path: 'none', overrides };
361
621
  }
362
- if (patch.contextBudgetTokens >= live || patch.immediate) {
363
- return {
364
- effectiveBudgetTokens: patch.contextBudgetTokens,
365
- liveBudgetTokens: live,
366
- path: 'immediate',
367
- overrides,
368
- };
622
+ if (path === 'immediate') {
623
+ return { effectiveBudgetTokens: budget, liveBudgetTokens: live, path, overrides };
369
624
  }
370
625
  return {
371
626
  effectiveBudgetTokens: live,
372
627
  liveBudgetTokens: live,
373
- advisoryTargetTokens: patch.contextBudgetTokens,
628
+ advisoryTargetTokens: budget,
374
629
  path: 'paced',
375
630
  overrides,
376
631
  };
@@ -397,6 +652,8 @@ export class Agent {
397
652
  let nextContextBudget = this.contextBudgetTokens;
398
653
  let nextContextTarget = this.contextBudgetTargetTokens;
399
654
  let appliedDefaultPace = false;
655
+ // A paced decrease arms a descent, even to the target already set.
656
+ let armed = false;
400
657
  const hotPatch: {
401
658
  tailTokens?: number;
402
659
  transitionPaceTokens?: number | null;
@@ -428,21 +685,24 @@ export class Agent {
428
685
  }
429
686
  hotPatch.preparedWindowTokens = patch.contextBudgetTokens - this.maxTokens;
430
687
  nextContextTarget = patch.contextBudgetTokens;
688
+ armed = true;
431
689
  }
432
690
  }
433
691
 
434
692
  if (Object.keys(hotPatch).length > 0) {
435
693
  this.updateHotContextSettings(hotPatch);
436
694
  }
437
- this.contextBudgetTokens = nextContextBudget;
438
- this.contextBudgetTargetTokens = nextContextTarget;
695
+ this.setContextBudget(nextContextBudget, nextContextTarget, armed);
696
+ // A budget written here is a decision about it, whatever its value: the
697
+ // boot's at-once fallback (restoredAtOnce) no longer speaks for it.
698
+ if (patch.contextBudgetTokens !== undefined) this.restoredAtOnce = false;
439
699
  if (appliedDefaultPace) {
440
700
  this.runtimeSettingsOverrides.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
441
701
  }
442
- for (const [key, value] of Object.entries(patch)) {
443
- if (key === 'sameRoundThinkTextPolicy') continue;
444
- if (key === 'immediate') continue; // a mode for this apply, never a persisted setting
445
- if (value !== undefined) (this.runtimeSettingsOverrides as Record<string, unknown>)[key] = value;
702
+ // Only settings reach the override map: `immediate` is a mode for this
703
+ // apply, and nothing else a caller passes is a setting.
704
+ for (const key of ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens'] as const) {
705
+ if (patch[key] !== undefined) this.runtimeSettingsOverrides[key] = patch[key];
446
706
  }
447
707
  if (patch.sameRoundThinkTextPolicy !== undefined) {
448
708
  this.runtimeSettingsOverrides.sameRoundThinkTextPolicy = patch.sameRoundThinkTextPolicy;
@@ -454,6 +714,8 @@ export class Agent {
454
714
  const reset = new Set(keys ?? ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens', 'sameRoundThinkTextPolicy']);
455
715
  let nextContextBudget = this.contextBudgetTokens;
456
716
  let nextContextTarget = this.contextBudgetTargetTokens;
717
+ // A reset to a recipe budget below the live one arms a descent to it.
718
+ let armed = false;
457
719
  const hotPatch: {
458
720
  tailTokens?: number;
459
721
  transitionPaceTokens?: number | null;
@@ -471,6 +733,7 @@ export class Agent {
471
733
  hotPatch.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
472
734
  }
473
735
  nextContextTarget = configured;
736
+ armed = true;
474
737
  } else {
475
738
  nextContextBudget = this.configuredContextBudgetTokens;
476
739
  nextContextTarget = undefined;
@@ -487,9 +750,11 @@ export class Agent {
487
750
  : this.configuredTransitionPaceTokens ?? null;
488
751
  }
489
752
  if (Object.keys(hotPatch).length > 0) this.updateHotContextSettings(hotPatch);
490
- this.contextBudgetTokens = nextContextBudget;
491
- this.contextBudgetTargetTokens = nextContextTarget;
492
- if (reset.has('contextBudgetTokens')) delete this.runtimeSettingsOverrides.contextBudgetTokens;
753
+ this.setContextBudget(nextContextBudget, nextContextTarget, armed);
754
+ if (reset.has('contextBudgetTokens')) {
755
+ delete this.runtimeSettingsOverrides.contextBudgetTokens;
756
+ this.restoredAtOnce = false;
757
+ }
493
758
  if (reset.has('tailTokens')) delete this.runtimeSettingsOverrides.tailTokens;
494
759
  if (reset.has('transitionPaceTokens')) {
495
760
  delete this.runtimeSettingsOverrides.transitionPaceTokens;
@@ -503,7 +768,7 @@ export class Agent {
503
768
  cancelRuntimeSettingsTransition(): AgentRuntimeSettingsSnapshot {
504
769
  if (this.contextBudgetTargetTokens !== undefined) {
505
770
  this.updateHotContextSettings({ preparedWindowTokens: null });
506
- this.contextBudgetTargetTokens = undefined;
771
+ this.setContextBudget(this.contextBudgetTokens, undefined);
507
772
  const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
508
773
  if (live === (this.configuredContextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS)) {
509
774
  delete this.runtimeSettingsOverrides.contextBudgetTokens;
@@ -514,11 +779,56 @@ export class Agent {
514
779
  return this.getRuntimeSettings();
515
780
  }
516
781
 
517
- restoreRuntimeSettings(overrides: AgentRuntimeSettingsOverrides): AgentRuntimeSettingsSnapshot {
518
- return this.updateRuntimeSettings(overrides);
782
+ /**
783
+ * Apply persisted settings at construction. `provenBudget` is a budget the
784
+ * caller has shown fits (the framework, from the record and a preflight),
785
+ * applied at once: it stands as the last compile's until a compile here
786
+ * says otherwise. When it's below the live budget, where the proof is what
787
+ * took the restore off the paced path, a refusal of a compile at it
788
+ * restores it paced instead, until the budget is next written or a compile
789
+ * at it fits. At or above the live budget, the restore is at once either
790
+ * way, and a refusal has nothing to fall back to.
791
+ */
792
+ restoreRuntimeSettings(
793
+ settings: AgentRuntimeSettingsPatch,
794
+ opts?: { provenBudget?: number },
795
+ ): AgentRuntimeSettingsSnapshot {
796
+ if (Object.keys(settings).length === 0) return this.getRuntimeSettings();
797
+ const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
798
+ const snapshot = this.updateRuntimeSettings(settings);
799
+ if (opts?.provenBudget !== undefined) {
800
+ this.lastCompiledBudget = { budget: opts.provenBudget, fit: true, epoch: this.liveBudgetEpoch };
801
+ this.restoredAtOnce = opts.provenBudget < live;
802
+ }
803
+ return snapshot;
804
+ }
805
+
806
+ /**
807
+ * Keep a persisted budget, tail or pace that couldn't be applied at
808
+ * construction: it stays in the override map, unapplied, so a later
809
+ * persisting write keeps it in the record. A budget is reported as the one
810
+ * set (`contextBudgetTokens`) beside the one compiles plan at
811
+ * (`effectiveContextBudgetTokens`); a tail or pace stays the strategy's.
812
+ */
813
+ keepRuntimeSettings(settings: Pick<AgentRuntimeSettingsPatch, 'contextBudgetTokens' | 'tailTokens' | 'transitionPaceTokens'>): void {
814
+ for (const key of ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens'] as const) {
815
+ if (settings[key] !== undefined) this.runtimeSettingsOverrides[key] = settings[key];
816
+ }
519
817
  }
520
818
 
521
819
  private validateRuntimeSettingsPatch(patch: AgentRuntimeSettingsPatch): void {
820
+ this.checkRuntimeSettingsPatch(patch);
821
+ if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
822
+ !this.getHotContextSettings()) {
823
+ throw new Error('The active context strategy does not support live tail/transition settings');
824
+ }
825
+ }
826
+
827
+ /**
828
+ * The checks of a runtime-settings patch that need nothing from the context manager.
829
+ * @internal For the framework's runtime-settings preview; not a host API.
830
+ */
831
+ checkRuntimeSettingsPatch(patch: AgentRuntimeSettingsPatch): void {
522
832
  if (Object.keys(patch).length === 0) throw new Error('At least one setting is required');
523
833
  if (patch.contextBudgetTokens !== undefined) {
524
834
  if (!Number.isSafeInteger(patch.contextBudgetTokens) || patch.contextBudgetTokens <= this.maxTokens) {
@@ -538,19 +848,16 @@ export class Agent {
538
848
  patch.sameRoundThinkTextPolicy !== 'private') {
539
849
  throw new Error("sameRoundThinkTextPolicy must be 'public' or 'private'");
540
850
  }
541
- if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
542
- !this.getHotContextSettings()) {
543
- throw new Error('The active context strategy does not support live tail/transition settings');
544
- }
545
851
  }
546
852
 
547
- private settleRuntimeSettingsTransition(): void {
548
- if (this.contextBudgetTargetTokens === undefined) return;
853
+ /** Settle a converging budget once the window is prepared; true if it settled. */
854
+ private settleRuntimeSettingsTransition(): boolean {
855
+ if (this.contextBudgetTargetTokens === undefined) return false;
549
856
  const hot = this.getHotContextSettings();
550
- if (!hot?.prepared) return;
551
- this.contextBudgetTokens = this.contextBudgetTargetTokens;
552
- this.contextBudgetTargetTokens = undefined;
857
+ if (!hot?.prepared) return false;
858
+ this.setContextBudget(this.contextBudgetTargetTokens, undefined);
553
859
  this.updateHotContextSettings({ preparedWindowTokens: null });
860
+ return true;
554
861
  }
555
862
 
556
863
  /**
@@ -591,32 +898,59 @@ export class Agent {
591
898
  }
592
899
  }
593
900
 
901
+ /** A committing compile, as for a request that is sent: the context
902
+ * manager commits what it commits for one, the consumed watermark moves,
903
+ * and a prepared budget change settles. To inspect what the agent would
904
+ * see, use the framework's `previewActivation`, which compiles as a dry
905
+ * run. */
594
906
  async compileContext(budget?: TokenBudget): Promise<CompileResult> {
595
907
  // RFC-006 §3.3: consumption is determined at assembly — a compile that
596
908
  // fails assembled nothing, so the watermark moves only on success.
597
909
  const watermark = this.pendingWatermark();
598
- const result = await this.contextManager.compile(this.compileBudget(budget));
910
+ const compile = () => this.contextManager.compile(this.compileBudget(budget));
911
+ const result = budget ? await compile() : await this.compileAtLiveBudget(compile);
599
912
  if (watermark) this.consumedWatermark = watermark;
600
- if (!budget) this.settleRuntimeSettingsTransition();
601
913
  return result;
602
914
  }
603
915
 
604
916
  /**
605
- * Compile the context with injections.
606
- * Same as compileContext but forwards injections (e.g. from MCPL servers)
607
- * to the context manager so they are merged into the compiled messages.
917
+ * Compile the context with injections (e.g. from MCPL servers), which the
918
+ * context manager merges into the compiled messages. Without
919
+ * `opts.dryRun` it commits as `compileContext` does: the consumed
920
+ * watermark moves, and a compile at the live window settles a prepared
921
+ * budget change. With `opts.dryRun`, for a compile that will never be
922
+ * sent, it does neither (see below).
608
923
  */
609
924
  async compileWithInjections(
610
925
  budget?: TokenBudget,
611
926
  injections?: ContextInjection[],
612
- opts?: { kvUnifiedImmutablePrefixHash?: string },
927
+ opts?: { kvUnifiedImmutablePrefixHash?: string; dryRun?: boolean },
613
928
  ): Promise<CompileResult> {
614
- const watermark = this.pendingWatermark();
615
- const result = await this.contextManager.compile(
616
- this.compileBudget(budget), injections, opts as never,
617
- );
929
+ // A dry run is a compile that will never be sent. The context manager
930
+ // persists no fold resolutions for it and queues none of the compression
931
+ // or merge work its fold plan calls for. Its thinking binding
932
+ // (context-manager#160, not yet released) will commit stamps and add a
933
+ // compile awaiting acceptance for a committing compile only. Here nothing
934
+ // moves either: the agent never read what a dry run assembled, so the
935
+ // consumed watermark stays. A dry run proves nothing about the budget
936
+ // either, so it goes straight to the compile, around
937
+ // compileAtLiveBudget: it settles no descent (a prepared budget change
938
+ // waits for the compile that is sent), notes no compiled budget, and
939
+ // restores nothing the boot applied at once, whether it fits or is
940
+ // refused.
941
+ //
942
+ // Context-manager 0.14 leaves what a select leaves behind as the last
943
+ // committing compile left it, on every path out of a dry run, throws
944
+ // included: no chunk record and no compression queued for a chunk the
945
+ // dry run closes, no calibration, and none of the bookkeeping the next
946
+ // compile or an observer reads (its `SelectOptions.dryRun` gives the
947
+ // contract). The one thing a dry run can still leave is the release of a
948
+ // compression hold whose deadline has passed, which `compile` does for
949
+ // any compile before it selects.
950
+ const watermark = opts?.dryRun ? null : this.pendingWatermark();
951
+ const compile = () => this.contextManager.compile(this.compileBudget(budget), injections, opts as never);
952
+ const result = budget || opts?.dryRun ? await compile() : await this.compileAtLiveBudget(compile);
618
953
  if (watermark) this.consumedWatermark = watermark;
619
- if (!budget) this.settleRuntimeSettingsTransition();
620
954
  return result;
621
955
  }
622
956
 
@@ -801,10 +1135,13 @@ export class Agent {
801
1135
  *
802
1136
  * This is the single source of truth for request assembly, shared by the
803
1137
  * real activation path (`startStreamWithInjections`) and debug/preview
804
- * tooling (`Framework.previewActivation`). It is pure and non-mutating:
805
- * it does not touch agent state and does not call the membrane, and
806
- * `ContextManager.compile` is itself side-effect-free (compression runs in
807
- * the background). Safe to call regardless of the agent's current status.
1138
+ * tooling (`Framework.previewActivation`). It does not call the membrane.
1139
+ * A compile that is sent commits what the context manager commits for it
1140
+ * (fold resolutions, the compression its fold plan calls for, the consumed
1141
+ * watermark); a preview passes `options.dryRun`, so its compile commits
1142
+ * none of that (see `compileWithInjections` for what a dry run still
1143
+ * leaves). Either way the context manager is handed the compression tool
1144
+ * definitions. Safe to call regardless of the agent's current status.
808
1145
  * It doesn't wait for the tool-result guard's annotations, so a preview
809
1146
  * never waits on a workspace write (rollback, undo and redo report one), and
810
1147
  * may show a withheld stub before its place is added; the activation waits
@@ -845,7 +1182,12 @@ export class Agent {
845
1182
  let { messages, systemInjections } = await this.compileWithInjections(
846
1183
  budget,
847
1184
  injections,
848
- immutablePrefixHash ? { kvUnifiedImmutablePrefixHash: immutablePrefixHash } : undefined,
1185
+ immutablePrefixHash || options.dryRun
1186
+ ? {
1187
+ ...(immutablePrefixHash ? { kvUnifiedImmutablePrefixHash: immutablePrefixHash } : {}),
1188
+ ...(options.dryRun ? { dryRun: true } : {}),
1189
+ }
1190
+ : undefined,
849
1191
  );
850
1192
 
851
1193
  // Sanitize: strip empty/whitespace text blocks and drop messages left with
@@ -972,10 +1314,13 @@ export class Agent {
972
1314
  // the prior stream's window size — on the physical-window restart path
973
1315
  // that inherited value IS the oversized number that caused the restart,
974
1316
  // and projecting on it before this stream's first usage event would
975
- // restart again (loop). The `> 0` guard downstream means "no usage
976
- // observed on THIS stream yet" only because of this reset.
1317
+ // restart again (loop). The `> 0` guard downstream means "no call on
1318
+ // THIS stream has reported a prompt yet" only because of this reset (a
1319
+ // call that reports none leaves these as they were, and adds to
1320
+ // lastStreamUnreportedTokens instead).
977
1321
  this.lastStreamRealInputTokens = 0;
978
1322
  this.lastStreamOutputTokens = 0;
1323
+ this.lastStreamUnreportedTokens = 0;
979
1324
 
980
1325
  // Compiled only once each withheld stub's annotation has finished,
981
1326
  // including one that began during the compile, and after a stub edit that