@animalabs/agent-framework 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/README.md +340 -22
  2. package/dist/src/admission/admit.d.ts +18 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -1
  4. package/dist/src/admission/admit.js +16 -3
  5. package/dist/src/admission/admit.js.map +1 -1
  6. package/dist/src/agent.d.ts +208 -80
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +345 -113
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  11. package/dist/src/api/mcp-server-core.js +50 -12
  12. package/dist/src/api/mcp-server-core.js.map +1 -1
  13. package/dist/src/api/server.d.ts.map +1 -1
  14. package/dist/src/api/server.js +4 -20
  15. package/dist/src/api/server.js.map +1 -1
  16. package/dist/src/append-all.d.ts +5 -0
  17. package/dist/src/append-all.d.ts.map +1 -0
  18. package/dist/src/append-all.js +8 -0
  19. package/dist/src/append-all.js.map +1 -0
  20. package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
  21. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  22. package/dist/src/code-execution/isolate/scripts.js +24 -0
  23. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  24. package/dist/src/config-checks.d.ts +7 -0
  25. package/dist/src/config-checks.d.ts.map +1 -1
  26. package/dist/src/config-checks.js +14 -0
  27. package/dist/src/config-checks.js.map +1 -1
  28. package/dist/src/context-manager-errors.d.ts +25 -0
  29. package/dist/src/context-manager-errors.d.ts.map +1 -0
  30. package/dist/src/context-manager-errors.js +31 -0
  31. package/dist/src/context-manager-errors.js.map +1 -0
  32. package/dist/src/context-options.d.ts +70 -0
  33. package/dist/src/context-options.d.ts.map +1 -0
  34. package/dist/src/context-options.js +90 -0
  35. package/dist/src/context-options.js.map +1 -0
  36. package/dist/src/cut-off-round.js +4 -4
  37. package/dist/src/deferred-import.d.ts +93 -0
  38. package/dist/src/deferred-import.d.ts.map +1 -0
  39. package/dist/src/deferred-import.js +177 -0
  40. package/dist/src/deferred-import.js.map +1 -0
  41. package/dist/src/dendrite/audience.d.ts +223 -0
  42. package/dist/src/dendrite/audience.d.ts.map +1 -0
  43. package/dist/src/dendrite/audience.js +202 -0
  44. package/dist/src/dendrite/audience.js.map +1 -0
  45. package/dist/src/dendrite/index.d.ts +5 -3
  46. package/dist/src/dendrite/index.d.ts.map +1 -1
  47. package/dist/src/dendrite/index.js +2 -1
  48. package/dist/src/dendrite/index.js.map +1 -1
  49. package/dist/src/dendrite/presets.d.ts +10 -5
  50. package/dist/src/dendrite/presets.d.ts.map +1 -1
  51. package/dist/src/dendrite/presets.js +17 -5
  52. package/dist/src/dendrite/presets.js.map +1 -1
  53. package/dist/src/dendrite/registry.d.ts +62 -7
  54. package/dist/src/dendrite/registry.d.ts.map +1 -1
  55. package/dist/src/dendrite/registry.js +183 -12
  56. package/dist/src/dendrite/registry.js.map +1 -1
  57. package/dist/src/dendrite/types.d.ts +91 -8
  58. package/dist/src/dendrite/types.d.ts.map +1 -1
  59. package/dist/src/failure-text.d.ts +36 -0
  60. package/dist/src/failure-text.d.ts.map +1 -0
  61. package/dist/src/failure-text.js +89 -0
  62. package/dist/src/failure-text.js.map +1 -0
  63. package/dist/src/foreign-rounds.d.ts +69 -0
  64. package/dist/src/foreign-rounds.d.ts.map +1 -0
  65. package/dist/src/foreign-rounds.js +152 -0
  66. package/dist/src/foreign-rounds.js.map +1 -0
  67. package/dist/src/framework.d.ts +2064 -842
  68. package/dist/src/framework.d.ts.map +1 -1
  69. package/dist/src/framework.js +8586 -3739
  70. package/dist/src/framework.js.map +1 -1
  71. package/dist/src/gate/event-gate.d.ts +125 -21
  72. package/dist/src/gate/event-gate.d.ts.map +1 -1
  73. package/dist/src/gate/event-gate.js +487 -72
  74. package/dist/src/gate/event-gate.js.map +1 -1
  75. package/dist/src/gate/index.d.ts +1 -1
  76. package/dist/src/gate/index.d.ts.map +1 -1
  77. package/dist/src/gate/index.js.map +1 -1
  78. package/dist/src/gate/reply-judge.d.ts +4 -0
  79. package/dist/src/gate/reply-judge.d.ts.map +1 -1
  80. package/dist/src/gate/reply-judge.js +12 -3
  81. package/dist/src/gate/reply-judge.js.map +1 -1
  82. package/dist/src/gate/rule-scope.d.ts +30 -0
  83. package/dist/src/gate/rule-scope.d.ts.map +1 -0
  84. package/dist/src/gate/rule-scope.js +41 -0
  85. package/dist/src/gate/rule-scope.js.map +1 -0
  86. package/dist/src/gate/suppress-script-worker.d.ts +2 -0
  87. package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
  88. package/dist/src/gate/suppress-script-worker.js +103 -0
  89. package/dist/src/gate/suppress-script-worker.js.map +1 -0
  90. package/dist/src/gate/suppress-scripts.d.ts +105 -0
  91. package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
  92. package/dist/src/gate/suppress-scripts.js +403 -0
  93. package/dist/src/gate/suppress-scripts.js.map +1 -0
  94. package/dist/src/gate/suppression-review.d.ts +143 -0
  95. package/dist/src/gate/suppression-review.d.ts.map +1 -0
  96. package/dist/src/gate/suppression-review.js +463 -0
  97. package/dist/src/gate/suppression-review.js.map +1 -0
  98. package/dist/src/gate/suppression-stats.d.ts +125 -0
  99. package/dist/src/gate/suppression-stats.d.ts.map +1 -0
  100. package/dist/src/gate/suppression-stats.js +214 -0
  101. package/dist/src/gate/suppression-stats.js.map +1 -0
  102. package/dist/src/gate/types.d.ts +142 -7
  103. package/dist/src/gate/types.d.ts.map +1 -1
  104. package/dist/src/gate/wake-onboarding.d.ts +119 -0
  105. package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
  106. package/dist/src/gate/wake-onboarding.js +277 -0
  107. package/dist/src/gate/wake-onboarding.js.map +1 -0
  108. package/dist/src/heartbeat/heartbeat.js +1 -1
  109. package/dist/src/heartbeat/heartbeat.js.map +1 -1
  110. package/dist/src/held-writes.d.ts +31 -23
  111. package/dist/src/held-writes.d.ts.map +1 -1
  112. package/dist/src/held-writes.js +44 -59
  113. package/dist/src/held-writes.js.map +1 -1
  114. package/dist/src/index.d.ts +6 -2
  115. package/dist/src/index.d.ts.map +1 -1
  116. package/dist/src/index.js +4 -0
  117. package/dist/src/index.js.map +1 -1
  118. package/dist/src/inference-bounds.d.ts +2 -1
  119. package/dist/src/inference-bounds.d.ts.map +1 -1
  120. package/dist/src/inference-bounds.js +9 -6
  121. package/dist/src/inference-bounds.js.map +1 -1
  122. package/dist/src/jsonrpc-envelope.d.ts +69 -0
  123. package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
  124. package/dist/src/jsonrpc-envelope.js +66 -0
  125. package/dist/src/jsonrpc-envelope.js.map +1 -0
  126. package/dist/src/mcpl/channel-registry.d.ts +188 -28
  127. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  128. package/dist/src/mcpl/channel-registry.js +426 -70
  129. package/dist/src/mcpl/channel-registry.js.map +1 -1
  130. package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
  131. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  132. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  133. package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
  134. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  135. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  136. package/dist/src/mcpl/inbound-source.d.ts +2 -1
  137. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  138. package/dist/src/mcpl/inbound-source.js +2 -1
  139. package/dist/src/mcpl/inbound-source.js.map +1 -1
  140. package/dist/src/mcpl/inference-router.d.ts +6 -6
  141. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  142. package/dist/src/mcpl/inference-router.js.map +1 -1
  143. package/dist/src/mcpl/modern-connection.d.ts +109 -15
  144. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  145. package/dist/src/mcpl/modern-connection.js +593 -86
  146. package/dist/src/mcpl/modern-connection.js.map +1 -1
  147. package/dist/src/mcpl/protocol-family.d.ts +3 -1
  148. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  149. package/dist/src/mcpl/protocol-family.js +15 -9
  150. package/dist/src/mcpl/protocol-family.js.map +1 -1
  151. package/dist/src/mcpl/push-coalescer.d.ts +132 -22
  152. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  153. package/dist/src/mcpl/push-coalescer.js +105 -15
  154. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  155. package/dist/src/mcpl/push-handler.d.ts +9 -7
  156. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  157. package/dist/src/mcpl/push-handler.js.map +1 -1
  158. package/dist/src/mcpl/server-connection.d.ts +50 -28
  159. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  160. package/dist/src/mcpl/server-connection.js +165 -72
  161. package/dist/src/mcpl/server-connection.js.map +1 -1
  162. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  163. package/dist/src/mcpl/tool-classes.js +3 -0
  164. package/dist/src/mcpl/tool-classes.js.map +1 -1
  165. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  166. package/dist/src/mcpl/tool-presence.js +10 -6
  167. package/dist/src/mcpl/tool-presence.js.map +1 -1
  168. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  169. package/dist/src/mcpl/toolset-host.js +7 -6
  170. package/dist/src/mcpl/toolset-host.js.map +1 -1
  171. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  172. package/dist/src/mcpl/toolsets-module.js +2 -1
  173. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  174. package/dist/src/mcpl/types.d.ts +33 -6
  175. package/dist/src/mcpl/types.d.ts.map +1 -1
  176. package/dist/src/mcpl/types.js.map +1 -1
  177. package/dist/src/module-registry.d.ts +24 -1
  178. package/dist/src/module-registry.d.ts.map +1 -1
  179. package/dist/src/module-registry.js +68 -18
  180. package/dist/src/module-registry.js.map +1 -1
  181. package/dist/src/modules/discord/index.d.ts +45 -8
  182. package/dist/src/modules/discord/index.d.ts.map +1 -1
  183. package/dist/src/modules/discord/index.js +238 -87
  184. package/dist/src/modules/discord/index.js.map +1 -1
  185. package/dist/src/modules/health/index.js +2 -2
  186. package/dist/src/modules/health/index.js.map +1 -1
  187. package/dist/src/modules/history/index.d.ts +59 -0
  188. package/dist/src/modules/history/index.d.ts.map +1 -1
  189. package/dist/src/modules/history/index.js +432 -67
  190. package/dist/src/modules/history/index.js.map +1 -1
  191. package/dist/src/modules/history/semantic.d.ts +6 -5
  192. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  193. package/dist/src/modules/history/semantic.js +18 -25
  194. package/dist/src/modules/history/semantic.js.map +1 -1
  195. package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
  196. package/dist/src/modules/workspace/disk-agreement.js +1 -1
  197. package/dist/src/modules/workspace/index.d.ts +28 -7
  198. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  199. package/dist/src/modules/workspace/index.js +80 -25
  200. package/dist/src/modules/workspace/index.js.map +1 -1
  201. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  202. package/dist/src/modules/workspace/observe.js +2 -1
  203. package/dist/src/modules/workspace/observe.js.map +1 -1
  204. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/reconcile.js +3 -2
  206. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  207. package/dist/src/notice-tags.d.ts +20 -9
  208. package/dist/src/notice-tags.d.ts.map +1 -1
  209. package/dist/src/notice-tags.js +20 -9
  210. package/dist/src/notice-tags.js.map +1 -1
  211. package/dist/src/operator-log.d.ts +36 -3
  212. package/dist/src/operator-log.d.ts.map +1 -1
  213. package/dist/src/operator-log.js +2 -2
  214. package/dist/src/operator-log.js.map +1 -1
  215. package/dist/src/presentation.d.ts +747 -0
  216. package/dist/src/presentation.d.ts.map +1 -0
  217. package/dist/src/presentation.js +1437 -0
  218. package/dist/src/presentation.js.map +1 -0
  219. package/dist/src/prose-drafts.d.ts +15 -11
  220. package/dist/src/prose-drafts.d.ts.map +1 -1
  221. package/dist/src/prose-drafts.js +16 -12
  222. package/dist/src/prose-drafts.js.map +1 -1
  223. package/dist/src/read-marks.d.ts +192 -0
  224. package/dist/src/read-marks.d.ts.map +1 -0
  225. package/dist/src/read-marks.js +242 -0
  226. package/dist/src/read-marks.js.map +1 -0
  227. package/dist/src/reasoning-loss.d.ts +5 -0
  228. package/dist/src/reasoning-loss.d.ts.map +1 -0
  229. package/dist/src/reasoning-loss.js +8 -0
  230. package/dist/src/reasoning-loss.js.map +1 -0
  231. package/dist/src/recovery/offline-branch.d.ts +91 -6
  232. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  233. package/dist/src/recovery/offline-branch.js +277 -32
  234. package/dist/src/recovery/offline-branch.js.map +1 -1
  235. package/dist/src/recovery/recover-cli.js +24 -4
  236. package/dist/src/recovery/recover-cli.js.map +1 -1
  237. package/dist/src/send-tools.d.ts +5 -9
  238. package/dist/src/send-tools.d.ts.map +1 -1
  239. package/dist/src/send-tools.js +40 -7
  240. package/dist/src/send-tools.js.map +1 -1
  241. package/dist/src/sent-rows.js +1 -1
  242. package/dist/src/stored-injections.d.ts +2 -2
  243. package/dist/src/stored-injections.js +2 -2
  244. package/dist/src/stored-removal.d.ts +17 -4
  245. package/dist/src/stored-removal.d.ts.map +1 -1
  246. package/dist/src/stored-removal.js +29 -5
  247. package/dist/src/stored-removal.js.map +1 -1
  248. package/dist/src/supplied-text.d.ts +1 -1
  249. package/dist/src/supplied-text.js +3 -3
  250. package/dist/src/supplied-text.js.map +1 -1
  251. package/dist/src/thinking-drops.d.ts +2 -0
  252. package/dist/src/thinking-drops.d.ts.map +1 -1
  253. package/dist/src/thinking-drops.js.map +1 -1
  254. package/dist/src/tool-presentation.d.ts.map +1 -1
  255. package/dist/src/tool-presentation.js +38 -9
  256. package/dist/src/tool-presentation.js.map +1 -1
  257. package/dist/src/tool-result-guard.d.ts +12 -1
  258. package/dist/src/tool-result-guard.d.ts.map +1 -1
  259. package/dist/src/tool-result-guard.js +42 -8
  260. package/dist/src/tool-result-guard.js.map +1 -1
  261. package/dist/src/tune-out/coordinator.d.ts +1170 -81
  262. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  263. package/dist/src/tune-out/coordinator.js +3732 -369
  264. package/dist/src/tune-out/coordinator.js.map +1 -1
  265. package/dist/src/tune-out/tools.d.ts +114 -17
  266. package/dist/src/tune-out/tools.d.ts.map +1 -1
  267. package/dist/src/tune-out/tools.js +89 -28
  268. package/dist/src/tune-out/tools.js.map +1 -1
  269. package/dist/src/tune-out/window-anchor.d.ts +143 -0
  270. package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
  271. package/dist/src/tune-out/window-anchor.js +202 -0
  272. package/dist/src/tune-out/window-anchor.js.map +1 -0
  273. package/dist/src/types/agent.d.ts +121 -25
  274. package/dist/src/types/agent.d.ts.map +1 -1
  275. package/dist/src/types/events.d.ts +2 -0
  276. package/dist/src/types/events.d.ts.map +1 -1
  277. package/dist/src/types/framework.d.ts +72 -17
  278. package/dist/src/types/framework.d.ts.map +1 -1
  279. package/dist/src/types/inference-log.d.ts +1 -0
  280. package/dist/src/types/inference-log.d.ts.map +1 -1
  281. package/dist/src/types/module.d.ts +180 -69
  282. package/dist/src/types/module.d.ts.map +1 -1
  283. package/dist/src/types/trace.d.ts +573 -39
  284. package/dist/src/types/trace.d.ts.map +1 -1
  285. package/package.json +4 -4
  286. package/src/admission/admit.ts +32 -3
  287. package/src/agent.ts +392 -132
  288. package/src/api/mcp-server-core.ts +54 -17
  289. package/src/api/server.ts +4 -23
  290. package/src/append-all.ts +6 -0
  291. package/src/code-execution/isolate/scripts.ts +28 -3
  292. package/src/config-checks.ts +14 -0
  293. package/src/context-manager-errors.ts +37 -0
  294. package/src/context-options.ts +106 -0
  295. package/src/cut-off-round.ts +4 -4
  296. package/src/deferred-import.ts +239 -0
  297. package/src/dendrite/audience.ts +315 -0
  298. package/src/dendrite/index.ts +14 -2
  299. package/src/dendrite/presets.ts +18 -6
  300. package/src/dendrite/registry.ts +213 -14
  301. package/src/dendrite/types.ts +92 -8
  302. package/src/failure-text.ts +88 -0
  303. package/src/foreign-rounds.ts +199 -0
  304. package/src/framework.ts +8796 -3813
  305. package/src/gate/event-gate.ts +518 -91
  306. package/src/gate/index.ts +3 -0
  307. package/src/gate/reply-judge.ts +12 -3
  308. package/src/gate/rule-scope.ts +45 -0
  309. package/src/gate/suppress-script-worker.ts +89 -0
  310. package/src/gate/suppress-scripts.ts +414 -0
  311. package/src/gate/suppression-review.ts +517 -0
  312. package/src/gate/suppression-stats.ts +314 -0
  313. package/src/gate/types.ts +148 -7
  314. package/src/gate/wake-onboarding.ts +309 -0
  315. package/src/heartbeat/heartbeat.ts +1 -1
  316. package/src/held-writes.ts +49 -60
  317. package/src/index.ts +10 -2
  318. package/src/inference-bounds.ts +11 -6
  319. package/src/jsonrpc-envelope.ts +81 -0
  320. package/src/mcpl/channel-registry.ts +513 -116
  321. package/src/mcpl/checkpoint-manager.ts +3 -2
  322. package/src/mcpl/feature-set-manager.ts +2 -1
  323. package/src/mcpl/inbound-source.ts +2 -1
  324. package/src/mcpl/inference-router.ts +7 -4
  325. package/src/mcpl/modern-connection.ts +629 -91
  326. package/src/mcpl/protocol-family.ts +15 -9
  327. package/src/mcpl/push-coalescer.ts +193 -34
  328. package/src/mcpl/push-handler.ts +10 -5
  329. package/src/mcpl/server-connection.ts +169 -75
  330. package/src/mcpl/tool-classes.ts +3 -0
  331. package/src/mcpl/tool-presence.ts +10 -5
  332. package/src/mcpl/toolset-host.ts +7 -6
  333. package/src/mcpl/toolsets-module.ts +2 -1
  334. package/src/mcpl/types.ts +33 -6
  335. package/src/module-registry.ts +73 -17
  336. package/src/modules/discord/index.ts +230 -82
  337. package/src/modules/health/index.ts +2 -2
  338. package/src/modules/history/index.ts +439 -70
  339. package/src/modules/history/semantic.ts +18 -24
  340. package/src/modules/workspace/disk-agreement.ts +1 -1
  341. package/src/modules/workspace/index.ts +89 -26
  342. package/src/modules/workspace/observe.ts +2 -1
  343. package/src/modules/workspace/reconcile.ts +3 -2
  344. package/src/notice-tags.ts +21 -9
  345. package/src/operator-log.ts +36 -3
  346. package/src/presentation.ts +1678 -0
  347. package/src/prose-drafts.ts +16 -12
  348. package/src/read-marks.ts +328 -0
  349. package/src/reasoning-loss.ts +7 -0
  350. package/src/recovery/offline-branch.ts +350 -35
  351. package/src/recovery/recover-cli.ts +23 -4
  352. package/src/send-tools.ts +40 -7
  353. package/src/sent-rows.ts +1 -1
  354. package/src/stored-injections.ts +2 -2
  355. package/src/stored-removal.ts +31 -7
  356. package/src/supplied-text.ts +3 -3
  357. package/src/thinking-drops.ts +2 -0
  358. package/src/tool-presentation.ts +34 -9
  359. package/src/tool-result-guard.ts +41 -7
  360. package/src/tune-out/coordinator.ts +4121 -472
  361. package/src/tune-out/tools.ts +162 -44
  362. package/src/tune-out/window-anchor.ts +277 -0
  363. package/src/types/agent.ts +122 -25
  364. package/src/types/events.ts +2 -0
  365. package/src/types/framework.ts +73 -17
  366. package/src/types/inference-log.ts +1 -0
  367. package/src/types/module.ts +181 -68
  368. package/src/types/trace.ts +607 -43
