@animalabs/agent-framework 0.22.2 → 0.22.3

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 (935) hide show
  1. package/README.md +3 -1
  2. package/dist/src/agent.d.ts +77 -12
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +136 -40
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.js +1 -1
  7. package/dist/src/api/mcp-server-core.js.map +1 -1
  8. package/dist/src/api/server.d.ts.map +1 -1
  9. package/dist/src/api/server.js +5 -1
  10. package/dist/src/api/server.js.map +1 -1
  11. package/dist/src/api/types.d.ts +1 -1
  12. package/dist/src/api/types.d.ts.map +1 -1
  13. package/dist/src/branch-names.d.ts +14 -0
  14. package/dist/src/branch-names.d.ts.map +1 -0
  15. package/dist/src/branch-names.js +22 -0
  16. package/dist/src/branch-names.js.map +1 -0
  17. package/dist/src/closest-names.d.ts +28 -0
  18. package/dist/src/closest-names.d.ts.map +1 -0
  19. package/dist/src/closest-names.js +98 -0
  20. package/dist/src/closest-names.js.map +1 -0
  21. package/dist/src/code-execution/py-runner.d.ts +16 -5
  22. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  23. package/dist/src/code-execution/py-runner.js +186 -74
  24. package/dist/src/code-execution/py-runner.js.map +1 -1
  25. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  26. package/dist/src/code-execution/runtime-py.js +49 -3
  27. package/dist/src/code-execution/runtime-py.js.map +1 -1
  28. package/dist/src/code-execution/tool-definition.d.ts +12 -1
  29. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  30. package/dist/src/code-execution/tool-definition.js +21 -3
  31. package/dist/src/code-execution/tool-definition.js.map +1 -1
  32. package/dist/src/config-checks.d.ts +28 -0
  33. package/dist/src/config-checks.d.ts.map +1 -0
  34. package/dist/src/config-checks.js +52 -0
  35. package/dist/src/config-checks.js.map +1 -0
  36. package/dist/src/deadline-timer.d.ts +21 -0
  37. package/dist/src/deadline-timer.d.ts.map +1 -0
  38. package/dist/src/deadline-timer.js +43 -0
  39. package/dist/src/deadline-timer.js.map +1 -0
  40. package/dist/src/error-message.d.ts +30 -0
  41. package/dist/src/error-message.d.ts.map +1 -0
  42. package/dist/src/error-message.js +50 -0
  43. package/dist/src/error-message.js.map +1 -0
  44. package/dist/src/framework.d.ts +828 -88
  45. package/dist/src/framework.d.ts.map +1 -1
  46. package/dist/src/framework.js +5011 -1326
  47. package/dist/src/framework.js.map +1 -1
  48. package/dist/src/gate/event-gate.d.ts +8 -0
  49. package/dist/src/gate/event-gate.d.ts.map +1 -1
  50. package/dist/src/gate/event-gate.js +27 -3
  51. package/dist/src/gate/event-gate.js.map +1 -1
  52. package/dist/src/index.d.ts +4 -2
  53. package/dist/src/index.d.ts.map +1 -1
  54. package/dist/src/index.js +4 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/mcpl/channel-registry.d.ts +144 -6
  57. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  58. package/dist/src/mcpl/channel-registry.js +424 -263
  59. package/dist/src/mcpl/channel-registry.js.map +1 -1
  60. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  61. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  62. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  63. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  64. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  65. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  66. package/dist/src/mcpl/conversation-router.js +23 -8
  67. package/dist/src/mcpl/conversation-router.js.map +1 -1
  68. package/dist/src/mcpl/convert-block.d.ts +20 -0
  69. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  70. package/dist/src/mcpl/convert-block.js +89 -0
  71. package/dist/src/mcpl/convert-block.js.map +1 -0
  72. package/dist/src/mcpl/errors.d.ts +13 -0
  73. package/dist/src/mcpl/errors.d.ts.map +1 -1
  74. package/dist/src/mcpl/errors.js +28 -0
  75. package/dist/src/mcpl/errors.js.map +1 -1
  76. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  77. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  78. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  79. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  80. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  81. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  82. package/dist/src/mcpl/inbound-source.js +83 -0
  83. package/dist/src/mcpl/inbound-source.js.map +1 -0
  84. package/dist/src/mcpl/index.d.ts +3 -1
  85. package/dist/src/mcpl/index.d.ts.map +1 -1
  86. package/dist/src/mcpl/index.js +3 -1
  87. package/dist/src/mcpl/index.js.map +1 -1
  88. package/dist/src/mcpl/inference-router.d.ts +15 -11
  89. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  90. package/dist/src/mcpl/inference-router.js +153 -84
  91. package/dist/src/mcpl/inference-router.js.map +1 -1
  92. package/dist/src/mcpl/push-coalescer.d.ts +33 -11
  93. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  94. package/dist/src/mcpl/push-coalescer.js +52 -20
  95. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  96. package/dist/src/mcpl/push-handler.d.ts +16 -9
  97. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  98. package/dist/src/mcpl/push-handler.js +18 -83
  99. package/dist/src/mcpl/push-handler.js.map +1 -1
  100. package/dist/src/mcpl/reference-fetcher.d.ts +14 -2
  101. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  102. package/dist/src/mcpl/reference-fetcher.js +72 -30
  103. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  104. package/dist/src/mcpl/references.d.ts +47 -12
  105. package/dist/src/mcpl/references.d.ts.map +1 -1
  106. package/dist/src/mcpl/references.js +74 -21
  107. package/dist/src/mcpl/references.js.map +1 -1
  108. package/dist/src/mcpl/server-connection.d.ts +89 -10
  109. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  110. package/dist/src/mcpl/server-connection.js +405 -129
  111. package/dist/src/mcpl/server-connection.js.map +1 -1
  112. package/dist/src/mcpl/server-registry.d.ts +10 -1
  113. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  114. package/dist/src/mcpl/server-registry.js +14 -3
  115. package/dist/src/mcpl/server-registry.js.map +1 -1
  116. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  117. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  118. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  119. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  120. package/dist/src/mcpl/transport.d.ts +51 -2
  121. package/dist/src/mcpl/transport.d.ts.map +1 -1
  122. package/dist/src/mcpl/transport.js +114 -20
  123. package/dist/src/mcpl/transport.js.map +1 -1
  124. package/dist/src/mcpl/types.d.ts +14 -3
  125. package/dist/src/mcpl/types.d.ts.map +1 -1
  126. package/dist/src/mcpl/types.js.map +1 -1
  127. package/dist/src/mcpl/visible-content.d.ts +15 -0
  128. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  129. package/dist/src/mcpl/visible-content.js +25 -0
  130. package/dist/src/mcpl/visible-content.js.map +1 -1
  131. package/dist/src/module-registry.d.ts +19 -1
  132. package/dist/src/module-registry.d.ts.map +1 -1
  133. package/dist/src/module-registry.js +54 -25
  134. package/dist/src/module-registry.js.map +1 -1
  135. package/dist/src/modules/api/index.js +1 -1
  136. package/dist/src/modules/api/index.js.map +1 -1
  137. package/dist/src/modules/discord/discord-js-client.d.ts +1 -0
  138. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  139. package/dist/src/modules/discord/discord-js-client.js +25 -9
  140. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  141. package/dist/src/modules/discord/index.d.ts +29 -3
  142. package/dist/src/modules/discord/index.d.ts.map +1 -1
  143. package/dist/src/modules/discord/index.js +379 -143
  144. package/dist/src/modules/discord/index.js.map +1 -1
  145. package/dist/src/modules/discord/types.d.ts +14 -1
  146. package/dist/src/modules/discord/types.d.ts.map +1 -1
  147. package/dist/src/modules/health/index.d.ts.map +1 -1
  148. package/dist/src/modules/health/index.js +2 -1
  149. package/dist/src/modules/health/index.js.map +1 -1
  150. package/dist/src/modules/history/index.d.ts +3 -1
  151. package/dist/src/modules/history/index.d.ts.map +1 -1
  152. package/dist/src/modules/history/index.js +359 -127
  153. package/dist/src/modules/history/index.js.map +1 -1
  154. package/dist/src/modules/history/semantic.d.ts +28 -1
  155. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  156. package/dist/src/modules/history/semantic.js +177 -32
  157. package/dist/src/modules/history/semantic.js.map +1 -1
  158. package/dist/src/modules/unknown-tool.d.ts +10 -0
  159. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  160. package/dist/src/modules/unknown-tool.js +17 -0
  161. package/dist/src/modules/unknown-tool.js.map +1 -0
  162. package/dist/src/modules/workspace/disk-agreement.d.ts +32 -2
  163. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  164. package/dist/src/modules/workspace/disk-agreement.js +30 -2
  165. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  166. package/dist/src/modules/workspace/effects.d.ts +64 -6
  167. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  168. package/dist/src/modules/workspace/effects.js +291 -44
  169. package/dist/src/modules/workspace/effects.js.map +1 -1
  170. package/dist/src/modules/workspace/index.d.ts +119 -23
  171. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  172. package/dist/src/modules/workspace/index.js +793 -247
  173. package/dist/src/modules/workspace/index.js.map +1 -1
  174. package/dist/src/modules/workspace/observe.d.ts +75 -8
  175. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  176. package/dist/src/modules/workspace/observe.js +174 -14
  177. package/dist/src/modules/workspace/observe.js.map +1 -1
  178. package/dist/src/modules/workspace/reconcile.d.ts +58 -7
  179. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  180. package/dist/src/modules/workspace/reconcile.js +521 -56
  181. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  182. package/dist/src/modules/workspace/types.d.ts +18 -4
  183. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  184. package/dist/src/modules/workspace/types.js.map +1 -1
  185. package/dist/src/modules/workspace/watcher.d.ts +7 -2
  186. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  187. package/dist/src/modules/workspace/watcher.js +28 -4
  188. package/dist/src/modules/workspace/watcher.js.map +1 -1
  189. package/dist/src/prose-drafts.d.ts +240 -0
  190. package/dist/src/prose-drafts.d.ts.map +1 -0
  191. package/dist/src/prose-drafts.js +362 -0
  192. package/dist/src/prose-drafts.js.map +1 -0
  193. package/dist/src/prose-segments.d.ts +21 -2
  194. package/dist/src/prose-segments.d.ts.map +1 -1
  195. package/dist/src/prose-segments.js +46 -9
  196. package/dist/src/prose-segments.js.map +1 -1
  197. package/dist/src/recovery/offline-branch.d.ts +1 -0
  198. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  199. package/dist/src/recovery/offline-branch.js +3 -1
  200. package/dist/src/recovery/offline-branch.js.map +1 -1
  201. package/dist/src/safe-slice.d.ts +5 -1
  202. package/dist/src/safe-slice.d.ts.map +1 -1
  203. package/dist/src/safe-slice.js +9 -1
  204. package/dist/src/safe-slice.js.map +1 -1
  205. package/dist/src/tool-image-ledger.d.ts +87 -19
  206. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  207. package/dist/src/tool-image-ledger.js +135 -32
  208. package/dist/src/tool-image-ledger.js.map +1 -1
  209. package/dist/src/tool-presentation.d.ts +9 -0
  210. package/dist/src/tool-presentation.d.ts.map +1 -1
  211. package/dist/src/tool-presentation.js +257 -60
  212. package/dist/src/tool-presentation.js.map +1 -1
  213. package/dist/src/tool-result-guard.d.ts.map +1 -1
  214. package/dist/src/tool-result-guard.js +7 -10
  215. package/dist/src/tool-result-guard.js.map +1 -1
  216. package/dist/src/tool-result-history.d.ts +32 -3
  217. package/dist/src/tool-result-history.d.ts.map +1 -1
  218. package/dist/src/tool-result-history.js +106 -19
  219. package/dist/src/tool-result-history.js.map +1 -1
  220. package/dist/src/tune-out/coordinator.d.ts +25 -1
  221. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  222. package/dist/src/tune-out/coordinator.js +260 -65
  223. package/dist/src/tune-out/coordinator.js.map +1 -1
  224. package/dist/src/types/agent.d.ts +17 -5
  225. package/dist/src/types/agent.d.ts.map +1 -1
  226. package/dist/src/types/events.d.ts +17 -0
  227. package/dist/src/types/events.d.ts.map +1 -1
  228. package/dist/src/types/framework.d.ts +21 -5
  229. package/dist/src/types/framework.d.ts.map +1 -1
  230. package/dist/src/types/inference-log.d.ts +1 -1
  231. package/dist/src/types/inference-log.d.ts.map +1 -1
  232. package/dist/src/types/module.d.ts +23 -4
  233. package/dist/src/types/module.d.ts.map +1 -1
  234. package/dist/src/types/trace.d.ts +95 -6
  235. package/dist/src/types/trace.d.ts.map +1 -1
  236. package/dist/src/usage/usage-tracker.d.ts +5 -0
  237. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  238. package/dist/src/usage/usage-tracker.js +35 -8
  239. package/dist/src/usage/usage-tracker.js.map +1 -1
  240. package/package.json +3 -4
  241. package/src/agent.ts +162 -40
  242. package/src/api/mcp-server-core.ts +1 -1
  243. package/src/api/server.ts +4 -1
  244. package/src/api/types.ts +1 -1
  245. package/src/branch-names.ts +19 -0
  246. package/src/closest-names.ts +94 -0
  247. package/src/code-execution/py-runner.ts +200 -76
  248. package/src/code-execution/runtime-py.ts +49 -3
  249. package/src/code-execution/tool-definition.ts +28 -4
  250. package/src/config-checks.ts +48 -0
  251. package/src/deadline-timer.ts +44 -0
  252. package/src/error-message.ts +48 -0
  253. package/src/framework.ts +5352 -1388
  254. package/src/gate/event-gate.ts +28 -3
  255. package/src/index.ts +11 -2
  256. package/src/mcpl/channel-registry.ts +517 -286
  257. package/src/mcpl/checkpoint-manager.ts +104 -29
  258. package/src/mcpl/conversation-router.ts +28 -8
  259. package/src/mcpl/convert-block.ts +97 -0
  260. package/src/mcpl/errors.ts +28 -0
  261. package/src/mcpl/hook-orchestrator.ts +84 -77
  262. package/src/mcpl/inbound-source.ts +156 -0
  263. package/src/mcpl/index.ts +10 -1
  264. package/src/mcpl/inference-router.ts +174 -99
  265. package/src/mcpl/push-coalescer.ts +60 -24
  266. package/src/mcpl/push-handler.ts +20 -90
  267. package/src/mcpl/reference-fetcher.ts +71 -26
  268. package/src/mcpl/references.ts +81 -22
  269. package/src/mcpl/server-connection.ts +427 -132
  270. package/src/mcpl/server-registry.ts +15 -4
  271. package/src/mcpl/skip-reply-wake.ts +73 -0
  272. package/src/mcpl/transport.ts +120 -18
  273. package/src/mcpl/types.ts +14 -3
  274. package/src/mcpl/visible-content.ts +27 -0
  275. package/src/module-registry.ts +58 -23
  276. package/src/modules/api/index.ts +1 -1
  277. package/src/modules/discord/discord-js-client.ts +26 -10
  278. package/src/modules/discord/index.ts +391 -158
  279. package/src/modules/discord/types.ts +15 -2
  280. package/src/modules/health/index.ts +2 -1
  281. package/src/modules/history/index.ts +410 -120
  282. package/src/modules/history/semantic.ts +189 -32
  283. package/src/modules/unknown-tool.ts +16 -0
  284. package/src/modules/workspace/disk-agreement.ts +51 -3
  285. package/src/modules/workspace/effects.ts +279 -35
  286. package/src/modules/workspace/index.ts +810 -260
  287. package/src/modules/workspace/observe.ts +194 -20
  288. package/src/modules/workspace/reconcile.ts +619 -64
  289. package/src/modules/workspace/types.ts +18 -4
  290. package/src/modules/workspace/watcher.ts +27 -4
  291. package/src/prose-drafts.ts +481 -0
  292. package/src/prose-segments.ts +51 -8
  293. package/src/recovery/offline-branch.ts +4 -1
  294. package/src/safe-slice.ts +9 -1
  295. package/src/tool-image-ledger.ts +153 -34
  296. package/src/tool-presentation.ts +224 -40
  297. package/src/tool-result-guard.ts +7 -8
  298. package/src/tool-result-history.ts +124 -20
  299. package/src/tune-out/coordinator.ts +259 -65
  300. package/src/types/agent.ts +17 -7
  301. package/src/types/events.ts +17 -0
  302. package/src/types/framework.ts +21 -5
  303. package/src/types/inference-log.ts +1 -1
  304. package/src/types/module.ts +24 -4
  305. package/src/types/trace.ts +101 -6
  306. package/src/usage/usage-tracker.ts +32 -8
  307. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  308. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  309. package/dist/test/activation-request-sanitize.test.js +0 -89
  310. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  311. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  312. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  313. package/dist/test/agent-cache-ttl.test.js +0 -41
  314. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  315. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  316. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  317. package/dist/test/agent-runtime-settings.test.js +0 -1598
  318. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  319. package/dist/test/api-admin-gate.test.d.ts +0 -2
  320. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  321. package/dist/test/api-admin-gate.test.js +0 -116
  322. package/dist/test/api-admin-gate.test.js.map +0 -1
  323. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  324. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  325. package/dist/test/assistant-persist-refusal.test.js +0 -63
  326. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  327. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  328. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  329. package/dist/test/batched-wake-locus.test.js +0 -166
  330. package/dist/test/batched-wake-locus.test.js.map +0 -1
  331. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  332. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  333. package/dist/test/busy-agent-scheduler.test.js +0 -92
  334. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  335. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  336. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  337. package/dist/test/channel-incoming-targeting.test.js +0 -81
  338. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  339. package/dist/test/channel-label-history.test.d.ts +0 -2
  340. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  341. package/dist/test/channel-label-history.test.js +0 -424
  342. package/dist/test/channel-label-history.test.js.map +0 -1
  343. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  344. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  345. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  346. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  347. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  348. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  349. package/dist/test/channel-registry-routing.test.js +0 -446
  350. package/dist/test/channel-registry-routing.test.js.map +0 -1
  351. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  352. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  353. package/dist/test/channel-registry-typing.test.js +0 -91
  354. package/dist/test/channel-registry-typing.test.js.map +0 -1
  355. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  356. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  357. package/dist/test/channels-changed-respond-first.test.js +0 -74
  358. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  359. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  360. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  361. package/dist/test/checkpoint-attribution.test.js +0 -200
  362. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  363. package/dist/test/code-execution.test.d.ts +0 -11
  364. package/dist/test/code-execution.test.d.ts.map +0 -1
  365. package/dist/test/code-execution.test.js +0 -1272
  366. package/dist/test/code-execution.test.js.map +0 -1
  367. package/dist/test/component-defaults.test.d.ts +0 -2
  368. package/dist/test/component-defaults.test.d.ts.map +0 -1
  369. package/dist/test/component-defaults.test.js +0 -54
  370. package/dist/test/component-defaults.test.js.map +0 -1
  371. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  372. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  373. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  374. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  375. package/dist/test/conversation-router.test.d.ts +0 -2
  376. package/dist/test/conversation-router.test.d.ts.map +0 -1
  377. package/dist/test/conversation-router.test.js +0 -174
  378. package/dist/test/conversation-router.test.js.map +0 -1
  379. package/dist/test/conversation-routing.test.d.ts +0 -7
  380. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  381. package/dist/test/conversation-routing.test.js +0 -621
  382. package/dist/test/conversation-routing.test.js.map +0 -1
  383. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  384. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  385. package/dist/test/deferred-wake-integrity.test.js +0 -320
  386. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  387. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  388. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  389. package/dist/test/discord-awareness-outbox.test.js +0 -371
  390. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  391. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  392. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  393. package/dist/test/discord-history-delete-window.test.js +0 -81
  394. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  395. package/dist/test/discord-history-sync.test.d.ts +0 -2
  396. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  397. package/dist/test/discord-history-sync.test.js +0 -56
  398. package/dist/test/discord-history-sync.test.js.map +0 -1
  399. package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
  400. package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
  401. package/dist/test/discord-typing-keepalive.test.js +0 -77
  402. package/dist/test/discord-typing-keepalive.test.js.map +0 -1
  403. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  404. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  405. package/dist/test/ephemeral-settle.test.js +0 -354
  406. package/dist/test/ephemeral-settle.test.js.map +0 -1
  407. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  408. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  409. package/dist/test/event-gate-passthrough.test.js +0 -289
  410. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  411. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  412. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  413. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  414. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  415. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  416. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  417. package/dist/test/event-gate-rate-limit.test.js +0 -627
  418. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  419. package/dist/test/event-gate-script.test.d.ts +0 -2
  420. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  421. package/dist/test/event-gate-script.test.js +0 -95
  422. package/dist/test/event-gate-script.test.js.map +0 -1
  423. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  424. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  425. package/dist/test/event-gate-selfwake.test.js +0 -125
  426. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  427. package/dist/test/event-gate-tags.test.d.ts +0 -2
  428. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  429. package/dist/test/event-gate-tags.test.js +0 -76
  430. package/dist/test/event-gate-tags.test.js.map +0 -1
  431. package/dist/test/event-gate.test.d.ts +0 -2
  432. package/dist/test/event-gate.test.d.ts.map +0 -1
  433. package/dist/test/event-gate.test.js +0 -919
  434. package/dist/test/event-gate.test.js.map +0 -1
  435. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  436. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  437. package/dist/test/explicit-prose-routing.test.js +0 -1715
  438. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  439. package/dist/test/framework.test.d.ts +0 -5
  440. package/dist/test/framework.test.d.ts.map +0 -1
  441. package/dist/test/framework.test.js +0 -1048
  442. package/dist/test/framework.test.js.map +0 -1
  443. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  444. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  445. package/dist/test/gather-context-timeout.test.js +0 -120
  446. package/dist/test/gather-context-timeout.test.js.map +0 -1
  447. package/dist/test/health-snapshot.test.d.ts +0 -2
  448. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  449. package/dist/test/health-snapshot.test.js +0 -74
  450. package/dist/test/health-snapshot.test.js.map +0 -1
  451. package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
  452. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  453. package/dist/test/helpers/coalescing-fixture.js +0 -152
  454. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  455. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  456. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  457. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  458. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  459. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  460. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  461. package/dist/test/helpers/mock-membrane.js +0 -168
  462. package/dist/test/helpers/mock-membrane.js.map +0 -1
  463. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  464. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  465. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  466. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  467. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  468. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  469. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  470. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  471. package/dist/test/hide-then-turn.test.d.ts +0 -2
  472. package/dist/test/hide-then-turn.test.d.ts.map +0 -1
  473. package/dist/test/hide-then-turn.test.js +0 -101
  474. package/dist/test/hide-then-turn.test.js.map +0 -1
  475. package/dist/test/history-module-overview.test.d.ts +0 -2
  476. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  477. package/dist/test/history-module-overview.test.js +0 -711
  478. package/dist/test/history-module-overview.test.js.map +0 -1
  479. package/dist/test/history-module-realstore.test.d.ts +0 -2
  480. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  481. package/dist/test/history-module-realstore.test.js +0 -363
  482. package/dist/test/history-module-realstore.test.js.map +0 -1
  483. package/dist/test/history-module-semantic.test.d.ts +0 -2
  484. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  485. package/dist/test/history-module-semantic.test.js +0 -213
  486. package/dist/test/history-module-semantic.test.js.map +0 -1
  487. package/dist/test/history-module-ux.test.d.ts +0 -2
  488. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  489. package/dist/test/history-module-ux.test.js +0 -304
  490. package/dist/test/history-module-ux.test.js.map +0 -1
  491. package/dist/test/history-module.test.d.ts +0 -2
  492. package/dist/test/history-module.test.d.ts.map +0 -1
  493. package/dist/test/history-module.test.js +0 -619
  494. package/dist/test/history-module.test.js.map +0 -1
  495. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  496. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  497. package/dist/test/history-semantic-review-findings.test.js +0 -338
  498. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  499. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  500. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  501. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  502. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  503. package/dist/test/host-quiesce.test.d.ts +0 -2
  504. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  505. package/dist/test/host-quiesce.test.js +0 -988
  506. package/dist/test/host-quiesce.test.js.map +0 -1
  507. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  508. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  509. package/dist/test/inference-exhaustion-rewind.test.js +0 -387
  510. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  511. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  512. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  513. package/dist/test/inference-failure-bounds.test.js +0 -1202
  514. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  515. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  516. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  517. package/dist/test/inference-failure-observability.test.js +0 -84
  518. package/dist/test/inference-failure-observability.test.js.map +0 -1
  519. package/dist/test/journal-tool.test.d.ts +0 -2
  520. package/dist/test/journal-tool.test.d.ts.map +0 -1
  521. package/dist/test/journal-tool.test.js +0 -33
  522. package/dist/test/journal-tool.test.js.map +0 -1
  523. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  524. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  525. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  526. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  527. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  528. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  529. package/dist/test/kv-unified-wiring.test.js +0 -95
  530. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  531. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  532. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  533. package/dist/test/late-events-after-stop.test.js +0 -122
  534. package/dist/test/late-events-after-stop.test.js.map +0 -1
  535. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  536. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  537. package/dist/test/liveness-watchdog.test.js +0 -94
  538. package/dist/test/liveness-watchdog.test.js.map +0 -1
  539. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  540. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  541. package/dist/test/machine-close-provenance.test.js +0 -116
  542. package/dist/test/machine-close-provenance.test.js.map +0 -1
  543. package/dist/test/maintenance-timer.test.d.ts +0 -2
  544. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  545. package/dist/test/maintenance-timer.test.js +0 -1827
  546. package/dist/test/maintenance-timer.test.js.map +0 -1
  547. package/dist/test/mcp-server-bin.test.d.ts +0 -2
  548. package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
  549. package/dist/test/mcp-server-bin.test.js +0 -142
  550. package/dist/test/mcp-server-bin.test.js.map +0 -1
  551. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  552. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  553. package/dist/test/mcpl-access-provider.test.js +0 -20
  554. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  555. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  556. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  557. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  558. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  559. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  560. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  561. package/dist/test/mcpl-capability-grant.test.js +0 -111
  562. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  563. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  564. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  565. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  566. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  567. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  568. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  569. package/dist/test/mcpl-child-env.test.js +0 -158
  570. package/dist/test/mcpl-child-env.test.js.map +0 -1
  571. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
  572. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
  573. package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
  574. package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
  575. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  576. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  577. package/dist/test/mcpl-coalescing.test.js +0 -1125
  578. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  579. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  580. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  581. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  582. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  583. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  584. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  585. package/dist/test/mcpl-empty-content.test.js +0 -368
  586. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  587. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  588. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  589. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  590. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  591. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  592. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  593. package/dist/test/mcpl-list-status.test.js +0 -82
  594. package/dist/test/mcpl-list-status.test.js.map +0 -1
  595. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  596. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  597. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  598. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  599. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  600. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  601. package/dist/test/mcpl-push-convert.test.js +0 -42
  602. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  603. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  604. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  605. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  606. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  607. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  608. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  609. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
  610. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  611. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  612. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  613. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  614. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  615. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  616. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  617. package/dist/test/mcpl-reference-fields.test.js +0 -149
  618. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  619. package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
  620. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
  621. package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
  622. package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
  623. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  624. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  625. package/dist/test/mcpl-request-timeout.test.js +0 -138
  626. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  627. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  628. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  629. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  630. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  631. package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
  632. package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
  633. package/dist/test/mcpl-stderr-tail.test.js +0 -210
  634. package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
  635. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  636. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  637. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  638. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  639. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  640. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  641. package/dist/test/mcpl-tool-classes.test.js +0 -80
  642. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  643. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  644. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  645. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  646. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  647. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  648. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  649. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  650. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  651. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  652. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  653. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  654. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  655. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  656. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  657. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  658. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  659. package/dist/test/mcpl-transport-close.test.d.ts +0 -2
  660. package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
  661. package/dist/test/mcpl-transport-close.test.js +0 -373
  662. package/dist/test/mcpl-transport-close.test.js.map +0 -1
  663. package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
  664. package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
  665. package/dist/test/mcpl-transport-spawn.test.js +0 -58
  666. package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
  667. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  668. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  669. package/dist/test/mcpl-ws-transport.test.js +0 -259
  670. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  671. package/dist/test/module-context-integration.test.d.ts +0 -18
  672. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  673. package/dist/test/module-context-integration.test.js +0 -181
  674. package/dist/test/module-context-integration.test.js.map +0 -1
  675. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  676. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  677. package/dist/test/offline-recovery-branch.test.js +0 -570
  678. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  679. package/dist/test/operator-surgery.test.d.ts +0 -2
  680. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  681. package/dist/test/operator-surgery.test.js +0 -595
  682. package/dist/test/operator-surgery.test.js.map +0 -1
  683. package/dist/test/ops-alert.test.d.ts +0 -2
  684. package/dist/test/ops-alert.test.d.ts.map +0 -1
  685. package/dist/test/ops-alert.test.js +0 -153
  686. package/dist/test/ops-alert.test.js.map +0 -1
  687. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  688. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  689. package/dist/test/overbudget-drain-breaker.test.js +0 -198
  690. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  691. package/dist/test/pack-installs-alone.test.d.ts +0 -2
  692. package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
  693. package/dist/test/pack-installs-alone.test.js +0 -284
  694. package/dist/test/pack-installs-alone.test.js.map +0 -1
  695. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  696. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  697. package/dist/test/per-agent-delivery.test.js +0 -108
  698. package/dist/test/per-agent-delivery.test.js.map +0 -1
  699. package/dist/test/physical-window-restart.test.d.ts +0 -16
  700. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  701. package/dist/test/physical-window-restart.test.js +0 -487
  702. package/dist/test/physical-window-restart.test.js.map +0 -1
  703. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  704. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  705. package/dist/test/placed-reaction-baseline.test.js +0 -312
  706. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  707. package/dist/test/present-while-acting.test.d.ts +0 -19
  708. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  709. package/dist/test/present-while-acting.test.js +0 -1649
  710. package/dist/test/present-while-acting.test.js.map +0 -1
  711. package/dist/test/prose-segments.test.d.ts +0 -2
  712. package/dist/test/prose-segments.test.d.ts.map +0 -1
  713. package/dist/test/prose-segments.test.js +0 -52
  714. package/dist/test/prose-segments.test.js.map +0 -1
  715. package/dist/test/prose-stream-router.test.d.ts +0 -2
  716. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  717. package/dist/test/prose-stream-router.test.js +0 -171
  718. package/dist/test/prose-stream-router.test.js.map +0 -1
  719. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  720. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  721. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  722. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  723. package/dist/test/provider-waits.test.d.ts +0 -2
  724. package/dist/test/provider-waits.test.d.ts.map +0 -1
  725. package/dist/test/provider-waits.test.js +0 -2259
  726. package/dist/test/provider-waits.test.js.map +0 -1
  727. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  728. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  729. package/dist/test/puppet-tool-call.test.js +0 -226
  730. package/dist/test/puppet-tool-call.test.js.map +0 -1
  731. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  732. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  733. package/dist/test/puppet-turn-reservation.test.js +0 -139
  734. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  735. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  736. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  737. package/dist/test/reaction-baseline-platform.test.js +0 -124
  738. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  739. package/dist/test/record-journal.test.d.ts +0 -6
  740. package/dist/test/record-journal.test.d.ts.map +0 -1
  741. package/dist/test/record-journal.test.js +0 -269
  742. package/dist/test/record-journal.test.js.map +0 -1
  743. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  744. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  745. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  746. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  747. package/dist/test/refusal-rewind.test.d.ts +0 -2
  748. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  749. package/dist/test/refusal-rewind.test.js +0 -117
  750. package/dist/test/refusal-rewind.test.js.map +0 -1
  751. package/dist/test/refusal-single-call.test.d.ts +0 -2
  752. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  753. package/dist/test/refusal-single-call.test.js +0 -177
  754. package/dist/test/refusal-single-call.test.js.map +0 -1
  755. package/dist/test/release-bump.test.d.ts +0 -2
  756. package/dist/test/release-bump.test.d.ts.map +0 -1
  757. package/dist/test/release-bump.test.js +0 -248
  758. package/dist/test/release-bump.test.js.map +0 -1
  759. package/dist/test/release-changelog.test.d.ts +0 -2
  760. package/dist/test/release-changelog.test.d.ts.map +0 -1
  761. package/dist/test/release-changelog.test.js +0 -159
  762. package/dist/test/release-changelog.test.js.map +0 -1
  763. package/dist/test/release-holds.test.d.ts +0 -2
  764. package/dist/test/release-holds.test.d.ts.map +0 -1
  765. package/dist/test/release-holds.test.js +0 -456
  766. package/dist/test/release-holds.test.js.map +0 -1
  767. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  768. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  769. package/dist/test/same-round-think-policy.test.js +0 -853
  770. package/dist/test/same-round-think-policy.test.js.map +0 -1
  771. package/dist/test/save-recent-image.test.d.ts +0 -26
  772. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  773. package/dist/test/save-recent-image.test.js +0 -974
  774. package/dist/test/save-recent-image.test.js.map +0 -1
  775. package/dist/test/shutdown-errors.test.d.ts +0 -2
  776. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  777. package/dist/test/shutdown-errors.test.js +0 -662
  778. package/dist/test/shutdown-errors.test.js.map +0 -1
  779. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  780. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  781. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  782. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  783. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  784. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  785. package/dist/test/silent-heartbeat.test.js +0 -165
  786. package/dist/test/silent-heartbeat.test.js.map +0 -1
  787. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  788. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  789. package/dist/test/state-scaling-e2e.test.js +0 -206
  790. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  791. package/dist/test/state-scaling.test.d.ts +0 -12
  792. package/dist/test/state-scaling.test.d.ts.map +0 -1
  793. package/dist/test/state-scaling.test.js +0 -251
  794. package/dist/test/state-scaling.test.js.map +0 -1
  795. package/dist/test/stop-with-host-operations.test.d.ts +0 -20
  796. package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
  797. package/dist/test/stop-with-host-operations.test.js +0 -1174
  798. package/dist/test/stop-with-host-operations.test.js.map +0 -1
  799. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  800. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  801. package/dist/test/stop-with-work-in-flight.test.js +0 -784
  802. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  803. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  804. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  805. package/dist/test/surgery-marks-receipt.test.js +0 -540
  806. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  807. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  808. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  809. package/dist/test/thinking-token-stamp.test.js +0 -129
  810. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  811. package/dist/test/timezone.test.d.ts +0 -2
  812. package/dist/test/timezone.test.d.ts.map +0 -1
  813. package/dist/test/timezone.test.js +0 -18
  814. package/dist/test/timezone.test.js.map +0 -1
  815. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  816. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  817. package/dist/test/tool-batch-hook.test.js +0 -275
  818. package/dist/test/tool-batch-hook.test.js.map +0 -1
  819. package/dist/test/tool-policy.test.d.ts +0 -2
  820. package/dist/test/tool-policy.test.d.ts.map +0 -1
  821. package/dist/test/tool-policy.test.js +0 -51
  822. package/dist/test/tool-policy.test.js.map +0 -1
  823. package/dist/test/tool-presentation.test.d.ts +0 -2
  824. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  825. package/dist/test/tool-presentation.test.js +0 -268
  826. package/dist/test/tool-presentation.test.js.map +0 -1
  827. package/dist/test/tool-result-content.test.d.ts +0 -2
  828. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  829. package/dist/test/tool-result-content.test.js +0 -37
  830. package/dist/test/tool-result-content.test.js.map +0 -1
  831. package/dist/test/tool-result-guard.test.d.ts +0 -2
  832. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  833. package/dist/test/tool-result-guard.test.js +0 -3729
  834. package/dist/test/tool-result-guard.test.js.map +0 -1
  835. package/dist/test/tool-result-history.test.d.ts +0 -7
  836. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  837. package/dist/test/tool-result-history.test.js +0 -156
  838. package/dist/test/tool-result-history.test.js.map +0 -1
  839. package/dist/test/tool-result-spill.test.d.ts +0 -13
  840. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  841. package/dist/test/tool-result-spill.test.js +0 -586
  842. package/dist/test/tool-result-spill.test.js.map +0 -1
  843. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  844. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  845. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  846. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  847. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  848. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  849. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  850. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  851. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  852. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  853. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  854. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  855. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  856. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  857. package/dist/test/trunk-channel-routing.test.js +0 -652
  858. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  859. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  860. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  861. package/dist/test/tune-out-e2e.test.js +0 -142
  862. package/dist/test/tune-out-e2e.test.js.map +0 -1
  863. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  864. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  865. package/dist/test/tune-out-gate-composition.test.js +0 -221
  866. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  867. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  868. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  869. package/dist/test/tune-out-lifecycle.test.js +0 -121
  870. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  871. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  872. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  873. package/dist/test/turn-trigger-provenance.test.js +0 -137
  874. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  875. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  876. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  877. package/dist/test/unconvertible-tool-result.test.js +0 -358
  878. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  879. package/dist/test/unsent-compiles.test.d.ts +0 -2
  880. package/dist/test/unsent-compiles.test.d.ts.map +0 -1
  881. package/dist/test/unsent-compiles.test.js +0 -139
  882. package/dist/test/unsent-compiles.test.js.map +0 -1
  883. package/dist/test/usage-accounting.test.d.ts +0 -2
  884. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  885. package/dist/test/usage-accounting.test.js +0 -13
  886. package/dist/test/usage-accounting.test.js.map +0 -1
  887. package/dist/test/usage-tracker.test.d.ts +0 -2
  888. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  889. package/dist/test/usage-tracker.test.js +0 -129
  890. package/dist/test/usage-tracker.test.js.map +0 -1
  891. package/dist/test/utils-surface.test.d.ts +0 -2
  892. package/dist/test/utils-surface.test.d.ts.map +0 -1
  893. package/dist/test/utils-surface.test.js +0 -145
  894. package/dist/test/utils-surface.test.js.map +0 -1
  895. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  896. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  897. package/dist/test/workspace-branch-continuation.test.js +0 -183
  898. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  899. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  900. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  901. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  902. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  903. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  904. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  905. package/dist/test/workspace-path-containment.test.js +0 -91
  906. package/dist/test/workspace-path-containment.test.js.map +0 -1
  907. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  908. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  909. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  910. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  911. package/dist/test/workspace-read-image.test.d.ts +0 -2
  912. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  913. package/dist/test/workspace-read-image.test.js +0 -597
  914. package/dist/test/workspace-read-image.test.js.map +0 -1
  915. package/dist/test/workspace-read-text.test.d.ts +0 -2
  916. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  917. package/dist/test/workspace-read-text.test.js +0 -101
  918. package/dist/test/workspace-read-text.test.js.map +0 -1
  919. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  920. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  921. package/dist/test/workspace-restore-order.test.js +0 -140
  922. package/dist/test/workspace-restore-order.test.js.map +0 -1
  923. package/dist/test/workspace-three-way.test.d.ts +0 -12
  924. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  925. package/dist/test/workspace-three-way.test.js +0 -4094
  926. package/dist/test/workspace-three-way.test.js.map +0 -1
  927. package/dist/test/workspace-watcher.test.d.ts +0 -2
  928. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  929. package/dist/test/workspace-watcher.test.js +0 -281
  930. package/dist/test/workspace-watcher.test.js.map +0 -1
  931. package/dist/test/xml-round-persist.test.d.ts +0 -19
  932. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  933. package/dist/test/xml-round-persist.test.js +0 -235
  934. package/dist/test/xml-round-persist.test.js.map +0 -1
  935. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -49,7 +49,9 @@ import type { ToolDefinition, ToolCall, ToolResult, ProcessEvent } from '../../t
