@animalabs/agent-framework 0.31.0 → 0.33.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 (298) hide show
  1. package/README.md +204 -14
  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 +185 -78
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +279 -102
  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/code-execution/isolate/scripts.d.ts +12 -8
  17. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  18. package/dist/src/code-execution/isolate/scripts.js +24 -0
  19. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  20. package/dist/src/config-checks.d.ts +7 -0
  21. package/dist/src/config-checks.d.ts.map +1 -1
  22. package/dist/src/config-checks.js +14 -0
  23. package/dist/src/config-checks.js.map +1 -1
  24. package/dist/src/context-manager-errors.d.ts +25 -0
  25. package/dist/src/context-manager-errors.d.ts.map +1 -0
  26. package/dist/src/context-manager-errors.js +31 -0
  27. package/dist/src/context-manager-errors.js.map +1 -0
  28. package/dist/src/context-options.d.ts +67 -0
  29. package/dist/src/context-options.d.ts.map +1 -0
  30. package/dist/src/context-options.js +85 -0
  31. package/dist/src/context-options.js.map +1 -0
  32. package/dist/src/deferred-import.d.ts +93 -0
  33. package/dist/src/deferred-import.d.ts.map +1 -0
  34. package/dist/src/deferred-import.js +177 -0
  35. package/dist/src/deferred-import.js.map +1 -0
  36. package/dist/src/dendrite/audience.d.ts +223 -0
  37. package/dist/src/dendrite/audience.d.ts.map +1 -0
  38. package/dist/src/dendrite/audience.js +202 -0
  39. package/dist/src/dendrite/audience.js.map +1 -0
  40. package/dist/src/dendrite/index.d.ts +5 -3
  41. package/dist/src/dendrite/index.d.ts.map +1 -1
  42. package/dist/src/dendrite/index.js +2 -1
  43. package/dist/src/dendrite/index.js.map +1 -1
  44. package/dist/src/dendrite/presets.d.ts +10 -5
  45. package/dist/src/dendrite/presets.d.ts.map +1 -1
  46. package/dist/src/dendrite/presets.js +17 -5
  47. package/dist/src/dendrite/presets.js.map +1 -1
  48. package/dist/src/dendrite/registry.d.ts +62 -7
  49. package/dist/src/dendrite/registry.d.ts.map +1 -1
  50. package/dist/src/dendrite/registry.js +179 -10
  51. package/dist/src/dendrite/registry.js.map +1 -1
  52. package/dist/src/dendrite/types.d.ts +91 -8
  53. package/dist/src/dendrite/types.d.ts.map +1 -1
  54. package/dist/src/foreign-rounds.d.ts +69 -0
  55. package/dist/src/foreign-rounds.d.ts.map +1 -0
  56. package/dist/src/foreign-rounds.js +152 -0
  57. package/dist/src/foreign-rounds.js.map +1 -0
  58. package/dist/src/framework.d.ts +1977 -820
  59. package/dist/src/framework.d.ts.map +1 -1
  60. package/dist/src/framework.js +7571 -3457
  61. package/dist/src/framework.js.map +1 -1
  62. package/dist/src/gate/event-gate.d.ts +85 -15
  63. package/dist/src/gate/event-gate.d.ts.map +1 -1
  64. package/dist/src/gate/event-gate.js +421 -47
  65. package/dist/src/gate/event-gate.js.map +1 -1
  66. package/dist/src/gate/index.d.ts +1 -1
  67. package/dist/src/gate/index.d.ts.map +1 -1
  68. package/dist/src/gate/index.js.map +1 -1
  69. package/dist/src/gate/rule-scope.d.ts +30 -0
  70. package/dist/src/gate/rule-scope.d.ts.map +1 -0
  71. package/dist/src/gate/rule-scope.js +41 -0
  72. package/dist/src/gate/rule-scope.js.map +1 -0
  73. package/dist/src/gate/suppress-script-worker.d.ts +2 -0
  74. package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
  75. package/dist/src/gate/suppress-script-worker.js +103 -0
  76. package/dist/src/gate/suppress-script-worker.js.map +1 -0
  77. package/dist/src/gate/suppress-scripts.d.ts +105 -0
  78. package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
  79. package/dist/src/gate/suppress-scripts.js +403 -0
  80. package/dist/src/gate/suppress-scripts.js.map +1 -0
  81. package/dist/src/gate/suppression-review.d.ts +143 -0
  82. package/dist/src/gate/suppression-review.d.ts.map +1 -0
  83. package/dist/src/gate/suppression-review.js +463 -0
  84. package/dist/src/gate/suppression-review.js.map +1 -0
  85. package/dist/src/gate/suppression-stats.d.ts +125 -0
  86. package/dist/src/gate/suppression-stats.d.ts.map +1 -0
  87. package/dist/src/gate/suppression-stats.js +214 -0
  88. package/dist/src/gate/suppression-stats.js.map +1 -0
  89. package/dist/src/gate/types.d.ts +135 -7
  90. package/dist/src/gate/types.d.ts.map +1 -1
  91. package/dist/src/gate/wake-onboarding.d.ts +119 -0
  92. package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
  93. package/dist/src/gate/wake-onboarding.js +277 -0
  94. package/dist/src/gate/wake-onboarding.js.map +1 -0
  95. package/dist/src/held-writes.d.ts +31 -23
  96. package/dist/src/held-writes.d.ts.map +1 -1
  97. package/dist/src/held-writes.js +44 -59
  98. package/dist/src/held-writes.js.map +1 -1
  99. package/dist/src/index.d.ts +5 -2
  100. package/dist/src/index.d.ts.map +1 -1
  101. package/dist/src/index.js +3 -0
  102. package/dist/src/index.js.map +1 -1
  103. package/dist/src/jsonrpc-envelope.d.ts +69 -0
  104. package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
  105. package/dist/src/jsonrpc-envelope.js +66 -0
  106. package/dist/src/jsonrpc-envelope.js.map +1 -0
  107. package/dist/src/mcpl/channel-registry.d.ts +131 -15
  108. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  109. package/dist/src/mcpl/channel-registry.js +290 -46
  110. package/dist/src/mcpl/channel-registry.js.map +1 -1
  111. package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
  112. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  113. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  114. package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
  115. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  116. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  117. package/dist/src/mcpl/inbound-source.d.ts +2 -1
  118. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  119. package/dist/src/mcpl/inbound-source.js +2 -1
  120. package/dist/src/mcpl/inbound-source.js.map +1 -1
  121. package/dist/src/mcpl/inference-router.d.ts +6 -6
  122. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  123. package/dist/src/mcpl/inference-router.js.map +1 -1
  124. package/dist/src/mcpl/push-coalescer.d.ts +132 -22
  125. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  126. package/dist/src/mcpl/push-coalescer.js +105 -15
  127. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  128. package/dist/src/mcpl/push-handler.d.ts +9 -7
  129. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  130. package/dist/src/mcpl/push-handler.js.map +1 -1
  131. package/dist/src/mcpl/server-connection.d.ts +13 -10
  132. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  133. package/dist/src/mcpl/server-connection.js +134 -56
  134. package/dist/src/mcpl/server-connection.js.map +1 -1
  135. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  136. package/dist/src/mcpl/tool-classes.js +3 -0
  137. package/dist/src/mcpl/tool-classes.js.map +1 -1
  138. package/dist/src/mcpl/types.d.ts +8 -2
  139. package/dist/src/mcpl/types.d.ts.map +1 -1
  140. package/dist/src/mcpl/types.js.map +1 -1
  141. package/dist/src/module-registry.d.ts +24 -1
  142. package/dist/src/module-registry.d.ts.map +1 -1
  143. package/dist/src/module-registry.js +68 -18
  144. package/dist/src/module-registry.js.map +1 -1
  145. package/dist/src/modules/discord/index.d.ts +45 -8
  146. package/dist/src/modules/discord/index.d.ts.map +1 -1
  147. package/dist/src/modules/discord/index.js +237 -86
  148. package/dist/src/modules/discord/index.js.map +1 -1
  149. package/dist/src/modules/history/index.d.ts +59 -0
  150. package/dist/src/modules/history/index.d.ts.map +1 -1
  151. package/dist/src/modules/history/index.js +432 -67
  152. package/dist/src/modules/history/index.js.map +1 -1
  153. package/dist/src/modules/history/semantic.d.ts +6 -5
  154. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  155. package/dist/src/modules/history/semantic.js +15 -22
  156. package/dist/src/modules/history/semantic.js.map +1 -1
  157. package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
  158. package/dist/src/modules/workspace/disk-agreement.js +1 -1
  159. package/dist/src/modules/workspace/index.d.ts +28 -7
  160. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  161. package/dist/src/modules/workspace/index.js +76 -22
  162. package/dist/src/modules/workspace/index.js.map +1 -1
  163. package/dist/src/notice-tags.d.ts +10 -1
  164. package/dist/src/notice-tags.d.ts.map +1 -1
  165. package/dist/src/notice-tags.js +10 -1
  166. package/dist/src/notice-tags.js.map +1 -1
  167. package/dist/src/operator-log.d.ts +36 -3
  168. package/dist/src/operator-log.d.ts.map +1 -1
  169. package/dist/src/operator-log.js +2 -2
  170. package/dist/src/operator-log.js.map +1 -1
  171. package/dist/src/presentation.d.ts +746 -0
  172. package/dist/src/presentation.d.ts.map +1 -0
  173. package/dist/src/presentation.js +1435 -0
  174. package/dist/src/presentation.js.map +1 -0
  175. package/dist/src/prose-drafts.d.ts +15 -11
  176. package/dist/src/prose-drafts.d.ts.map +1 -1
  177. package/dist/src/prose-drafts.js +16 -12
  178. package/dist/src/prose-drafts.js.map +1 -1
  179. package/dist/src/read-marks.d.ts +192 -0
  180. package/dist/src/read-marks.d.ts.map +1 -0
  181. package/dist/src/read-marks.js +242 -0
  182. package/dist/src/read-marks.js.map +1 -0
  183. package/dist/src/recovery/offline-branch.d.ts +91 -6
  184. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  185. package/dist/src/recovery/offline-branch.js +273 -29
  186. package/dist/src/recovery/offline-branch.js.map +1 -1
  187. package/dist/src/recovery/recover-cli.js +22 -3
  188. package/dist/src/recovery/recover-cli.js.map +1 -1
  189. package/dist/src/send-tools.d.ts +5 -9
  190. package/dist/src/send-tools.d.ts.map +1 -1
  191. package/dist/src/send-tools.js +40 -7
  192. package/dist/src/send-tools.js.map +1 -1
  193. package/dist/src/stored-injections.d.ts +2 -2
  194. package/dist/src/stored-injections.js +2 -2
  195. package/dist/src/stored-removal.d.ts +17 -4
  196. package/dist/src/stored-removal.d.ts.map +1 -1
  197. package/dist/src/stored-removal.js +29 -5
  198. package/dist/src/stored-removal.js.map +1 -1
  199. package/dist/src/tool-result-guard.d.ts +12 -1
  200. package/dist/src/tool-result-guard.d.ts.map +1 -1
  201. package/dist/src/tool-result-guard.js +42 -8
  202. package/dist/src/tool-result-guard.js.map +1 -1
  203. package/dist/src/tune-out/coordinator.d.ts +1159 -79
  204. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  205. package/dist/src/tune-out/coordinator.js +3709 -363
  206. package/dist/src/tune-out/coordinator.js.map +1 -1
  207. package/dist/src/tune-out/tools.d.ts +114 -17
  208. package/dist/src/tune-out/tools.d.ts.map +1 -1
  209. package/dist/src/tune-out/tools.js +87 -27
  210. package/dist/src/tune-out/tools.js.map +1 -1
  211. package/dist/src/tune-out/window-anchor.d.ts +143 -0
  212. package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
  213. package/dist/src/tune-out/window-anchor.js +202 -0
  214. package/dist/src/tune-out/window-anchor.js.map +1 -0
  215. package/dist/src/types/agent.d.ts +86 -31
  216. package/dist/src/types/agent.d.ts.map +1 -1
  217. package/dist/src/types/events.d.ts +2 -0
  218. package/dist/src/types/events.d.ts.map +1 -1
  219. package/dist/src/types/framework.d.ts +43 -21
  220. package/dist/src/types/framework.d.ts.map +1 -1
  221. package/dist/src/types/module.d.ts +180 -69
  222. package/dist/src/types/module.d.ts.map +1 -1
  223. package/dist/src/types/trace.d.ts +510 -36
  224. package/dist/src/types/trace.d.ts.map +1 -1
  225. package/package.json +3 -3
  226. package/src/admission/admit.ts +32 -3
  227. package/src/agent.ts +323 -120
  228. package/src/api/mcp-server-core.ts +54 -17
  229. package/src/api/server.ts +4 -23
  230. package/src/code-execution/isolate/scripts.ts +28 -3
  231. package/src/config-checks.ts +14 -0
  232. package/src/context-manager-errors.ts +37 -0
  233. package/src/context-options.ts +96 -0
  234. package/src/deferred-import.ts +239 -0
  235. package/src/dendrite/audience.ts +315 -0
  236. package/src/dendrite/index.ts +14 -2
  237. package/src/dendrite/presets.ts +18 -6
  238. package/src/dendrite/registry.ts +209 -12
  239. package/src/dendrite/types.ts +92 -8
  240. package/src/foreign-rounds.ts +199 -0
  241. package/src/framework.ts +7814 -3585
  242. package/src/gate/event-gate.ts +428 -65
  243. package/src/gate/index.ts +3 -0
  244. package/src/gate/rule-scope.ts +45 -0
  245. package/src/gate/suppress-script-worker.ts +89 -0
  246. package/src/gate/suppress-scripts.ts +414 -0
  247. package/src/gate/suppression-review.ts +517 -0
  248. package/src/gate/suppression-stats.ts +314 -0
  249. package/src/gate/types.ts +141 -7
  250. package/src/gate/wake-onboarding.ts +309 -0
  251. package/src/held-writes.ts +49 -60
  252. package/src/index.ts +9 -2
  253. package/src/jsonrpc-envelope.ts +81 -0
  254. package/src/mcpl/channel-registry.ts +353 -85
  255. package/src/mcpl/checkpoint-manager.ts +3 -2
  256. package/src/mcpl/feature-set-manager.ts +2 -1
  257. package/src/mcpl/inbound-source.ts +2 -1
  258. package/src/mcpl/inference-router.ts +7 -4
  259. package/src/mcpl/push-coalescer.ts +193 -34
  260. package/src/mcpl/push-handler.ts +10 -5
  261. package/src/mcpl/server-connection.ts +141 -60
  262. package/src/mcpl/tool-classes.ts +3 -0
  263. package/src/mcpl/types.ts +8 -2
  264. package/src/module-registry.ts +73 -17
  265. package/src/modules/discord/index.ts +229 -81
  266. package/src/modules/history/index.ts +439 -70
  267. package/src/modules/history/semantic.ts +15 -21
  268. package/src/modules/workspace/disk-agreement.ts +1 -1
  269. package/src/modules/workspace/index.ts +85 -23
  270. package/src/notice-tags.ts +10 -1
  271. package/src/operator-log.ts +36 -3
  272. package/src/presentation.ts +1676 -0
  273. package/src/prose-drafts.ts +16 -12
  274. package/src/read-marks.ts +328 -0
  275. package/src/recovery/offline-branch.ts +346 -32
  276. package/src/recovery/recover-cli.ts +21 -3
  277. package/src/send-tools.ts +40 -7
  278. package/src/stored-injections.ts +2 -2
  279. package/src/stored-removal.ts +31 -7
  280. package/src/tool-result-guard.ts +41 -7
  281. package/src/tune-out/coordinator.ts +4089 -457
  282. package/src/tune-out/tools.ts +160 -43
  283. package/src/tune-out/window-anchor.ts +277 -0
  284. package/src/types/agent.ts +90 -31
  285. package/src/types/events.ts +2 -0
  286. package/src/types/framework.ts +43 -21
  287. package/src/types/module.ts +181 -68
  288. package/src/types/trace.ts +542 -40
  289. package/dist/src/reasoning-loss-notice.d.ts +0 -23
  290. package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
  291. package/dist/src/reasoning-loss-notice.js +0 -52
  292. package/dist/src/reasoning-loss-notice.js.map +0 -1
  293. package/dist/src/reasoning-loss.d.ts +0 -24
  294. package/dist/src/reasoning-loss.d.ts.map +0 -1
  295. package/dist/src/reasoning-loss.js +0 -102
  296. package/dist/src/reasoning-loss.js.map +0 -1
  297. package/src/reasoning-loss-notice.ts +0 -64
  298. package/src/reasoning-loss.ts +0 -120