@@ -1,6 +1,7 @@
1
1
  import type { ContentBlock } from '@animalabs/membrane';
2
2
  import { authorLine } from '../author-presentation.js';
3
3
  import type { StoredMessage } from '@animalabs/context-manager';
4
+ import { appendAll } from '../append-all.js';
4
5
  /**
5
6
  * The subconscious's tool surface (issue #77).
6
7
  *
@@ -29,10 +30,9 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
29
30
  {
30
31
  name: 'deliver_summary',
31
32
  description:
32
- 'Deliver a summary into the resident\'s context, in your own voice, ' +
33
- 'addressed to them (second person). Use at cadence when the diverted ' +
34
- 'traffic merits it; staying silent is always allowed — an empty ' +
35
- 'period needs no report.',
33
+ 'Deliver a summary into the resident\'s context: `text` arrives there ' +
34
+ 'verbatim (leading and trailing whitespace trimmed), under your own ' +
35
+ 'name.',
36
36
  inputSchema: {
37
37
  type: 'object',
38
38
  properties: {
@@ -51,11 +51,15 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
51
51
  {
52
52
  name: 'cancel_tuneout',
53
53
  description:
54
- 'End the tune-out on a channel now — use when something needs the ' +
55
- 'resident\'s full attention. The diverted backlog is delivered to ' +
56
- 'them (capped), and normal attention resumes. Add your own note in ' +
57
- '`text`; it arrives alongside the backlog, in your voice. If you were ' +
58
- 'started to read one hold, you can end only that hold.',
54
+ 'End the tune-out on a channel. The diverted backlog is delivered ' +
55
+ 'to the resident (capped), and the channel\'s traffic reaches them as ' +
56
+ 'it did before the tune-out, unless the all-except hold holds it too. ' +
57
+ 'A note in `text` arrives alongside the ' +
58
+ 'backlog, verbatim (leading and trailing whitespace trimmed), under ' +
59
+ 'your own name. Where the cancel has to wait (an operator\'s change to ' +
60
+ 'the store, say), its answer says so, and whether your note waits with ' +
61
+ 'it. If you were started to read ' +
62
+ 'one hold, you can end only that hold.',
59
63
  inputSchema: {
60
64
  type: 'object',
61
65
  properties: {
@@ -72,10 +76,10 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
72
76
  {
73
77
  name: 'note_disposition',
74
78
  description:
75
- 'Record a standing disposition for yourself — a durable note that ' +
76
- 'shapes how you treat future traffic ("antra\'s pings always ' +
77
- 'escalate", "ignore release-bot"). Replaces any previous note under ' +
78
- 'the same key.',
79
+ 'Record a standing disposition: a durable note, kept under `key` ' +
80
+ 'across invocations and shown to the reader each time it runs (e.g. ' +
81
+ 'key "<sender>", text "<a note on their messages>"). Replaces any ' +
82
+ 'previous note under the same key.',
79
83
  inputSchema: {
80
84
  type: 'object',
81
85
  properties: {
@@ -91,14 +95,14 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
91
95
  {
92
96
  name: 'speak_in_channel',
93
97
  description:
94
- 'Post a message into a tuned-out channel, in your own words and ' +
95
- 'clearly as yourself (not as the resident); it goes out through the ' +
96
- 'resident\'s publish path, so the channel sees it under their account. ' +
97
- 'Use sparingly — e.g. to tell someone the resident is tuned out and ' +
98
- 'when to expect them. If you were started to read one hold, you can ' +
99
- 'post only into a channel that hold still holds, and a post made once ' +
100
- 'it has ended is refused; your report can say it instead. Disabled unless ' +
101
- 'the resident\'s configuration allows it.',
98
+ 'Post `text` into a tuned-out channel, as written (leading and trailing ' +
99
+ 'whitespace trimmed). It goes out through ' +
100
+ 'the resident\'s publish path, so the channel sees it under the ' +
101
+ 'resident\'s account; nothing in the post names you. If you were ' +
102
+ 'started to read one hold, you can post only into a channel that hold ' +
103
+ 'still holds, and a post made once it has ended is refused; your report ' +
104
+ 'can say it instead. Disabled unless the resident\'s configuration ' +
105
+ 'allows it.',
102
106
  inputSchema: {
103
107
  type: 'object',
104
108
  properties: {
@@ -110,6 +114,77 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
110
114
  },
111
115
  ];
112
116
 
117
+ /**
118
+ * Who reads a tuned-out channel's traffic, as the text a resident reads
119
+ * says it: the persistent subconscious by the name it runs under, or reader
120
+ * forks. There is no hold without a reader (0.33.0) but one whose release
121
+ * failed after the persistent reader ended, or at a start with none
122
+ * configured: none runs then (`running: false`), the reader named as
123
+ * configured, or by its default name, for tune_out's own text, and what a
124
+ * held channel's messages are says that none reads them (tuneOutHeldWords,
125
+ * tunedOutTrafficWords). A persistent reader's name is a value the host
126
+ * supplied, marked as such (suppliedValue).
127
+ */
128
+ export type TuneOutReader =
129
+ | { readonly kind: 'persistent'; readonly name: string; readonly running?: false }
130
+ | { readonly kind: 'forks' };
131
+
132
+ /**
133
+ * What a tuned-out channel's messages are, as the words after "its
134
+ * messages" in a refusal a resident reads (a channel tool's on a tuned-out
135
+ * channel): held from it and read by `reader`, by the name it runs under or
136
+ * as forks of the resident's context.
137
+ */
138
+ export function tuneOutHeldWords(reader: TuneOutReader): string {
139
+ if (reader.kind === 'persistent' && reader.running === false) {
140
+ return "are held from you, with no reader running here to read them, until the tune-out's release, which failed, is tried again";
141
+ }
142
+ return reader.kind === 'persistent' ? `are held from you and read by ${reader.name}`
143
+ : 'are held from you and read by forks of your context';
144
+ }
145
+
146
+ /**
147
+ * What a tuned-out channel's traffic is, as the words after "its traffic"
148
+ * in the [tuned out] note to a send there: held from the resident and read
149
+ * by `reader` until the tune-out ends, or, where none runs, held until its
150
+ * release is tried again (TuneOutReader).
151
+ */
152
+ export function tunedOutTrafficWords(reader: TuneOutReader): string {
153
+ if (reader.kind === 'persistent' && reader.running === false) {
154
+ return "is still held from you, with no reader running here to read it, until the tune-out's release, which failed, is tried again.";
155
+ }
156
+ return reader.kind === 'persistent'
157
+ ? `is still held from you, and read by ${reader.name}, until the tune-out ends.`
158
+ : 'is still held from you, and read by forks of your context, until the tune-out ends.';
159
+ }
160
+
161
+ /**
162
+ * Whether a tuned-out channel's messages get to its reader, as the words
163
+ * after "its messages" in tune_out's answers about the channel's transport,
164
+ * in the sentence's `form`: they reach `reader`, by the name it runs under
165
+ * or as "the reader" (forks, as tuneOutTool says it). Where no reader runs,
166
+ * those answers aren't given: tuneOutNoReaderAnswer is, in their place.
167
+ */
168
+ export function tuneOutReachWords(reader: TuneOutReader, form: 'reach' | 'may not reach' | "don't reach"): string {
169
+ return `${form} ${reader.kind === 'persistent' ? reader.name : 'the reader'}`;
170
+ }
171
+
172
+ /**
173
+ * tune_out's answer about a tuned-out channel's transport where no reader
174
+ * runs (`running: false`, a hold left on record by a failed release): a
175
+ * sentence of its own, given alone in place of each answer that says
176
+ * whether the messages reach the reader and when (tuneOutReachWords), since
177
+ * opening the channel doesn't change that none reads them. Undefined where
178
+ * a reader runs. tune_out is refused with no reader, repeated for a channel
179
+ * already tuned out too, so this answers a tune_out whose reader ended
180
+ * while its channel's open was out.
181
+ */
182
+ export function tuneOutNoReaderAnswer(reader: TuneOutReader): string | undefined {
183
+ if (reader.kind !== 'persistent' || reader.running !== false) return undefined;
184
+ return 'The tune-out is recorded, but its messages are held, with no reader running here to read them, until the '
185
+ + "tune-out's release, which failed, is tried again; opening its channel doesn't change that.";
186
+ }
187
+
113
188
  /**
114
189
  * Configuration for the subconscious (FrameworkConfig.subconscious). Two
115
190
  * shapes, by `reader`:
@@ -156,12 +231,22 @@ interface SubconsciousConfigBase {
156
231
  thinkingBinding?: AgentConfig['thinkingBinding'];
157
232
  }
158
233
 
234
+ /**
235
+ * The name a persistent reader runs under: its configuration's `name`, or,
236
+ * where the host gave none, `'Subconscious'` (PersistentReaderConfig.name).
237
+ * The one place the framework names a reader the host didn't: everything
238
+ * that needs the name of an unnamed reader asks this.
239
+ */
240
+ export function persistentReaderName(config: { name?: string }): string {
241
+ return config.name ?? 'Subconscious';
242
+ }
243
+
159
244
  export interface PersistentReaderConfig extends SubconsciousConfigBase {
160
245
  reader?: 'persistent';
161
246
  /**
162
247
  * Registry + participant name. Default 'Subconscious' — following the
163
248
  * Context Manager precedent: a title-case functional voice, not an
164
- * agent-prefixed identifier.
249
+ * agent-prefixed identifier. persistentReaderName applies the default.
165
250
  */
166
251
  name?: string;
167
252
  /** Model id; defaults to the primary agent's (same-model side-process).
@@ -171,29 +256,53 @@ export interface PersistentReaderConfig extends SubconsciousConfigBase {
171
256
  * `thinkingBinding` set its own. */
172
257
  model?: string;
173
258
  /**
174
- * The voice/criteria mode block — recipe-side and co-authored with the
175
- * resident (it is an aspect of their attention). Report-shaped, second
176
- * person toward the resident. Canary before fleet use (issue #77).
259
+ * The persistent reader's system prompt (its AgentConfig.systemPrompt).
260
+ * Who writes it and what it says, a policy for the reader tools
261
+ * included, are the host's choice. Canary before fleet use (issue #77).
177
262
  */
178
263
  systemPrompt: string;
179
264
  /** WindowedPassthroughStrategy re-anchor fraction (default 0.5). */
180
265
  reAnchorFraction?: number;
266
+ /**
267
+ * How a resident's rounds go out in the subconscious's requests once its
268
+ * window follows the presentation index (#291, answer 13). Its own rounds go
269
+ * as stored either way.
270
+ *
271
+ * - `'transcript'` (the default): a line per tool call (the tool's name and
272
+ * its input in brief) and per result (its outcome in brief), the text as
273
+ * stored, and none of the resident's thinking (foreign-rounds.ts).
274
+ * - `'raw'`: as stored, as 0.30.0 sent them: the resident's thinking
275
+ * blocks, its tool calls and their results, in the subconscious's
276
+ * requests under the resident's name. A provider pairs tool blocks only
277
+ * in the turns of the agent that made the call, so it may refuse them, or
278
+ * the formatter may move them into the subconscious's own turns, where
279
+ * they read as calls it made.
280
+ *
281
+ * A thinking block's signature binds the block to the prefix the resident
282
+ * was sent, so the block can't be sent as part of another agent's prefix;
283
+ * its text could be. Leaving the text out is the transcript's choice, not
284
+ * the provider's.
285
+ */
286
+ foreignRounds?: 'transcript' | 'raw';
181
287
  }