49
49
  import type { EventResponse, ProcessState } from '../../types/module.js';
50
50
  import type { SearchWorkerMessage, SearchWorkerMatch } from './search-regex-worker.js';
51
51
  import type { ChannelRegistry } from '../../mcpl/channel-registry.js';
52
- import { SemanticIndexClient, SemanticIndexer, messageIndexText, type SemanticIndexConfig, type SyncReport, type PendingChanges } from './semantic.js';
52
+ import { EmbedServiceError, SemanticIndexClient, SemanticIndexer, messageIndexText, type SearchResponse, type SemanticIndexConfig, type SyncReport, type PendingChanges } from './semantic.js';
53
+ import { unknownToolMessage } from '../unknown-tool.js';
54
+ import { safeSlice } from '../../safe-slice.js';
53
55
 
54
56
  // ============================================================================
55
57
  // Tool input shapes
@@ -177,10 +179,15 @@ const AROUND_MAX = 100;
177
179
  * see a handful; past this the call fails loudly rather than guessing. */
178
180
  const AROUND_TIE_CAP = 5000;
179
181
 
180
- function tieCapError(anchorId: string, n: number): Error {
182
+ function tieCapError(anchorId: string, ms: number, channelId: string | undefined): Error {
183
+ const at = new Date(ms).toISOString();
184
+ // The channel is the agent's to send back, so it's quoted whole: a cut one
185
+ // couldn't be sent. It's one the store holds, with all those messages in it.
186
+ const inChannel = channelId !== undefined ? ` and "channelId" set to ${JSON.stringify(channelId)}` : '';
181
187
  return new Error(
182
- `Message ${anchorId} shares its timestamp with ${n} messages (more than ${AROUND_TIE_CAP}), too many to order ` +
183
- 'around it. Use extract with from/to set to that instant instead.',
188
+ `Nothing was read: more than ${AROUND_TIE_CAP} messages share the timestamp of message ${shown(anchorId)}, ${at}, too ` +
189
+ `many for aroundId to put in order. To read them, call history--extract with "from" and "to" both set to ${at}` +
190
+ `${inChannel}; it pages through them with "offset" and "limit".`,
184
191
  );
185
192
  }
186
193
 
@@ -208,9 +215,13 @@ const SEMANTIC_DEFAULT_LIMIT = 10;
208
215
  const SEMANTIC_MAX_LIMIT = 50;
209
216
  /** Largest k sent to the embed-service when over-fetching past dropped hits (its own cap is 200). */
210
217
  const SEMANTIC_MAX_FETCH = 200;
211
- /** Local snippet: the leading SEMANTIC_SNIPPET_CHARS of the current text. */
218
+ /**
219
+ * Local snippet: the leading SEMANTIC_SNIPPET_CHARS of the current text, one
220
+ * fewer where the last would split a surrogate pair, since the snippet reaches
221
+ * the agent's context.
222
+ */
212
223
  function snippetOf(text: string): string {
213
- return text.length <= SEMANTIC_SNIPPET_CHARS ? text : `${text.slice(0, SEMANTIC_SNIPPET_CHARS)}…`;
224
+ return text.length <= SEMANTIC_SNIPPET_CHARS ? text : `${safeSlice(text, 0, SEMANTIC_SNIPPET_CHARS)}…`;
214
225
  }
215
226
  const SEMANTIC_SNIPPET_CHARS = 400;
216
227
 
@@ -336,16 +347,20 @@ export class HistoryModule implements Module {
336
347
  * you're trying to deliver a message, nonsense advice from a read-only
337
348
  * history tool. This module always uses its own read-only-appropriate
338
349
  * wording instead, regardless of which branch of the live resolver
339
- * produced the miss.
350
+ * produced the miss. Its refusals open with "Nothing was read", which
351
+ * holds because every tool that resolves a channel here only reads (see
352
+ * the note above the helpers).
340
353
  */
341
354
  private resolveChannel(input: string | undefined): string | undefined {
342
355
  if (!input || !this.channelRegistry) return input;
343
356
  const resolved = this.channelRegistry.resolveProseTargetDurable(input);
344
357
  if (!('error' in resolved)) return resolved.channelId;
345
358
  if (input.startsWith('#') || input.startsWith('@') || DM_MENTION_RE.test(input)) {
346
- const suggestions = resolved.candidates?.length ? ` Did you mean: ${resolved.candidates.join(', ')}?` : '';
359
+ const suggestions = resolved.candidates?.length ? ` Did you mean ${quotedList(resolved.candidates, 'or')}?` : '';
347
360
  throw new Error(
348
- `No channel history found for "${input}" — it may never have been seen by this resident, or predates this feature.${suggestions}`,
361
+ `Nothing was read: no channel history matches ${shown(input)}. This host may never have seen a channel by that ` +
362
+ `name, or saw it before it kept channel names.${suggestions} Each message history--search returns carries ` +
363
+ 'its "channelId", so searching for something said in that channel shows its id; pass that id as "channelId".',
349
364
  );
350
365
  }
351
366
  return input;
@@ -390,9 +405,15 @@ export class HistoryModule implements Module {
390
405
  ? cm.queryMessagesByTimeAndChannel({ fromMs: ms, toMs: ms, channelId, limit: 0 }).matchedCount
391
406
  : cm.queryMessagesByTime({ fromMs: ms, toMs: ms, limit: AROUND_TIE_CAP + 1 }).messages.length;
392
407
  if (n > AROUND_TIE_CAP) {
408
+ // The count is by channel alone, so an author filter can't bring it down.
409
+ const at = new Date(ms).toISOString();
393
410
  throw new Error(
394
- `More than ${AROUND_TIE_CAP} messages share the instant ${new Date(ms).toISOString()}; a continuation can't step ` +
395
- 'through them. Narrow the search with channelId/author instead.',
411
+ `Nothing was searched: more than ${AROUND_TIE_CAP} messages share the instant ${at}, too many for a ` +
412
+ `continuation to step past. To read them, call history--extract with "from" and "to" both set to ${at}` +
413
+ (channelId !== undefined
414
+ ? ' and the same "channelId"; it pages through them with "offset" and "limit".'
415
+ : '; it pages through them with "offset" and "limit". Searching one channel, with "channelId", may also ' +
416
+ 'bring them under the limit.'),
396
417
  );
397
418
  }
398
419
  return n;
@@ -450,13 +471,19 @@ export class HistoryModule implements Module {
450
471
  q: { fromMs?: number; toMs?: number; channelId?: string },
451
472
  ): { windowOffset: number; shift: number } {
452
473
  if (windowOffset === 0) {
453
- if (afterId != null) throw new Error('"afterId" only applies together with a windowOffset > 0.');
474
+ if (afterId != null) {
475
+ throw new Error(
476
+ 'Nothing was read: "afterId" goes with a "windowOffset" above 0, both copied from a truncated response\'s ' +
477
+ '"resume". For a first page, leave afterId out; to continue, copy the fields of that "resume" into the call.',
478
+ );
479
+ }
454
480
  return { windowOffset, shift: 0 };
455
481
  }
456
482
  if (afterId == null) {
457
483
  throw new Error(
458
- '"windowOffset" needs the "afterId" from the same `resume` object — without it a message added or removed ' +
459
- 'since the previous call would be silently skipped. Pass the whole `resume` object back.',
484
+ 'Nothing was read: "windowOffset" needs the "afterId" from the same "resume", or a message added or removed ' +
485
+ 'since the previous call would be skipped unseen. Copy the fields of the previous response\'s "resume" into ' +
486
+ 'the call.',
460
487
  );
461
488
  }
462
489
  const cm = this.cm as ContextManager;
@@ -467,9 +494,9 @@ export class HistoryModule implements Module {
467
494
  const i = around.findIndex((m) => String(m.id) === afterId);
468
495
  if (i === -1) {
469
496
  throw new Error(
470
- `The window changed since the previous call: message ${afterId}, where that scan stopped, is no longer ` +
471
- `within ${RELOCATE_RADIUS} positions of windowOffset ${windowOffset} (it may have been deleted). ` +
472
- 'Restart the scan without windowOffset/afterId, or narrow it with from/to.',
497
+ `Nothing was read: the window changed since the previous call. Message ${shown(afterId)}, where that scan stopped, ` +
498
+ `is no longer within ${RELOCATE_RADIUS} positions of windowOffset ${windowOffset}; it may have been deleted. ` +
499
+ 'Restart the scan without "windowOffset" and "afterId", over the same range or one narrowed with "from" and "to".',
473
500
  );
474
501
  }
475
502
  const relocated = lo + i + 1;
@@ -717,7 +744,7 @@ export class HistoryModule implements Module {
717
744
  excludeAuthor: EXCLUDE_AUTHOR_SCHEMA,
718
745
  order: { type: 'string', enum: ['oldest', 'newest'], description: 'Scan and return oldest-first (default) or newest-first.' },
719
746
  limit: { type: 'number', description: `Max matches to return (default ${SEARCH_DEFAULT_LIMIT}, hard cap ${SEARCH_MAX_LIMIT}). Must be a non-negative integer.` },
720
- maxScan: { type: 'number', description: `Max candidate messages to scan (default ${SEARCH_DEFAULT_MAX_SCAN}, hard cap ${SEARCH_MAX_MAX_SCAN}). Must be a non-negative integer.` },
747
+ maxScan: { type: 'number', description: `Max candidate messages to scan (default ${SEARCH_DEFAULT_MAX_SCAN}, hard cap ${SEARCH_MAX_MAX_SCAN}). Must be a whole number, 1 or more.` },
721
748
  skipSequences: {
722
749
  type: 'array',
723
750
  items: { type: 'number' },
@@ -793,7 +820,10 @@ export class HistoryModule implements Module {
793
820
  'Purely a read: nothing is written to your history. Hits are checked against your current branch ' +
794
821
  'before they come back: a message you undid or a summary from a branch you left is dropped and ' +
795
822
  'counted in index.droppedOffBranch; a message edited down to nothing searchable is dropped and ' +
796
- 'counted in index.droppedStale. Message snippets always show the current text of the message.',
823
+ 'counted in index.droppedStale. When that leaves fewer than `limit` hits and the index may hold more, it ' +
824
+ 'searches again, deeper, down to its first 200 hits; if a search after the first fails, the hits found so ' +
825
+ 'far come back, with why in index.searchAgainError. Message snippets ' +
826
+ 'always show the current text of the message.',
797
827
  inputSchema: {
798
828
  type: 'object' as const,
799
829
  properties: {
@@ -814,7 +844,13 @@ export class HistoryModule implements Module {
814
844
  async handleToolCall(call: ToolCall): Promise<ToolResult> {
815
845
  try {
816
846
  if (!this.cm) {
817
- throw new Error('HistoryModule not bound — host must call bind(contextManager) before tool dispatch.');
847
+ // The host's mistake, named for whoever runs it; the agent reads what
848
+ // it can do about it, which is nothing.
849
+ console.error('[history] a tool was called before the host bound the module: call bind(contextManager) before tool dispatch');
850
+ throw new Error(
851
+ "The history tools aren't available: this host hasn't finished setting up the history module, so nothing " +
852
+ "was read. Calling them again won't change that; ask whoever runs this host.",
853
+ );
818
854
  }
819
855
  switch (call.name) {
820
856
  case 'stats':
@@ -828,7 +864,7 @@ export class HistoryModule implements Module {
828
864
  case 'semantic_search':
829
865
  return await this.handleSemanticSearch((call.input ?? {}) as SemanticSearchInput);
830
866
  default:
831
- return { success: false, isError: true, error: `Unknown tool: ${call.name}` };
867
+ return { success: false, isError: true, error: unknownToolMessage(this.name, call.name, this.getTools()) };
832
868
  }
833
869
  } catch (error) {
834
870
  // Catches our own validation errors (bad ISO date, invalid regex,
@@ -897,20 +933,33 @@ export class HistoryModule implements Module {
897
933
 
898
934
  private handleExtract(input: ExtractInput): ToolResult {
899
935
  if (input.aroundId !== undefined) return this.handleExtractAround(input);
900
- if (input.before !== undefined || input.after !== undefined || input.allChannels !== undefined) {
901
- throw new Error('"before"/"after"/"allChannels" only apply together with "aroundId".');
936
+ const aroundOnly = (['before', 'after', 'allChannels'] as const).filter((k) => input[k] !== undefined);
937
+ if (aroundOnly.length > 0) {
938
+ const one = aroundOnly.length === 1;
939
+ throw new Error(
940
+ `Nothing was read: ${quotedList(aroundOnly, 'and')} ${one ? 'goes' : 'go'} with "aroundId", which reads the ` +
941
+ `conversation around one message. Pass aroundId too, or leave ${one ? 'it' : 'them'} out to read a range ` +
942
+ 'with "from", "to" and "channelId".',
943
+ );
902
944
  }
903
945
  const channelId = this.resolveChannel(input.channelId);
904
946
  const fromMs = parseIsoDate(input.from, 'from');
905
947
  const toMs = parseIsoDate(input.to, 'to');
906
- const limit = clampCount(input.limit, EXTRACT_DEFAULT_LIMIT, EXTRACT_MAX_LIMIT, 'limit');
907
- const offset = clampCount(input.offset, 0, NATIVE_OFFSET_MAX, 'offset');
948
+ const limit = clampCount(input.limit, EXTRACT_DEFAULT_LIMIT, EXTRACT_MAX_LIMIT, 'limit', 'read');
949
+ const offset = clampCount(input.offset, 0, NATIVE_OFFSET_MAX, 'offset', 'read');
908
950
  const format = input.format ?? 'text';
909
951
  const authorFilter = buildAuthorFilter(input.author, input.excludeAuthor);
910
952
 
911
953
  const cm = this.cm as ContextManager;
912
- if (!authorFilter && (input.windowOffset !== undefined || input.afterId !== undefined || input.seqMark !== undefined)) {
913
- throw new Error('"windowOffset"/"afterId" only apply together with author/excludeAuthor (they resume a filtered scan). Use offset.');
954
+ const resumeOnly = (['windowOffset', 'afterId', 'seqMark'] as const).filter((k) => input[k] !== undefined);
955
+ if (!authorFilter && resumeOnly.length > 0) {
956
+ const one = resumeOnly.length === 1;
957
+ throw new Error(
958
+ `Nothing was read: ${quotedList(resumeOnly, 'and')} ${one ? 'resumes' : 'resume'} an author-filtered ` +
959
+ `extract, so ${one ? 'it goes' : 'they go'} with the "author" or "excludeAuthor" of the call that returned ` +
960
+ `${one ? 'it' : 'them'}: repeat that call with the fields of its "resume". To page through a range without an ` +
961
+ 'author filter, use "offset".',
962
+ );
914
963
  }
915
964
 
916
965
  if (authorFilter) {
@@ -924,9 +973,11 @@ export class HistoryModule implements Module {
924
973
  // channel-scoped window pages in append order, and Discord catch-up
925
974
  // appends old-timestamped messages late, so "continue from the last
926
975
  // timestamp seen" would skip them.
927
- const maxScan = clampCount(input.maxScan, EXTRACT_FILTER_DEFAULT_MAX_SCAN, EXTRACT_FILTER_MAX_MAX_SCAN, 'maxScan');
928
- if (maxScan === 0) throw new Error('"maxScan" must be at least 1 for an author-filtered extract.');
929
- const requestedOffset = clampCount(input.windowOffset, 0, NATIVE_OFFSET_MAX, 'windowOffset');
976
+ // An author-filtered extract has to examine at least 1 message.
977
+ const maxScan = clampCount(
978
+ input.maxScan, EXTRACT_FILTER_DEFAULT_MAX_SCAN, EXTRACT_FILTER_MAX_MAX_SCAN, 'maxScan', 'read', 1,
979
+ );
980
+ const requestedOffset = clampCount(input.windowOffset, 0, NATIVE_OFFSET_MAX, 'windowOffset', 'read');
930
981
  // A position is only meaningful against the window it was taken in:
931
982
  // a removal before it (every discord:delete, host hide, /undo) shifts
932
983
  // later positions left, and a message inserted before it (a
@@ -951,7 +1002,8 @@ export class HistoryModule implements Module {
951
1002
  if (channelId === undefined && windowOffset > 0) {
952
1003
  if (typeof input.seqMark !== 'number') {
953
1004
  throw new Error(
954
- '"windowOffset" without channelId needs the "seqMark" from the same `resume` object. Pass the whole `resume` object back.',
1005
+ 'Nothing was read: "windowOffset" without "channelId" needs the "seqMark" from the same "resume". Copy the ' +
1006
+ 'fields of the previous response\'s "resume" into the call.',
955
1007
  );
956
1008
  }
957
1009
  const cursor = cm.queryMessagesByTimeAndChannel({ fromMs, toMs, limit: 1, offset: windowOffset - 1 }).messages[0];
@@ -1128,25 +1180,27 @@ export class HistoryModule implements Module {
1128
1180
  * via edgeWindow, which is exact by timestamp).
1129
1181
  */
1130
1182
  private handleExtractAround(input: ExtractInput): ToolResult {
1131
- if (
1132
- input.from !== undefined ||
1133
- input.to !== undefined ||
1134
- input.offset !== undefined ||
1135
- input.windowOffset !== undefined ||
1136
- input.limit !== undefined
1137
- ) {
1183
+ const windowFields = (['from', 'to', 'offset', 'windowOffset', 'limit'] as const).filter((k) => input[k] !== undefined);
1184
+ if (windowFields.length > 0) {
1138
1185
  throw new Error(
1139
- '"aroundId" cannot be combined with from/to/offset/windowOffset/limit — it picks its own window; size it with before/after.',
1186
+ `Nothing was read: "aroundId" picks its own window, so it doesn't take ${quotedList(windowFields, 'or')}. Size ` +
1187
+ 'the window with "before" and "after", or leave out "aroundId" to read a range.',
1140
1188
  );
1141
1189
  }
1142
1190
  if (input.author !== undefined || input.excludeAuthor !== undefined) {
1143
- throw new Error('"aroundId" returns the whole conversation around a message; author filters do not apply. Use search/extract with author instead.');
1191
+ throw new Error(
1192
+ 'Nothing was read: "aroundId" reads the whole conversation around one message, so author filters don\'t ' +
1193
+ 'apply to it. To find someone\'s messages, use history--search or history--extract with "author".',
1194
+ );
1144
1195
  }
1145
1196
  if (input.allChannels && input.channelId !== undefined) {
1146
- throw new Error('Pass either "channelId" or "allChannels", not both.');
1197
+ throw new Error(
1198
+ 'Nothing was read: "channelId" and "allChannels" can\'t go together. Leave out "channelId" to read around the ' +
1199
+ 'message in every channel, or leave out both to read around it in its own channel.',
1200
+ );
1147
1201
  }
1148
- const before = clampCount(input.before, AROUND_DEFAULT, AROUND_MAX, 'before');
1149
- const after = clampCount(input.after, AROUND_DEFAULT, AROUND_MAX, 'after');
1202
+ const before = clampCount(input.before, AROUND_DEFAULT, AROUND_MAX, 'before', 'read');
1203
+ const after = clampCount(input.after, AROUND_DEFAULT, AROUND_MAX, 'after', 'read');
1150
1204
  const format = input.format ?? 'text';
1151
1205
  const cm = this.cm as ContextManager;
1152
1206
 
@@ -1155,12 +1209,20 @@ export class HistoryModule implements Module {
1155
1209
  const rawId = String(input.aroundId);
1156
1210
  if (/^sum:/.test(rawId)) {
1157
1211
  throw new Error(
1158
- `${rawId} is a compression summary, not a message — use overview for summaries, or aroundId with a msg: hit.`,
1212
+ `Nothing was read: ${shown(rawId)} is a compression summary, not a message. history--overview reads ` +
1213
+ 'summaries; "aroundId" takes a message, such as ' +
1214
+ (this.semanticCfg
1215
+ ? 'the "messageId" of a history--semantic_search hit.'
1216
+ : 'the "id" of a history--search or history--extract result.'),
1159
1217
  );
1160
1218
  }
1161
1219
  const anchor = cm.getMessage(rawId.replace(/^msg:/, ''));
1162
1220
  if (!anchor) {
1163
- throw new Error(`No message with id ${JSON.stringify(input.aroundId)} in this history.`);
1221
+ throw new Error(
1222
+ `Nothing was read: no message in your history has the id ${shown(input.aroundId)}. Pass the "id" ` +
1223
+ 'of a history--search or history--extract result' +
1224
+ (this.semanticCfg ? ', or the "messageId" of a history--semantic_search hit.' : '.'),
1225
+ );
1164
1226
  }
1165
1227
  const anchorId = String(anchor.id);
1166
1228
  const anchorChannel = getChannelId(anchor);
@@ -1175,11 +1237,11 @@ export class HistoryModule implements Module {
1175
1237
  let ties: StoredMessage[];
1176
1238
  if (channelId !== undefined) {
1177
1239
  const n = cm.queryMessagesByTimeAndChannel({ fromMs: t, toMs: t, channelId, limit: 0 }).matchedCount;
1178
- if (n > AROUND_TIE_CAP) throw tieCapError(anchorId, n);
1240
+ if (n > AROUND_TIE_CAP) throw tieCapError(anchorId, t, channelId);
1179
1241
  ties = cm.queryMessagesByTimeAndChannel({ fromMs: t, toMs: t, channelId, limit: n }).messages;
1180
1242
  } else {
1181
1243
  ties = cm.queryMessagesByTime({ fromMs: t, toMs: t, limit: AROUND_TIE_CAP + 1 }).messages;
1182
- if (ties.length > AROUND_TIE_CAP) throw tieCapError(anchorId, ties.length);
1244
+ if (ties.length > AROUND_TIE_CAP) throw tieCapError(anchorId, t, undefined);
1183
1245
  }
1184
1246
  ties = [...ties].sort((x, y) => x.sequence - y.sequence);
1185
1247
  const idx = ties.findIndex((m) => String(m.id) === anchorId);
@@ -1187,9 +1249,9 @@ export class HistoryModule implements Module {
1187
1249
  // The anchor is in the store and every message of its millisecond in
1188
1250
  // `channelId` was fetched, so only an explicit channelId excludes it.
1189
1251
  throw new Error(
1190
- `Message ${anchorId} is not in channel ${JSON.stringify(input.channelId)}` +
1191
- (anchorChannel ? ` (it is in ${anchorChannel}).` : '.') +
1192
- ' Omit channelId to use the anchor\'s own channel.',
1252
+ `Nothing was read: message ${shown(anchorId)} isn't in channel ${shown(input.channelId)}` +
1253
+ (anchorChannel ? ` (it's in ${shown(anchorChannel)}).` : '.') +
1254
+ ' Leave out "channelId" to read around it in its own channel.',
1193
1255
  );
1194
1256
  }
1195
1257
  const tiesBefore = ties.slice(Math.max(0, idx - before), idx);
@@ -1219,22 +1281,35 @@ export class HistoryModule implements Module {
1219
1281
  // ==========================================================================
1220
1282
 
1221
1283
  private async handleSearch(input: SearchInput): Promise<ToolResult> {
1222
- if (!input.query) {
1223
- throw new Error('search requires a non-empty "query".');
1284
+ // A finite number is searched as its text, as a regular expression made
1285
+ // from it reads it; any other value that isn't text is refused.
1286
+ const given: unknown = input.query;
1287
+ const query = typeof given === 'number' && Number.isFinite(given) ? String(given) : given;
1288
+ if (typeof query !== 'string' || query === '') {
1289
+ throw new Error(
1290
+ 'Nothing was searched: "query" must be the text to look for; ' +
1291
+ `${given === undefined ? 'this call gave none' : `it's ${shown(given)}`}.`,
1292
+ );
1224
1293
  }
1225
1294
  const channelId = this.resolveChannel(input.channelId);
1226
1295
  const fromMs = parseIsoDate(input.from, 'from');
1227
1296
  const toMs = parseIsoDate(input.to, 'to');
1228
- const limit = clampCount(input.limit, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LIMIT, 'limit');
1229
- const maxScan = clampCount(input.maxScan, SEARCH_DEFAULT_MAX_SCAN, SEARCH_MAX_MAX_SCAN, 'maxScan');
1297
+ const limit = clampCount(input.limit, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LIMIT, 'limit', 'searched');
1298
+ // A search has to scan at least 1 message.
1299
+ const maxScan = clampCount(input.maxScan, SEARCH_DEFAULT_MAX_SCAN, SEARCH_MAX_MAX_SCAN, 'maxScan', 'searched', 1);
1230
1300
  const caseSensitive = input.caseSensitive ?? false;
1231
1301
  const flags = caseSensitive ? '' : 'i';
1232
1302
  const order = input.order ?? 'oldest';
1233
1303
  if (order !== 'oldest' && order !== 'newest') {
1234
- throw new Error(`"order" must be "oldest" or "newest", got ${JSON.stringify(input.order)}.`);
1304
+ throw new Error(
1305
+ `Nothing was searched: "order" must be "oldest" or "newest", or left out for "oldest"; it's ${shown(input.order)}.`,
1306
+ );
1235
1307
  }
1236
1308
  if (input.wholeWord && input.regex) {
1237
- throw new Error('"wholeWord" applies to substring search only; in regex mode write \\b around the pattern instead.');
1309
+ throw new Error(
1310
+ 'Nothing was searched: "wholeWord" applies to a search for text, not a regular expression. In a regular ' +
1311
+ 'expression, write \\b around the pattern instead, or set "regex" to false.',
1312
+ );
1238
1313
  }
1239
1314
  const authorFilter = buildAuthorFilter(input.author, input.excludeAuthor);
1240
1315
 
@@ -1245,12 +1320,18 @@ export class HistoryModule implements Module {
1245
1320
  // than here.
1246
1321
  if (input.regex) {
1247
1322
  try {
1248
- new RegExp(input.query, flags);
1323
+ new RegExp(query, flags);
1249
1324
  } catch (error) {
1250
- throw new Error(`Invalid regex "${input.query}": ${error instanceof Error ? error.message : String(error)}`);
1325
+ // V8's message restates the pattern; the reason alone follows it.
1326
+ const reason = (error instanceof Error ? error.message : String(error))
1327
+ .replace(/^Invalid regular expression: \/.*\/[a-z]*: /s, '');
1328
+ throw new Error(
1329
+ `Nothing was searched: "query" ${shown(query)} isn't a valid regular expression (${reason}). ` +
1330
+ 'Fix the pattern, or set "regex" to false to search for the text as written.',
1331
+ );
1251
1332
  }
1252
1333
  }
1253
- const needle = caseSensitive ? input.query : input.query.toLowerCase();
1334
+ const needle = caseSensitive ? query : query.toLowerCase();
1254
1335
 
1255
1336
  const cm = this.cm as ContextManager;
1256
1337
  // The candidate window is the oldest (order:"oldest") or newest
@@ -1259,7 +1340,7 @@ export class HistoryModule implements Module {
1259
1340
  // detected up front rather than
1260
1341
  // silently scanning maxScan candidates and returning as if that were the
1261
1342
  // whole window. See the tool description's `truncated` contract.
1262
- if (maxScan === 0) throw new Error('"maxScan" must be at least 1 — a zero-message scan can never make progress.');
1343
+ //
1263
1344
  // Continuation: `skipSequences` names what the previous call already
1264
1345
  // scanned at exactly the resume bound's millisecond (the bound is
1265
1346
  // inclusive). Identified by sequence range rather than a count, so a
@@ -1269,7 +1350,11 @@ export class HistoryModule implements Module {
1269
1350
  const skipRange = parseSkipSequences(input.skipSequences);
1270
1351
  const edgeMs = order === 'oldest' ? fromMs : toMs;
1271
1352
  if (skipRange && edgeMs === undefined) {
1272
- throw new Error(`"skipSequences" only applies together with "${order === 'oldest' ? 'from' : 'to'}" — pass the whole \`resume\` object back.`);
1353
+ throw new Error(
1354
+ `Nothing was searched: "skipSequences" goes with the "${order === 'oldest' ? 'from' : 'to'}" from the same ` +
1355
+ '"resume". Copy the fields of the previous response\'s "resume" into the call, or leave "skipSequences" ' +
1356
+ 'out for a new search.',
1357
+ );
1273
1358
  }
1274
1359
  const isSkipped = (m: StoredMessage) =>
1275
1360
  !!skipRange && m.timestamp.getTime() === edgeMs && m.sequence >= skipRange[0] && m.sequence <= skipRange[1];
@@ -1350,7 +1435,7 @@ export class HistoryModule implements Module {
1350
1435
  // through a worker thread instead, which can be forcibly terminated
1351
1436
  // on a deadline. See search-regex-worker.ts's header for the full
1352
1437
  // rationale.
1353
- const { matches, scanned } = await this.searchWithRegexWorker(candidates, input.query, flags, limit);
1438
+ const { matches, scanned } = await this.searchWithRegexWorker(candidates, query, flags, limit);
1354
1439
  return { success: true, data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned), matches } };
1355
1440
  }
1356
1441
 
@@ -1416,22 +1501,32 @@ export class HistoryModule implements Module {
1416
1501
  const timer = setTimeout(() => {
1417
1502
  reject(
1418
1503
  new Error(
1419
- `search timed out after ${SEARCH_REGEX_TIMEOUT_MS}ms while matching regex "${pattern}" against ` +
1420
- `up to ${texts.length} candidate(s) — the pattern may be catastrophically slow (exponential ` +
1421
- 'backtracking) against this data; try a simpler pattern, a literal substring search ' +
1422
- '(regex:false), or a smaller maxScan.',
1504
+ `The search stopped, and returned nothing: matching the regular expression ${shown(pattern)} ` +
1505
+ `against up to ${texts.length} ${texts.length === 1 ? 'message' : 'messages'} took longer than ` +
1506
+ `${SEARCH_REGEX_TIMEOUT_MS} ms, as a ` +
1507
+ 'pattern that backtracks heavily can. Try a simpler pattern, set "regex" to false to search for the ' +
1508
+ 'text as written, or lower "maxScan".',
1423
1509
  ),
1424
1510
  );
1425
1511
  }, SEARCH_REGEX_TIMEOUT_MS);
1426
1512
  timer.unref?.();
1427
1513
  worker.once('message', (msg: SearchWorkerMessage) => {
1428
1514
  clearTimeout(timer);
1429
- if (msg.type === 'error') reject(new Error(msg.error));
1430
- else resolve({ matches: msg.matches, scanned: msg.scanned });
1515
+ if (msg.type === 'error') {
1516
+ reject(new Error(
1517
+ `The search stopped, and returned nothing: matching the regular expression ${shown(pattern)} ` +
1518
+ `failed (${msg.error}). Try a simpler pattern, or set "regex" to false to search for the text as written.`,
1519
+ ));
1520
+ } else {
1521
+ resolve({ matches: msg.matches, scanned: msg.scanned });
1522
+ }
1431
1523
  });
1432
1524
  worker.once('error', (err) => {
1433
1525
  clearTimeout(timer);
1434
- reject(err);
1526
+ reject(new Error(
1527
+ `The search stopped, and returned nothing: the worker matching the regular expression ${shown(pattern)} ` +
1528
+ `failed (${err.message}). Set "regex" to false to search for the text as written.`,
1529
+ ));
1435
1530
  });
1436
1531
  },
1437
1532
  );
@@ -1463,30 +1558,54 @@ export class HistoryModule implements Module {
1463
1558
 
1464
1559
  private async handleSemanticSearch(input: SemanticSearchInput): Promise<ToolResult> {
1465
1560
  if (!this.semanticCfg || !this.semanticClient) {
1466
- throw new Error('semantic_search is not configured for this resident (no embed-service in the recipe).');
1561
+ throw new Error(
1562
+ "Nothing was searched: this host has no semantic index set up, so history--semantic_search isn't available " +
1563
+ "here, and calling it again won't change that. history--search finds messages by their words.",
1564
+ );
1565
+ }
1566
+ if (typeof input.query !== 'string' || !input.query.trim()) {
1567
+ throw new Error(
1568
+ 'Nothing was searched: "query" must be a sentence describing what you\'re looking for; ' +
1569
+ `${input.query === undefined ? 'this call gave none' : `it's ${shown(input.query)}`}.`,
1570
+ );
1467
1571
  }
1468
- if (typeof input.query !== 'string' || !input.query.trim()) throw new Error('query must be a non-empty string');
1469
- const limit = clampCount(input.limit, SEMANTIC_DEFAULT_LIMIT, SEMANTIC_MAX_LIMIT, 'limit');
1470
- if (limit < 1) throw new Error('limit must be at least 1');
1572
+ // A search has to return at least 1 hit.
1573
+ const limit = clampCount(input.limit, SEMANTIC_DEFAULT_LIMIT, SEMANTIC_MAX_LIMIT, 'limit', 'searched', 1);
1471
1574
  const fromMs = parseIsoDate(input.from, 'from');
1472
1575
  const toMs = parseIsoDate(input.to, 'to');
1473
- if (fromMs !== undefined && toMs !== undefined && fromMs > toMs) throw new Error('`from` must not be after `to`');
1576
+ assertOrderedRange(fromMs, toMs, input.from, input.to);
1474
1577
  const channelId = this.resolveChannel(input.channelId);
1475
1578
  // Summaries carry no channel, so a channel filter on a summaries-only
1476
1579
  // search can only ever match nothing. Say so instead of returning [].
1477
1580
  const wantsSummariesOnly = input.level !== undefined || input.kinds === 'summaries';
1478
1581
  if (channelId && wantsSummariesOnly) {
1479
- throw new Error('channelId cannot be combined with kinds="summaries" or level: summaries are not per-channel. Drop channelId, or search kinds="messages".');
1582
+ const picks = [
1583
+ ...(input.kinds === 'summaries' ? ['"kinds": "summaries"'] : []),
1584
+ ...(input.level !== undefined ? ['"level"'] : []),
1585
+ ];
1586
+ const fields = [...(input.kinds === 'summaries' ? ['kinds'] : []), ...(input.level !== undefined ? ['level'] : [])];
1587
+ throw new Error(
1588
+ `Nothing was searched: summaries aren't kept by channel, so "channelId" can't go with ${picks.join(' or ')}. ` +
1589
+ `Leave out "channelId" to search summaries, or leave out ${quotedList(fields, 'and')} to search that ` +
1590
+ 'channel\'s messages.',
1591
+ );
1480
1592
  }
1481
1593
  if (input.level !== undefined && input.kinds === 'messages') {
1482
- throw new Error('level applies to summaries only and cannot be combined with kinds="messages".');
1594
+ throw new Error(
1595
+ 'Nothing was searched: "level" picks summaries, and "kinds" is "messages", so the two can\'t go together. ' +
1596
+ 'Leave out "kinds" to search summaries of that level, or leave out "level" to search messages.',
1597
+ );
1483
1598
  }
1484
1599
  let kinds: string[] | undefined;
1485
1600
  if (wantsSummariesOnly) kinds = ['summary'];
1486
1601
  else if (input.kinds === 'messages' || channelId) kinds = ['message'];
1487
- if (input.level !== undefined && (!Number.isInteger(input.level) || input.level < 0)) throw new Error('level must be a non-negative integer');
1602
+ if (input.level !== undefined && (!Number.isInteger(input.level) || input.level < 0)) {
1603
+ throw new Error(`Nothing was searched: "level" must be a whole number, 0 or more, such as 1; it's ${shown(input.level)}.`);
1604
+ }
1488
1605
  if (input.minScore !== undefined && (typeof input.minScore !== 'number' || input.minScore < -1 || input.minScore > 1)) {
1489
- throw new Error('minScore must be a number in -1..1');
1606
+ throw new Error(
1607
+ `Nothing was searched: "minScore" must be a number from -1 to 1, or left out; it's ${shown(input.minScore)}.`,
1608
+ );
1490
1609
  }
1491
1610
 
1492
1611
  // Bounded catch-up so the newest messages are searchable; never block on a backlog.
@@ -1501,6 +1620,48 @@ export class HistoryModule implements Module {
1501
1620
  ts_to: toMs === undefined ? undefined : toMs / 1000,
1502
1621
  channel: channelId, kinds, level: input.level, min_score: input.minScore, snippet: SEMANTIC_SNIPPET_CHARS,
1503
1622
  });
1623
+ // The client says what went wrong with the service; what that means for
1624
+ // this call, and what the agent can do, is said here. The service's own
1625
+ // source isn't in this repository, so a 404's advice holds whether it
1626
+ // means this store's index doesn't exist yet or a route the service lacks.
1627
+ const failure = (e: unknown): { reason: string; advice: string; mayPass: boolean } => {
1628
+ const status = e instanceof EmbedServiceError ? e.status : null;
1629
+ const reason = e instanceof Error ? e.message : String(e);
1630
+ const later = {
1631
+ reason, mayPass: true,
1632
+ advice: 'You can try again later; if it keeps happening, ask whoever runs this host',
1633
+ };
1634
+ if (status === 404) {
1635
+ return {
1636
+ reason, mayPass: true,
1637
+ advice: "If this store's index doesn't exist yet, the first messages indexed create it, and a later call " +
1638
+ 'can work; if it keeps happening, ask whoever runs this host',
1639
+ };
1640
+ }
1641
+ if (status === 408 || status === 429 || (status !== null && status >= 500)) return later;
1642
+ // An answer without what a search reads: a page shown while the service
1643
+ // restarts clears, and an address that points at something else doesn't.
1644
+ if (status !== null && status >= 200 && status < 300) {
1645
+ return {
1646
+ reason, mayPass: true,
1647
+ advice: 'If the service was restarting, a later call can work; if its address points at something else, ' +
1648
+ "none will until whoever runs this host fixes the semantic index's settings",
1649
+ };
1650
+ }
1651
+ if (status !== null) {
1652
+ return {
1653
+ reason, mayPass: false,
1654
+ advice: "Calling it again the same way won't change that; if it keeps happening, ask whoever runs this host",
1655
+ };
1656
+ }
1657
+ // No answer: the service down or slow, or the connection dropped, which
1658
+ // clears, or its address or certificate wrong, which doesn't.
1659
+ return {
1660
+ reason, mayPass: true,
1661
+ advice: 'If the service is down or slow, or the connection dropped, a later call can work; if its address ' +
1662
+ "or certificate is wrong, none will until whoever runs this host fixes the semantic index's settings",
1663
+ };
1664
+ };
1504
1665
  // The remote index is append-only and knows nothing about branches: a
1505
1666
  // message removed from the agent's reach by /undo, /checkout, /restore or
1506
1667
  // /newtopic keeps its id (a new message gets a fresh one) and stays
@@ -1525,7 +1686,7 @@ export class HistoryModule implements Module {
1525
1686
  if (msgId !== undefined) {
1526
1687
  const m = cm.getMessage(msgId);
1527
1688
  if (!m) return 'offBranch';
1528
- const text = messageIndexText(m, includePrivate).slice(0, this.semanticCfg!.maxChars ?? 32_000);
1689
+ const text = safeSlice(messageIndexText(m, includePrivate), 0, this.semanticCfg!.maxChars ?? 32_000);
1529
1690
  if (!text) return 'stale';
1530
1691
  currentText.set(h.id, text);
1531
1692
  return 'ok';
@@ -1534,11 +1695,21 @@ export class HistoryModule implements Module {
1534
1695
  return 'ok';
1535
1696
  };
1536
1697
  let k = limit;
1537
- let res = await runSearch(k);
1698
+ let res: SearchResponse;
1699
+ try {
1700
+ res = await runSearch(k);
1701
+ } catch (e) {
1702
+ const { reason, advice } = failure(e);
1703
+ throw new Error(`Nothing was searched: ${reason}. ${advice}. history--search finds messages by their words meanwhile.`);
1704
+ }
1538
1705
  let droppedOffBranch = 0;
1539
1706
  let droppedStale = 0;
1707
+ // Why searching again for more hits failed, when it did. The search
1708
+ // before it answered, so its hits on this branch are returned.
1709
+ let searchAgainError: string | null = null;
1540
1710
  let onBranch: typeof res.hits = [];
1541
- for (;;) {
1711
+ // The last answer's hits on this branch, checked as they are now.
1712
+ const check = (): void => {
1542
1713
  droppedOffBranch = 0; droppedStale = 0;
1543
1714
  onBranch = res.hits.filter((h) => {
1544
1715
  const v = keep(h);
@@ -1546,25 +1717,58 @@ export class HistoryModule implements Module {
1546
1717
  else if (v === 'stale') droppedStale++;
1547
1718
  return v === 'ok';
1548
1719
  });
1720
+ };
1721
+ for (;;) {
1722
+ check();
1549
1723
  if (onBranch.length >= limit || res.hits.length < k || k >= SEMANTIC_MAX_FETCH) break;
1550
1724
  k = Math.min(SEMANTIC_MAX_FETCH, k * 4);
1551
- res = await runSearch(k);
1725
+ try {
1726
+ res = await runSearch(k);
1727
+ } catch (e) {
1728
+ const { reason, mayPass } = failure(e);
1729
+ searchAgainError = `searching again for more hits failed: ${reason}. ` +
1730
+ (mayPass ? 'Calling again may bring more.' : "Calling again the same way won't change that.");
1731
+ // A message may have been edited, deleted or taken off this branch
1732
+ // while that search waited, so the hits are checked again.
1733
+ check();
1734
+ break;
1735
+ }
1552
1736
  }
1553
1737
  onBranch = onBranch.slice(0, limit);
1554
- const hits = onBranch.map((h) => ({
1555
- id: h.id,
1556
- /** Raw message id for a `msg:` hit (null for summaries) — pass it to extract({ aroundId }). */
1557
- messageId: h.id.startsWith('msg:') ? h.id.slice(4) : null,
1558
- kind: h.kind,
1559
- level: h.level,
1560
- score: h.score,
1561
- timestamp: h.ts === null ? null : new Date(h.ts * 1000).toISOString(),
1562
- channelId: h.channel,
1563
- participant: (h.meta as { participant?: unknown }).participant ?? null,
1564
- author: (h.meta as { author?: unknown }).author ?? null,
1565
- snippet: currentText.has(h.id) ? snippetOf(currentText.get(h.id)!) : (h.text ?? ''),
1566
- chars: currentText.has(h.id) ? currentText.get(h.id)!.length : h.chars,
1567
- }));
1738
+ // A ts that makes no date reads as no timestamp, as a null one does, and a
1739
+ // meta that isn't an object as no participant or author. The hit is still
1740
+ // returned, but the index holds data written wrong, so the host is told.
1741
+ // The index writes ts as a number of seconds, and text holding one reads
1742
+ // as main read it; nothing else makes a date, and reading it can't throw.
1743
+ const unread: string[] = [];
1744
+ const hits = onBranch.map((h) => {
1745
+ const ts: unknown = h.ts;
1746
+ const seconds = typeof ts === 'number' ? ts : typeof ts === 'string' && ts.trim() !== '' ? Number(ts) : NaN;
1747
+ const at = new Date(seconds * 1000);
1748
+ const timestamp = Number.isNaN(at.getTime()) ? null : at.toISOString();
1749
+ const meta = typeof h.meta === 'object' && h.meta !== null && !Array.isArray(h.meta)
1750
+ ? h.meta as { participant?: unknown; author?: unknown }
1751
+ : null;
1752
+ const missing = [...(h.ts !== null && timestamp === null ? ['ts'] : []), ...(meta === null ? ['meta'] : [])];
1753
+ if (missing.length > 0) unread.push(`${shown(h.id)} (${missing.join(' and ')})`);
1754
+ return {
1755
+ id: h.id,
1756
+ /** Raw message id for a `msg:` hit (null for summaries) — pass it to extract({ aroundId }). */
1757
+ messageId: h.id.startsWith('msg:') ? h.id.slice(4) : null,
1758
+ kind: h.kind,
1759
+ level: h.level,
1760
+ score: h.score,
1761
+ timestamp,
1762
+ channelId: h.channel,
1763
+ participant: meta?.participant ?? null,
1764
+ author: meta?.author ?? null,
1765
+ snippet: currentText.has(h.id) ? snippetOf(currentText.get(h.id)!) : (h.text ?? ''),
1766
+ chars: currentText.has(h.id) ? currentText.get(h.id)!.length : h.chars,
1767
+ };
1768
+ });
1769
+ if (unread.length > 0) {
1770
+ this.indexer?.warn(`a search returned hits whose ts or meta couldn't be read, without them: ${unread.join(', ')}`);
1771
+ }
1568
1772
  return {
1569
1773
  success: true,
1570
1774
  data: {
@@ -1576,8 +1780,10 @@ export class HistoryModule implements Module {
1576
1780
  lastError: this.indexer?.lastError ?? null,
1577
1781
  /** Hits the index returned for messages/summaries not on the current branch (undone, checked out past). */
1578
1782
  droppedOffBranch,
1579
- /** Hits for messages whose current text has nothing indexable (edited blank; the index delete is pending). */
1783
+ /** Hits for messages whose current text has nothing indexable (edited blank; the index keeps such a message until its delete goes through, and on a service without /delete it never does). */
1580
1784
  droppedStale,
1785
+ /** When too few hits were on this branch and searching again for more failed: why, and whether calling again can help. */
1786
+ searchAgainError,
1581
1787
  },
1582
1788
  timingMs: res.timing_ms,
1583
1789
  },
@@ -1589,7 +1795,7 @@ export class HistoryModule implements Module {
1589
1795
  const fromMs = parseIsoDate(input.from, 'from');
1590
1796
  const toMs = parseIsoDate(input.to, 'to');
1591
1797
  assertOrderedRange(fromMs, toMs, input.from, input.to);
1592
- const limit = clampCount(input.limit, OVERVIEW_DEFAULT_LIMIT, OVERVIEW_MAX_LIMIT, 'limit');
1798
+ const limit = clampCount(input.limit, OVERVIEW_DEFAULT_LIMIT, OVERVIEW_MAX_LIMIT, 'limit', 'read');
1593
1799
  const cm = this.cm as ContextManager;
1594
1800
 
1595
1801
  let entries: TimeRangeSummaryEntry[];
@@ -1959,6 +2165,12 @@ export class HistoryModule implements Module {
1959
2165
  // Helpers
1960
2166
  // ============================================================================
1961
2167
 
2168
+ // The checks here (and resolveChannel) are shared by several tools. Their
2169
+ // refusals open with "Nothing was read", or for clampCount with the outcome
2170
+ // its caller names, and "Nothing was read" holds because every history tool
2171
+ // only reads. A tool that changes something should give its refusals an
2172
+ // opener of its own rather than reach these.
2173
+
1962
2174
  /** Parse an ISO 8601 date string to Unix ms. Throws a clear error (not a
1963
2175
  * crash) on an unparseable string; returns undefined for an omitted field
1964
2176
  * (open-ended bound). */
@@ -1966,7 +2178,10 @@ function parseIsoDate(value: string | undefined, field: string): number | undefi
1966
2178
  if (value === undefined) return undefined;
1967
2179
  const ms = Date.parse(value);
1968
2180
  if (Number.isNaN(ms)) {
1969
- throw new Error(`Invalid ISO 8601 date for "${field}": ${JSON.stringify(value)}`);
2181
+ throw new Error(
2182
+ `Nothing was read: "${field}" is ${shown(value)}, which isn't an ISO 8601 date. Write it like 2026-03-01 or ` +
2183
+ '2026-03-01T09:00:00Z.',
2184
+ );
1970
2185
  }
1971
2186
  return ms;
1972
2187
  }
@@ -1980,7 +2195,10 @@ function parseIsoDate(value: string | undefined, field: string): number | undefi
1980
2195
  */
1981
2196
  function assertOrderedRange(fromMs: number | undefined, toMs: number | undefined, fromRaw: string | undefined, toRaw: string | undefined): void {
1982
2197
  if (fromMs !== undefined && toMs !== undefined && fromMs > toMs) {
1983
- throw new Error(`"from" (${fromRaw}) must not be after "to" (${toRaw}).`);
2198
+ throw new Error(
2199
+ `Nothing was read: "from" (${shown(fromRaw)}) is after "to" (${shown(toRaw)}), so the range holds nothing. Swap them, ` +
2200
+ 'or correct the one that\'s wrong.',
2201
+ );
1984
2202
  }
1985
2203
  }
1986
2204
 
@@ -1992,24 +2210,54 @@ function assertOrderedRange(fromMs: number | undefined, toMs: number | undefined
1992
2210
  * "hard cap" and reach a native chronicle call expecting an unsigned
1993
2211
  * pagination argument (observed: `extract({limit:-1})` returning every
1994
2212
  * message in the store instead of being capped). Rejects (rather than
1995
- * silently clamping) anything that isn't a finite non-negative integer, so
1996
- * a caller mistake is surfaced as a clean tool error instead of silently
1997
- * doing something other than what was asked.
2213
+ * silently clamping) anything that isn't a whole number of at least `min`,
2214
+ * so a caller mistake is surfaced as a clean tool error instead of silently
2215
+ * doing something other than what was asked. The refusal opens with what
2216
+ * didn't happen for the calling tool (`outcome`), and gives the range and
2217
+ * the default.
1998
2218
  */
1999
- function clampCount(value: number | undefined, def: number, max: number, field: string): number {
2219
+ function clampCount(
2220
+ value: number | undefined, def: number, max: number, field: string, outcome: 'read' | 'searched', min = 0,
2221
+ ): number {
2000
2222
  if (value === undefined) return def;
2001
- if (typeof value !== 'number' || !Number.isFinite(value)) {
2002
- throw new Error(`"${field}" must be a finite number, got ${JSON.stringify(value)}.`);
2003
- }
2004
- if (!Number.isInteger(value)) {
2005
- throw new Error(`"${field}" must be an integer, got ${value}.`);
2006
- }
2007
- if (value < 0) {
2008
- throw new Error(`"${field}" must be >= 0, got ${value}.`);
2223
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < min) {
2224
+ throw new Error(
2225
+ `Nothing was ${outcome}: "${field}" is ${shown(value)}, which isn't a whole number from ${min} to ${max}. `
2226
+ + `Pass one, or leave it out for ${def}.`,
2227
+ );
2009
2228
  }
2010
2229
  return Math.min(value, max);
2011
2230
  }
2012
2231
 
2232
+ /**
2233
+ * A value a message names, the call's or the store's, as the message quotes
2234
+ * it: its JSON, or what String() gives where JSON can't give the value itself,
2235
+ * cut to 80 UTF-16 units, or one fewer where the 80th would split a surrogate
2236
+ * pair: the message stays in the agent's context, and half a pair isn't
2237
+ * well-formed text. Inside a list or an object, JSON writes Infinity and NaN
2238
+ * as null, and so does the quote. A value the message gives the agent to send
2239
+ * is quoted whole instead, since a cut one couldn't be sent.
2240
+ */
2241
+ function shown(value: unknown): string {
2242
+ let text: string | undefined;
2243
+ // A value JSON can't give, such as a cycle, or Infinity or NaN as the whole
2244
+ // value, which JSON.stringify gives as null, is shown as String() shows it.
2245
+ if (typeof value !== 'number' || Number.isFinite(value)) {
2246
+ try { text = JSON.stringify(value); } catch { /* String() below */ }
2247
+ }
2248
+ text ??= String(value);
2249
+ return text.length <= 80 ? text : `${safeSlice(text, 0, 80)}…`;
2250
+ }
2251
+
2252
+ /**
2253
+ * Names in prose, each quoted whole: "a"; "a" and "b"; "a", "b" and "c" (or
2254
+ * "or"). They're the tools' own field names, or names the agent can send.
2255
+ */
2256
+ function quotedList(names: readonly string[], joiner: 'and' | 'or'): string {
2257
+ const quoted = names.map((name) => JSON.stringify(name));
2258
+ return quoted.length <= 1 ? quoted.join('') : `${quoted.slice(0, -1).join(', ')} ${joiner} ${quoted[quoted.length - 1]}`;
2259
+ }
2260
+
2013
2261
  /** channelId lives at metadata.external.channelId — same path context-manager's
2014
2262
  * own native index reads from (message-store.ts's CHANNEL_FIELD). Metadata's
2015
2263
  * index-signature type means this needs an explicit narrow, same as
@@ -2219,7 +2467,9 @@ function codePointBefore(s: string, i: number): string | undefined {
2219
2467
  }
2220
2468
 
2221
2469
  /**
2222
- * First occurrence of `needle` in `text`. With `wholeWord`, an occurrence
2470
+ * First occurrence of `needle` in `text`, as the units of `text` it covers.
2471
+ * Without `caseSensitive`, `needle` is lower case already, and is found in
2472
+ * `text.toLowerCase()`. With `wholeWord`, an occurrence
2223
2473
  * only counts when it isn't glued to a letter/digit/underscore on either
2224
2474
  * side (Unicode-aware, so Cyrillic words work too) — "mission" no longer
2225
2475
  * hits inside "uncommissioned". Word-ness is checked only on the sides
@@ -2228,6 +2478,13 @@ function codePointBefore(s: string, i: number): string | undefined {
2228
2478
  */
2229
2479
  function matchSubstring(text: string, needle: string, caseSensitive: boolean, wholeWord = false): MatchHit | null {
2230
2480
  const haystack = caseSensitive ? text : text.toLowerCase();
2481
+ const hit = firstOccurrence(haystack, needle, wholeWord);
2482
+ // Lowering can lengthen a character, as it does `İ` (one unit to two). Then
2483
+ // the lowered text's units aren't the text's, and the snippet cuts the text.
2484
+ return hit !== null && haystack.length !== text.length ? inTextUnits(text, hit) : hit;
2485
+ }
2486
+
2487
+ function firstOccurrence(haystack: string, needle: string, wholeWord: boolean): MatchHit | null {
2231
2488
  if (!wholeWord) {
2232
2489
  const index = haystack.indexOf(needle);
2233
2490
  return index === -1 ? null : { index, length: needle.length };
@@ -2245,6 +2502,26 @@ function matchSubstring(text: string, needle: string, caseSensitive: boolean, wh
2245
2502
  }
2246
2503
  }
2247
2504
 
2505
+ /**
2506
+ * The units of `text` that a hit in `text.toLowerCase()` covers. Each code
2507
+ * point of `text` takes as many units there as its own lower case has, so a
2508
+ * hit that starts or ends inside a character's lower case takes in the whole
2509
+ * character.
2510
+ */
2511
+ function inTextUnits(text: string, hit: MatchHit): MatchHit {
2512
+ const end = hit.index + hit.length;
2513
+ let start = -1;
2514
+ let lowered = 0;
2515
+ let at = 0;
2516
+ while (at < text.length && lowered < end) {
2517
+ const cp = text.codePointAt(at)!;
2518
+ lowered += String.fromCodePoint(cp).toLowerCase().length;
2519
+ if (start === -1 && lowered > hit.index) start = at;
2520
+ at += cp > 0xffff ? 2 : 1;
2521
+ }
2522
+ return { index: start, length: at - start };
2523
+ }
2524
+
2248
2525
  // ============================================================================
2249
2526
  // author filtering
2250
2527
  // ============================================================================
@@ -2270,7 +2547,11 @@ function parseSkipSequences(v: unknown): [number, number] | null {
2270
2547
  !v.every((x) => typeof x === 'number' && Number.isInteger(x) && x >= 0) ||
2271
2548
  v[0] > v[1]
2272
2549
  ) {
2273
- throw new Error('"skipSequences" must be [lo, hi], two non-negative integers with lo <= hi — copy it from `resume`.');
2550
+ throw new Error(
2551
+ 'Nothing was searched: "skipSequences" must be two whole numbers, 0 or more, the first no larger than the ' +
2552
+ `second, as a search's "resume" gives it; it's ${shown(v)}. Copy it from "resume" with its "from" or "to", ` +
2553
+ 'or leave it out for a new search.',
2554
+ );
2274
2555
  }
2275
2556
  return [v[0], v[1]];
2276
2557
  }
@@ -2295,11 +2576,20 @@ function normalizeAuthorSpec(spec: string): string {
2295
2576
  function toAuthorSet(spec: AuthorSpec | undefined, field: string): Set<string> | null {
2296
2577
  if (spec === undefined) return null;
2297
2578
  const list = Array.isArray(spec) ? spec : [spec];
2298
- if (list.some((s) => typeof s !== 'string')) {
2299
- throw new Error(`"${field}" must be a string or an array of strings.`);
2579
+ const odd = list.findIndex((s) => typeof s !== 'string');
2580
+ if (odd !== -1) {
2581
+ throw new Error(
2582
+ `Nothing was read: "${field}" must list names or ids as text, such as ["alice", "1234"], and ` +
2583
+ `${shown(list[odd])} isn't text.`,
2584
+ );
2300
2585
  }
2301
2586
  const set = new Set(list.map(normalizeAuthorSpec).filter((s) => s.length > 0));
2302
- if (set.size === 0) throw new Error(`"${field}" must name at least one author.`);
2587
+ if (set.size === 0) {
2588
+ throw new Error(
2589
+ `Nothing was read: "${field}" names no one: ${list.length > 0 ? 'its entries are blank' : 'the list is empty'}. ` +
2590
+ `List at least one name or id, such as ["alice"], or leave "${field}" out.`,
2591
+ );
2592
+ }
2303
2593
  return set;
2304
2594
  }
2305
2595
 
@@ -2331,12 +2621,12 @@ function buildAuthorFilter(include: AuthorSpec | undefined, exclude: AuthorSpec
2331
2621
  /** ~SNIPPET_CONTEXT_CHARS of surrounding context on each side of a match, or
2332
2622
  * the first ~SNIPPET_FALLBACK_CHARS of content when no meaningful match
2333
2623
  * position is given (kept simple on purpose — this is a readability aid,
2334
- * not a highlighting engine). */
2624
+ * not a highlighting engine). Neither end splits a surrogate pair. */
2335
2625
  function snippetAround(text: string, index: number, length: number): string {
2336
- if (index < 0) return text.slice(0, SNIPPET_FALLBACK_CHARS);
2626
+ if (index < 0) return safeSlice(text, 0, SNIPPET_FALLBACK_CHARS);
2337
2627
  const start = Math.max(0, index - SNIPPET_CONTEXT_CHARS);
2338
2628
  const end = Math.min(text.length, index + length + SNIPPET_CONTEXT_CHARS);
2339
2629
  const prefix = start > 0 ? '…' : '';
2340
2630
  const suffix = end < text.length ? '…' : '';
2341
- return prefix + text.slice(start, end) + suffix;
2631
+ return prefix + safeSlice(text, start, end) + suffix;
2342
2632
  }