@@ -29,10 +29,9 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
29
29
  {
30
30
  name: 'deliver_summary',
31
31
  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.',
32
+ 'Deliver a summary into the resident\'s context: `text` arrives there ' +
33
+ 'verbatim (leading and trailing whitespace trimmed), under your own ' +
34
+ 'name.',
36
35
  inputSchema: {
37
36
  type: 'object',
38
37
  properties: {
@@ -51,11 +50,15 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
51
50
  {
52
51
  name: 'cancel_tuneout',
53
52
  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.',
53
+ 'End the tune-out on a channel. The diverted backlog is delivered ' +
54
+ 'to the resident (capped), and the channel\'s traffic reaches them as ' +
55
+ 'it did before the tune-out, unless the all-except hold holds it too. ' +
56
+ 'A note in `text` arrives alongside the ' +
57
+ 'backlog, verbatim (leading and trailing whitespace trimmed), under ' +
58
+ 'your own name. Where the cancel has to wait (an operator\'s change to ' +
59
+ 'the store, say), its answer says so, and whether your note waits with ' +
60
+ 'it. If you were started to read ' +
61
+ 'one hold, you can end only that hold.',
59
62
  inputSchema: {
60
63
  type: 'object',
61
64
  properties: {
@@ -72,10 +75,10 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
72
75
  {
73
76
  name: 'note_disposition',
74
77
  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.',
78
+ 'Record a standing disposition: a durable note, kept under `key` ' +
79
+ 'across invocations and shown to the reader each time it runs (e.g. ' +
80
+ 'key "<sender>", text "<a note on their messages>"). Replaces any ' +
81
+ 'previous note under the same key.',
79
82
  inputSchema: {
80
83
  type: 'object',
81
84
  properties: {
@@ -91,14 +94,14 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
91
94
  {
92
95
  name: 'speak_in_channel',
93
96
  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.',
97
+ 'Post `text` into a tuned-out channel, as written (leading and trailing ' +
98
+ 'whitespace trimmed). It goes out through ' +
99
+ 'the resident\'s publish path, so the channel sees it under the ' +
100
+ 'resident\'s account; nothing in the post names you. If you were ' +
101
+ 'started to read one hold, you can post only into a channel that hold ' +
102
+ 'still holds, and a post made once it has ended is refused; your report ' +
103
+ 'can say it instead. Disabled unless the resident\'s configuration ' +
104
+ 'allows it.',
102
105
  inputSchema: {
103
106
  type: 'object',
104
107
  properties: {
@@ -110,6 +113,77 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
110
113
  },
111
114
  ];
112
115
 
116
+ /**
117
+ * Who reads a tuned-out channel's traffic, as the text a resident reads
118
+ * says it: the persistent subconscious by the name it runs under, or reader
119
+ * forks. There is no hold without a reader (0.33.0) but one whose release
120
+ * failed after the persistent reader ended, or at a start with none
121
+ * configured: none runs then (`running: false`), the reader named as
122
+ * configured, or by its default name, for tune_out's own text, and what a
123
+ * held channel's messages are says that none reads them (tuneOutHeldWords,
124
+ * tunedOutTrafficWords). A persistent reader's name is a value the host
125
+ * supplied, marked as such (suppliedValue).
126
+ */
127
+ export type TuneOutReader =
128
+ | { readonly kind: 'persistent'; readonly name: string; readonly running?: false }
129
+ | { readonly kind: 'forks' };
130
+
131
+ /**
132
+ * What a tuned-out channel's messages are, as the words after "its
133
+ * messages" in a refusal a resident reads (a channel tool's on a tuned-out
134
+ * channel): held from it and read by `reader`, by the name it runs under or
135
+ * as forks of the resident's context.
136
+ */
137
+ export function tuneOutHeldWords(reader: TuneOutReader): string {
138
+ if (reader.kind === 'persistent' && reader.running === false) {
139
+ return "are held from you, with no reader running here to read them, until the tune-out's release, which failed, is tried again";
140
+ }
141
+ return reader.kind === 'persistent' ? `are held from you and read by ${reader.name}`
142
+ : 'are held from you and read by forks of your context';
143
+ }
144
+
145
+ /**
146
+ * What a tuned-out channel's traffic is, as the words after "its traffic"
147
+ * in the [tuned out] note to a send there: held from the resident and read
148
+ * by `reader` until the tune-out ends, or, where none runs, held until its
149
+ * release is tried again (TuneOutReader).
150
+ */
151
+ export function tunedOutTrafficWords(reader: TuneOutReader): string {
152
+ if (reader.kind === 'persistent' && reader.running === false) {
153
+ 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.";
154
+ }
155
+ return reader.kind === 'persistent'
156
+ ? `is still held from you, and read by ${reader.name}, until the tune-out ends.`
157
+ : 'is still held from you, and read by forks of your context, until the tune-out ends.';
158
+ }
159
+
160
+ /**
161
+ * Whether a tuned-out channel's messages get to its reader, as the words
162
+ * after "its messages" in tune_out's answers about the channel's transport,
163
+ * in the sentence's `form`: they reach `reader`, by the name it runs under
164
+ * or as "the reader" (forks, as tuneOutTool says it). Where no reader runs,
165
+ * those answers aren't given: tuneOutNoReaderAnswer is, in their place.
166
+ */
167
+ export function tuneOutReachWords(reader: TuneOutReader, form: 'reach' | 'may not reach' | "don't reach"): string {
168
+ return `${form} ${reader.kind === 'persistent' ? reader.name : 'the reader'}`;
169
+ }
170
+
171
+ /**
172
+ * tune_out's answer about a tuned-out channel's transport where no reader
173
+ * runs (`running: false`, a hold left on record by a failed release): a
174
+ * sentence of its own, given alone in place of each answer that says
175
+ * whether the messages reach the reader and when (tuneOutReachWords), since
176
+ * opening the channel doesn't change that none reads them. Undefined where
177
+ * a reader runs. tune_out is refused with no reader, repeated for a channel
178
+ * already tuned out too, so this answers a tune_out whose reader ended
179
+ * while its channel's open was out.
180
+ */
181
+ export function tuneOutNoReaderAnswer(reader: TuneOutReader): string | undefined {
182
+ if (reader.kind !== 'persistent' || reader.running !== false) return undefined;
183
+ return 'The tune-out is recorded, but its messages are held, with no reader running here to read them, until the '
184
+ + "tune-out's release, which failed, is tried again; opening its channel doesn't change that.";
185
+ }
186
+
113
187
  /**
114
188
  * Configuration for the subconscious (FrameworkConfig.subconscious). Two
115
189
  * shapes, by `reader`:
@@ -156,12 +230,22 @@ interface SubconsciousConfigBase {
156
230
  thinkingBinding?: AgentConfig['thinkingBinding'];
157
231
  }
158
232
 
233
+ /**
234
+ * The name a persistent reader runs under: its configuration's `name`, or,
235
+ * where the host gave none, `'Subconscious'` (PersistentReaderConfig.name).
236
+ * The one place the framework names a reader the host didn't: everything
237
+ * that needs the name of an unnamed reader asks this.
238
+ */
239
+ export function persistentReaderName(config: { name?: string }): string {
240
+ return config.name ?? 'Subconscious';
241
+ }
242
+
159
243
  export interface PersistentReaderConfig extends SubconsciousConfigBase {
160
244
  reader?: 'persistent';
161
245
  /**
162
246
  * Registry + participant name. Default 'Subconscious' — following the
163
247
  * Context Manager precedent: a title-case functional voice, not an
164
- * agent-prefixed identifier.
248
+ * agent-prefixed identifier. persistentReaderName applies the default.
165
249
  */
166
250
  name?: string;
167
251
  /** Model id; defaults to the primary agent's (same-model side-process).
@@ -171,29 +255,53 @@ export interface PersistentReaderConfig extends SubconsciousConfigBase {
171
255
  * `thinkingBinding` set its own. */
172
256
  model?: string;
173
257
  /**
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).
258
+ * The persistent reader's system prompt (its AgentConfig.systemPrompt).
259
+ * Who writes it and what it says, a policy for the reader tools
260
+ * included, are the host's choice. Canary before fleet use (issue #77).
177
261
  */
178
262
  systemPrompt: string;
179
263
  /** WindowedPassthroughStrategy re-anchor fraction (default 0.5). */
180
264
  reAnchorFraction?: number;
265
+ /**
266
+ * How a resident's rounds go out in the subconscious's requests once its
267
+ * window follows the presentation index (#291, answer 13). Its own rounds go
268
+ * as stored either way.
269
+ *
270
+ * - `'transcript'` (the default): a line per tool call (the tool's name and
271
+ * its input in brief) and per result (its outcome in brief), the text as
272
+ * stored, and none of the resident's thinking (foreign-rounds.ts).
273
+ * - `'raw'`: as stored, as 0.30.0 sent them: the resident's thinking
274
+ * blocks, its tool calls and their results, in the subconscious's
275
+ * requests under the resident's name. A provider pairs tool blocks only
276
+ * in the turns of the agent that made the call, so it may refuse them, or
277
+ * the formatter may move them into the subconscious's own turns, where
278
+ * they read as calls it made.
279
+ *
280
+ * A thinking block's signature binds the block to the prefix the resident
281
+ * was sent, so the block can't be sent as part of another agent's prefix;
282
+ * its text could be. Leaving the text out is the transcript's choice, not
283
+ * the provider's.
284
+ */
285
+ foreignRounds?: 'transcript' | 'raw';
181
286
  }
182
287
 
183
288
  export interface ForkReaderConfig extends SubconsciousConfigBase {
184
289
  reader: 'forks';
185
290
  /**
186
291
  * 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.
292
+ * resident's whole prefix, and which model reads it is recorded on each
293
+ * fork's `dendrite:agent-created` trace and is what a host's
294
+ * `dendrite.beforeDerive` can decide on. The resident's own model, or
295
+ * another model stated here. `create` refuses the omission.
190
296
  */
191
297
  model: string;
192
298
  /**
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.
299
+ * The reader's voice block: text the host supplies, handed to `framing`.
300
+ * The default framing (defaultReaderFraming) puts it in the one message a
301
+ * fork receives after the fork point, with the notice, the standing
302
+ * dispositions and the held traffic; it is never a system prompt. Who
303
+ * writes it and what it says, a policy for the reader tools included,
304
+ * are the host's choice.
197
305
  */
198
306
  voice: string;
199
307
  /**
@@ -211,7 +319,13 @@ export interface ForkReaderConfig extends SubconsciousConfigBase {
211
319
  * framework stores whatever this returns as a user message with
212
320
  * `metadata.kind = 'tune-out-reader-framing'`; the notice, voice,
213
321
  * dispositions and held traffic are in the context, and nothing else
214
- * about the fork's prompt is fixed by the path.
322
+ * about the fork's prompt is fixed by the path. note_disposition tells a
323
+ * reader that a disposition it records is shown to the reader each time
324
+ * it runs, and a fork is shown them only as its framing puts them
325
+ * (`dispositions`), so a framing that leaves them out makes that untrue.
326
+ * Likewise the held traffic reaches the fork only as its framing puts it
327
+ * (`backlog`): the fork's view, the residents', leaves the raw rows out,
328
+ * as it leaves out all held traffic (the default framing shows them).
215
329
  */
216
330
  framing?: (context: ReaderFramingContext) => ContentBlock[];
217
331
  }
@@ -232,13 +346,20 @@ export interface ReaderFramingContext {
232
346
  /** Standing dispositions recorded on the epoch, or null. */
233
347
  dispositions: string | null;
234
348
  /**
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.
349
+ * The traffic held since the last look, oldest first, as stored; empty
350
+ * when nothing arrived. A held message is stored as it arrives, the
351
+ * resident's turn alive or not (#291), so the stored rows are all of it,
352
+ * but for one a surgery's hold or a manager awaiting recovery keeps in
353
+ * memory: it isn't stored yet, and is in the first backlog after it is.
239
354
  */
240
355
  backlog: StoredMessage[];
241
- /** How many of `backlog`'s last entries are still queued behind the resident's turn. Absent is none. */
356
+ /**
357
+ * Was how many of `backlog`'s last entries were still queued behind the
358
+ * resident's turn. Since #291 nothing is queued, so the framework never
359
+ * sets it.
360
+ *
361
+ * @deprecated Never set since #291; removed in a later release.
362
+ */
242
363
  queuedCount?: number;
243
364
  }
244
365
 
@@ -259,15 +380,11 @@ export function defaultReaderFraming(ctx: ReaderFramingContext): ContentBlock[]
259
380
  return content;
260
381
  }
261
382
  const n = ctx.backlog.length;
262
- const q = Math.min(Math.max(0, Math.floor(ctx.queuedCount ?? 0)), n);
263
383
  const lines: string[] = [`\n[Held in ${ctx.channelId} since your last look: ${n} message${n === 1 ? '' : 's'}]`];
264
384
  const flush = () => {
265
385
  if (lines.length > 0) content.push({ type: 'text', text: lines.splice(0).join('\n') });
266
386
  };
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);
387
+ for (const message of ctx.backlog) {
271
388
  const author = (message.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? message.participant;
272
389
  const text = message.content
273
390
  .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
@@ -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
+ }