182
288
 
183
289
  export interface ForkReaderConfig extends SubconsciousConfigBase {
184
290
  reader: 'forks';
185
291
  /**
186
292
  * Whose weights run the fork. Required, never defaulted: a fork holds the
187
- * resident's whole prefix, and which model reads it is the one fact the
188
- * resident's consent names. The resident's own model is the copy; another
189
- * model is allowed when stated here. `create` refuses the omission.
293
+ * resident's whole prefix, and which model reads it is recorded on each
294
+ * fork's `dendrite:agent-created` trace and is what a host's
295
+ * `dendrite.beforeDerive` can decide on. The resident's own model, or
296
+ * another model stated here. `create` refuses the omission.
190
297
  */
191
298
  model: string;
192
299
  /**
193
- * The reader's voice block — report-shaped, second person toward the
194
- * resident, co-authored with them. Delivered as the first message after
195
- * the fork point, with the notice, the standing dispositions and the held
196
- * traffic; never a system prompt.
300
+ * The reader's voice block: text the host supplies, handed to `framing`.
301
+ * The default framing (defaultReaderFraming) puts it in the one message a
302
+ * fork receives after the fork point, with the notice, the standing
303
+ * dispositions and the held traffic; it is never a system prompt. Who
304
+ * writes it and what it says, a policy for the reader tools included,
305
+ * are the host's choice.
197
306
  */
198
307
  voice: string;
199
308
  /**
@@ -211,7 +320,13 @@ export interface ForkReaderConfig extends SubconsciousConfigBase {
211
320
  * framework stores whatever this returns as a user message with
212
321
  * `metadata.kind = 'tune-out-reader-framing'`; the notice, voice,
213
322
  * dispositions and held traffic are in the context, and nothing else
214
- * about the fork's prompt is fixed by the path.
323
+ * about the fork's prompt is fixed by the path. note_disposition tells a
324
+ * reader that a disposition it records is shown to the reader each time
325
+ * it runs, and a fork is shown them only as its framing puts them
326
+ * (`dispositions`), so a framing that leaves them out makes that untrue.
327
+ * Likewise the held traffic reaches the fork only as its framing puts it
328
+ * (`backlog`): the fork's view, the residents', leaves the raw rows out,
329
+ * as it leaves out all held traffic (the default framing shows them).
215
330
  */
216
331
  framing?: (context: ReaderFramingContext) => ContentBlock[];
217
332
  }
@@ -232,13 +347,20 @@ export interface ReaderFramingContext {
232
347
  /** Standing dispositions recorded on the epoch, or null. */
233
348
  dispositions: string | null;
234
349
  /**
235
- * The traffic held since the last look, oldest first; empty when nothing
236
- * arrived. It includes the messages still queued behind the resident's
237
- * turn (admitted, not yet stored), last: their ids are held handles and
238
- * their sequences provisional, as a module's held rows are.
350
+ * The traffic held since the last look, oldest first, as stored; empty
351
+ * when nothing arrived. A held message is stored as it arrives, the
352
+ * resident's turn alive or not (#291), so the stored rows are all of it,
353
+ * but for one a surgery's hold or a manager awaiting recovery keeps in
354
+ * memory: it isn't stored yet, and is in the first backlog after it is.
239
355
  */
240
356
  backlog: StoredMessage[];
241
- /** How many of `backlog`'s last entries are still queued behind the resident's turn. Absent is none. */
357
+ /**
358
+ * Was how many of `backlog`'s last entries were still queued behind the
359
+ * resident's turn. Since #291 nothing is queued, so the framework never
360
+ * sets it.
361
+ *
362
+ * @deprecated Never set since #291; removed in a later release.
363
+ */
242
364
  queuedCount?: number;
243
365
  }
244
366
 
@@ -259,15 +381,11 @@ export function defaultReaderFraming(ctx: ReaderFramingContext): ContentBlock[]
259
381
  return content;
260
382
  }
261
383
  const n = ctx.backlog.length;
262
- const q = Math.min(Math.max(0, Math.floor(ctx.queuedCount ?? 0)), n);
263
384
  const lines: string[] = [`\n[Held in ${ctx.channelId} since your last look: ${n} message${n === 1 ? '' : 's'}]`];
264
385
  const flush = () => {
265
386
  if (lines.length > 0) content.push({ type: 'text', text: lines.splice(0).join('\n') });
266
387
  };
267
- const which = q === n ? (q === 1 ? 'It' : 'They') : (q === 1 ? 'The last one' : `The last ${q}`);
268
- const queuedNote = `[${which} arrived while ${ctx.resident}'s turn was running, so ${q === 1 ? 'it is' : 'they are'} still queued, not yet stored.]`;
269
- for (const [i, message] of ctx.backlog.entries()) {
270
- if (q > 0 && i === n - q) lines.push(queuedNote);
388
+ for (const message of ctx.backlog) {
271
389
  const author = (message.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? message.participant;
272
390
  const text = message.content
273
391
  .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
@@ -277,7 +395,7 @@ export function defaultReaderFraming(ctx: ReaderFramingContext): ContentBlock[]
277
395
  const media = message.content.filter((b) => b.type !== 'text');
278
396
  if (media.length > 0) {
279
397
  flush();
280
- content.push(...media);
398
+ appendAll(content, media);
281
399
  }
282
400
  }
283
401
  flush();
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Where the persistent subconscious's window starts (#291, AF-5).
3
+ *
4
+ * The subconscious reads a tune-out's held traffic through a windowed
5
+ * passthrough strategy: everything from the anchor on, the anchor at the
6
+ * oldest active tune-out's start (the coordinator's anchorSubconscious), or
7
+ * at an earlier start kept in force: a cancelled tune-out's, kept while
8
+ * another stays active and across a restart, until the next enter sets the
9
+ * oldest active one's (below).
10
+ * While its window is the record, a start is a sequence, and the window is
11
+ * every row stored at it or after. Once its window follows the presentation
12
+ * index, the window is the rows in the order it was shown them, and a start
13
+ * is an index ordinal: the row at it and every row presented after it.
14
+ *
15
+ * A tune-out's start can't be turned into an ordinal once and for all at its
16
+ * enter. The subconscious's window grows only at its own points, so a row
17
+ * stored before the start can still be out of its window then (its turn
18
+ * alive, the host quiesced), and would be presented after the start, inside
19
+ * the window. Converting the sequence again later (a restart, another
20
+ * tune-out's enter) can give another ordinal, and so another window, once
21
+ * rows have been presented out of record order. So each tune-out's start is
22
+ * fixed once, at the first moment no row the subconscious is owed stored
23
+ * before the start is out of its window: the ordinal just past the last row
24
+ * in its window stored before the start, which leaves every such row out
25
+ * (0 when there is none). Others' rows enter a window in record order, so
26
+ * that is where the first row stored at the start or after entered, unless
27
+ * one of the subconscious's own rows was presented ahead of them, at its
28
+ * write, in the turn the tune-out began in; that row stays out too, the turn
29
+ * having been before the tune-out. The coordinator keeps the ordinal
30
+ * (subconsciousWindowFixed), and from then on the start is set by it,
31
+ * clamped to the window's length on a branch whose window is shorter (a
32
+ * rollback), the clamped ordinal then kept as the start, never converted
33
+ * again. A kept ordinal is kept as the start in force once it is set, not
34
+ * before, and one whose setting or keeping fails is tried again at the next
35
+ * presentation, as a fix is. One set while the window is the record (a
36
+ * branch move whose re-initialization of the subconscious failed) waits
37
+ * longer: it is set by sequence and fixed at the next enter or restart once
38
+ * the window presents. One whose setting or keeping failed before a branch
39
+ * move is fixed again on the branch moved to, from its window there, as one
40
+ * never fixed is: an ordinal is a place on the branch it was fixed on; one
41
+ * for tune-outs none of which is active then, and a cancelled tune-out's
42
+ * start set in force by its kept ordinal, are dropped at the move. A
43
+ * restart that finds a start in force kept sets that one, and fixes the
44
+ * oldest active tune-out's start, if it isn't kept, without moving the
45
+ * window to it; one that finds none in force kept sets the anchor the
46
+ * strategy persisted, as the start in force: where the oldest's start is
47
+ * kept, if it is earlier (a cancelled tune-out's start that was set but
48
+ * never kept), and where it isn't (its fix set, its keeping failed),
49
+ * wherever it is, rather than converting the oldest's sequence again, that
50
+ * start fixed without moving the window to it; where that anchor can't be
51
+ * read, it sets nothing, the window left where it was until the next enter
52
+ * or restart.
53
+ */
54
+ import type { ContextManager, StoredMessage } from '@animalabs/context-manager';
55
+ import type { SubconsciousWindowStart } from './coordinator.js';
56
+ import type { TraceEventInput } from '../types/trace.js';
57
+
58
+ /** The windowed strategy's anchor setters and read, as the anchor uses them. */
59
+ export interface AnchoredStrategy {
60
+ setAnchor(sequence: number): void;
61
+ setAnchorOrdinal(ordinal: number): void;
62
+ getAnchorOrdinal(): number | undefined;
63
+ }
64
+
65
+ /** What the anchor reads of the framework. */
66
+ export interface SubconsciousWindowAnchorHost {
67
+ /** The subconscious's name, for its traces. */
68
+ readonly agentName: string;
69
+ /** Its context manager while it is registered. */
70
+ cm(): Pick<ContextManager, 'indexLength' | 'getPresentedMessages' | 'ordinalOf'> | undefined;
71
+ /** Its windowed strategy. */
72
+ strategy(): AnchoredStrategy | null;
73
+ /** Whether its window follows the presentation index, so its places are ordinals. */
74
+ presents(): boolean;
75
+ /** The rows it is owed that its window doesn't hold yet, one per body (AgentPresentation.owedOf). */
76
+ owed(): readonly Pick<StoredMessage, 'sequence'>[];
77
+ /**
78
+ * Keep a start fixed for `epochIds` (TuneOutCoordinator.subconsciousWindowFixed), as the start in force when
79
+ * `inForce`; false when it couldn't be kept.
80
+ */
81
+ fixed(epochIds: readonly string[], ordinal: number, inForce: boolean): boolean;
82
+ emitTrace(event: Extract<TraceEventInput, { type: 'tune-out:subconscious-window' }>): void;
83
+ }
84
+
85
+ /** The subconscious's window start, set by sequence in legacy mode and by a fixed ordinal once its window is presented. */
86
+ export class SubconsciousWindowAnchor {
87
+ /**
88
+ * The starts whose ordinal isn't set and kept yet, the one in force
89
+ * (`inForce`) first if it is among them: some row older than a start is
90
+ * still out of the window, or setting or keeping the ordinal it was fixed
91
+ * at (`ordinal`) failed, which the next presentation tries again. A start
92
+ * set by an ordinal already known (`known`) waits only for its setting and
93
+ * keeping: kept for `keep` (none for a start in force only) and as the
94
+ * start in force, traced when clamped (`clampedFrom`, the ordinal kept
95
+ * before).
96
+ */
97
+ private pending: Array<{
98
+ start: SubconsciousWindowStart;
99
+ ordinal?: number;
100
+ inForce: boolean;
101
+ known?: { keep: readonly string[]; clampedFrom?: number };
102
+ }> = [];
103
+
104
+ constructor(private readonly host: SubconsciousWindowAnchorHost) {}
105
+
106
+ /**
107
+ * Set the window start (the coordinator's setSubconsciousAnchor). In
108
+ * legacy mode by its sequence, as before the index. In presented mode by
109
+ * its ordinal when one was fixed, clamped to the window's index length
110
+ * and traced when past it, the clamped ordinal kept as the start; the
111
+ * strategy's persisted anchor where it is earlier, or, with no ordinal
112
+ * known, where it has one, at a resume that asks for it
113
+ * (keepEarlierAnchor); else by its sequence for now, converted as
114
+ * the strategy converts one, and the ordinal is fixed (see the module
115
+ * doc) as soon as no row the subconscious is owed stored before the start
116
+ * is out of its window, now or at a later presentation (presented). An
117
+ * ordinal set is kept as the start in force once it is set, and tried
118
+ * again at the next presentation until both succeed. The starts of
119
+ * `others`, the other active tune-outs' whose ordinal isn't kept yet, are
120
+ * fixed the same way, without moving the window to them. Never throws: a
121
+ * set that fails is logged, and the anchor stays where it was.
122
+ */
123
+ set(start: SubconsciousWindowStart, others: readonly SubconsciousWindowStart[] = []): void {
124
+ try {
125
+ const strategy = this.host.strategy();
126
+ if (!strategy) return;
127
+ if (!this.host.presents()) {
128
+ this.pending = [];
129
+ strategy.setAnchor(start.sequence);
130
+ return;
131
+ }
132
+ const was = this.pending;
133
+ const carried = (wanted: SubconsciousWindowStart, inForce: boolean) => ({
134
+ ...(was.find((p) => !p.known && sameStart(p.start, wanted)) ?? { start: wanted }),
135
+ inForce,
136
+ });
137
+ this.pending = others.map((other) => carried(other, false));
138
+ let known = start.ordinal;
139
+ let inForceOnly = start.inForceOnly === true;
140
+ if (start.keepEarlierAnchor) {
141
+ const persisted = persistedOrdinal(strategy);
142
+ if (persisted === null) {
143
+ // Whether the strategy holds an earlier start (a cancelled
144
+ // tune-out's, set and never kept) can't be told: the kept one set
145
+ // over it would drop the rows it held from the window, and be kept
146
+ // in force. The window stays where it was, nothing set or kept in
147
+ // force; the next enter or restart sets it. The others are fixed,
148
+ // and so is the start itself where no ordinal is kept for it,
149
+ // without moving the window to it.
150
+ if (known === undefined) this.pending.push(carried(start, false));
151
+ this.tryFix();
152
+ return;
153
+ }
154
+ // With no ordinal kept (one fixed and set whose keeping failed), the
155
+ // persisted anchor is the start this window had, wherever it is: the
156
+ // sequence converted again could give a later one, leaving out a
157
+ // row presented out of record order (AF #844 review 5, #47). The
158
+ // start's own ordinal is fixed beside it, unmoved to.
159
+ if (persisted !== undefined && (known === undefined || persisted < known)) {
160
+ if (known === undefined) this.pending.push(carried(start, false));
161
+ known = persisted;
162
+ inForceOnly = true;
163
+ }
164
+ }
165
+ if (known !== undefined) {
166
+ const length = this.host.cm()?.indexLength() ?? 0;
167
+ const ordinal = Math.min(known, length);
168
+ this.pending.unshift({
169
+ start,
170
+ ordinal,
171
+ inForce: true,
172
+ known: { keep: inForceOnly ? [] : start.epochIds, ...(ordinal !== known ? { clampedFrom: known } : {}) },
173
+ });
174
+ } else {
175
+ this.pending.unshift(carried(start, true));
176
+ strategy.setAnchor(start.sequence);
177
+ }
178
+ this.tryFix();
179
+ } catch (error) {
180
+ console.error(`[tune-out] the subconscious's window start couldn't be set, so it stays where it was:`, error);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * The store's branch moved (the framework's followBranchMove, before the
186
+ * agents follow it): an ordinal is a place on the branch it was fixed on,
187
+ * so a start pending at one (its setting or keeping failed), or set by one
188
+ * kept there (`known`), waits again to be fixed, from the window it
189
+ * presents on now, as a start never fixed does (tryFix): one for a
190
+ * tune-out still active (`active`, the coordinator's, where the framework
191
+ * says). One fixed for tune-outs none of which is active any more, and
192
+ * the start in force only (a cancelled tune-out's, set by its kept
193
+ * ordinal: fixed again, it would be the active one's start), are dropped,
194
+ * as every start fixed at an ordinal was before (AF #844 review 3, #30
195
+ * and #44): the anchor the destination's strategy keeps stands until the
196
+ * next enter or restart sets one.
197
+ */
198
+ branchMoved(active?: readonly string[]): void {
199
+ const holds = active === undefined ? undefined : new Set(active);
200
+ this.pending = this.pending
201
+ .filter((p) => {
202
+ if (p.known && p.known.keep.length === 0) return false;
203
+ const fixedThere = p.ordinal !== undefined || p.known !== undefined;
204
+ return !fixedThere || holds === undefined || p.start.epochIds.some((id) => holds.has(id));
205
+ })
206
+ .map((p) => ({ start: p.start, inForce: p.inForce }));
207
+ }
208
+
209
+ /** Rows entered the subconscious's window (the presentation index's onPresented): a start waiting to be fixed may be fixable now. */
210
+ presented(): void {
211
+ if (this.pending.length === 0) return;
212
+ try {
213
+ this.tryFix();
214
+ } catch (error) {
215
+ console.error(`[tune-out] the subconscious's window start couldn't be fixed at this presentation; the next one tries again:`, error);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Fix each pending start's ordinal, once no row owed and stored before it
221
+ * is out of the window, then set it (the start in force) and keep it;
222
+ * until both succeed a start stays pending at that ordinal. A start set by
223
+ * an ordinal known is set and kept the same way, and traced only when
224
+ * clamped.
225
+ */
226
+ private tryFix(): void {
227
+ if (this.pending.length === 0 || !this.host.presents()) return;
228
+ const cm = this.host.cm();
229
+ const strategy = this.host.strategy();
230
+ if (!cm || !strategy) return;
231
+ // Read once, and only for a start still to be fixed.
232
+ let owed: readonly Pick<StoredMessage, 'sequence'>[] | undefined;
233
+ for (const entry of [...this.pending]) {
234
+ if (entry.ordinal === undefined) {
235
+ owed ??= this.host.owed();
236
+ if (owed.some((row) => row.sequence < entry.start.sequence)) continue;
237
+ // Just past the last row in the window stored before the start.
238
+ let ordinal = 0;
239
+ for (const row of cm.getPresentedMessages()) {
240
+ if (row.sequence >= entry.start.sequence) continue;
241
+ const at = cm.ordinalOf(row.id);
242
+ if (at !== undefined && at + 1 > ordinal) ordinal = at + 1;
243
+ }
244
+ entry.ordinal = ordinal;
245
+ }
246
+ const ordinal = entry.ordinal;
247
+ if (entry.inForce) strategy.setAnchorOrdinal(ordinal);
248
+ if (!this.host.fixed(entry.known ? entry.known.keep : entry.start.epochIds, ordinal, entry.inForce)) continue;
249
+ this.pending = this.pending.filter((p) => p !== entry);
250
+ if (!entry.known) {
251
+ this.host.emitTrace({
252
+ type: 'tune-out:subconscious-window', agentName: this.host.agentName,
253
+ epochIds: [...entry.start.epochIds], how: 'fixed', ordinal,
254
+ });
255
+ } else if (entry.known.clampedFrom !== undefined) {
256
+ this.host.emitTrace({
257
+ type: 'tune-out:subconscious-window', agentName: this.host.agentName,
258
+ epochIds: [...entry.start.epochIds], how: 'clamped', ordinal, kept: entry.known.clampedFrom,
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ /** The anchor the strategy persisted, as an ordinal: undefined where it persisted none, null where it can't be read. */
266
+ function persistedOrdinal(strategy: AnchoredStrategy): number | undefined | null {
267
+ try {
268
+ return strategy.getAnchorOrdinal();
269
+ } catch (error) {
270
+ console.error(`[tune-out] the subconscious's persisted window start couldn't be read, so the window start stays where it was; the next enter or restart sets it:`, error);
271
+ return null;
272
+ }
273
+ }
274
+
275
+ function sameStart(a: SubconsciousWindowStart, b: SubconsciousWindowStart): boolean {
276
+ return a.sequence === b.sequence && a.epochIds.length === b.epochIds.length && a.epochIds.every((id) => b.epochIds.includes(id));
277
+ }