@animalabs/agent-framework 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (997) hide show
  1. package/README.md +108 -4
  2. package/dist/src/agent.d.ts +108 -17
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +207 -67
  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/index.d.ts +1 -1
  22. package/dist/src/code-execution/index.d.ts.map +1 -1
  23. package/dist/src/code-execution/index.js.map +1 -1
  24. package/dist/src/code-execution/py-runner.d.ts +50 -7
  25. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  26. package/dist/src/code-execution/py-runner.js +258 -90
  27. package/dist/src/code-execution/py-runner.js.map +1 -1
  28. package/dist/src/code-execution/runtime-py.d.ts +3 -2
  29. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  30. package/dist/src/code-execution/runtime-py.js +94 -8
  31. package/dist/src/code-execution/runtime-py.js.map +1 -1
  32. package/dist/src/code-execution/tool-definition.d.ts +17 -1
  33. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  34. package/dist/src/code-execution/tool-definition.js +72 -6
  35. package/dist/src/code-execution/tool-definition.js.map +1 -1
  36. package/dist/src/config-checks.d.ts +28 -0
  37. package/dist/src/config-checks.d.ts.map +1 -0
  38. package/dist/src/config-checks.js +56 -0
  39. package/dist/src/config-checks.js.map +1 -0
  40. package/dist/src/deadline-timer.d.ts +21 -0
  41. package/dist/src/deadline-timer.d.ts.map +1 -0
  42. package/dist/src/deadline-timer.js +43 -0
  43. package/dist/src/deadline-timer.js.map +1 -0
  44. package/dist/src/error-message.d.ts +30 -0
  45. package/dist/src/error-message.d.ts.map +1 -0
  46. package/dist/src/error-message.js +50 -0
  47. package/dist/src/error-message.js.map +1 -0
  48. package/dist/src/framework.d.ts +1529 -165
  49. package/dist/src/framework.d.ts.map +1 -1
  50. package/dist/src/framework.js +8264 -1939
  51. package/dist/src/framework.js.map +1 -1
  52. package/dist/src/gate/event-gate.d.ts +8 -0
  53. package/dist/src/gate/event-gate.d.ts.map +1 -1
  54. package/dist/src/gate/event-gate.js +36 -5
  55. package/dist/src/gate/event-gate.js.map +1 -1
  56. package/dist/src/held-writes.d.ts +16 -0
  57. package/dist/src/held-writes.d.ts.map +1 -0
  58. package/dist/src/held-writes.js +56 -0
  59. package/dist/src/held-writes.js.map +1 -0
  60. package/dist/src/index.d.ts +10 -4
  61. package/dist/src/index.d.ts.map +1 -1
  62. package/dist/src/index.js +11 -5
  63. package/dist/src/index.js.map +1 -1
  64. package/dist/src/mcpl/capability-grant.d.ts +4 -0
  65. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  66. package/dist/src/mcpl/capability-grant.js +1 -1
  67. package/dist/src/mcpl/capability-grant.js.map +1 -1
  68. package/dist/src/mcpl/channel-registry.d.ts +233 -18
  69. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  70. package/dist/src/mcpl/channel-registry.js +975 -386
  71. package/dist/src/mcpl/channel-registry.js.map +1 -1
  72. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  73. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  74. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  75. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  76. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  77. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  78. package/dist/src/mcpl/conversation-router.js +23 -8
  79. package/dist/src/mcpl/conversation-router.js.map +1 -1
  80. package/dist/src/mcpl/convert-block.d.ts +20 -0
  81. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  82. package/dist/src/mcpl/convert-block.js +88 -0
  83. package/dist/src/mcpl/convert-block.js.map +1 -0
  84. package/dist/src/mcpl/errors.d.ts +13 -0
  85. package/dist/src/mcpl/errors.d.ts.map +1 -1
  86. package/dist/src/mcpl/errors.js +28 -0
  87. package/dist/src/mcpl/errors.js.map +1 -1
  88. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  89. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  90. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  91. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  92. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  93. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  94. package/dist/src/mcpl/inbound-source.js +83 -0
  95. package/dist/src/mcpl/inbound-source.js.map +1 -0
  96. package/dist/src/mcpl/index.d.ts +7 -1
  97. package/dist/src/mcpl/index.d.ts.map +1 -1
  98. package/dist/src/mcpl/index.js +5 -1
  99. package/dist/src/mcpl/index.js.map +1 -1
  100. package/dist/src/mcpl/inference-router.d.ts +19 -13
  101. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  102. package/dist/src/mcpl/inference-router.js +155 -87
  103. package/dist/src/mcpl/inference-router.js.map +1 -1
  104. package/dist/src/mcpl/log-value.d.ts +7 -0
  105. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  106. package/dist/src/mcpl/log-value.js +9 -0
  107. package/dist/src/mcpl/log-value.js.map +1 -1
  108. package/dist/src/mcpl/modern-connection.d.ts +227 -0
  109. package/dist/src/mcpl/modern-connection.d.ts.map +1 -0
  110. package/dist/src/mcpl/modern-connection.js +1088 -0
  111. package/dist/src/mcpl/modern-connection.js.map +1 -0
  112. package/dist/src/mcpl/prefix-rule.d.ts +18 -0
  113. package/dist/src/mcpl/prefix-rule.d.ts.map +1 -0
  114. package/dist/src/mcpl/prefix-rule.js +25 -0
  115. package/dist/src/mcpl/prefix-rule.js.map +1 -0
  116. package/dist/src/mcpl/protocol-family.d.ts +179 -0
  117. package/dist/src/mcpl/protocol-family.d.ts.map +1 -0
  118. package/dist/src/mcpl/protocol-family.js +697 -0
  119. package/dist/src/mcpl/protocol-family.js.map +1 -0
  120. package/dist/src/mcpl/push-coalescer.d.ts +58 -17
  121. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  122. package/dist/src/mcpl/push-coalescer.js +93 -32
  123. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  124. package/dist/src/mcpl/push-handler.d.ts +16 -9
  125. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  126. package/dist/src/mcpl/push-handler.js +18 -83
  127. package/dist/src/mcpl/push-handler.js.map +1 -1
  128. package/dist/src/mcpl/reference-fetcher.d.ts +76 -13
  129. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  130. package/dist/src/mcpl/reference-fetcher.js +275 -66
  131. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  132. package/dist/src/mcpl/references.d.ts +98 -18
  133. package/dist/src/mcpl/references.d.ts.map +1 -1
  134. package/dist/src/mcpl/references.js +170 -32
  135. package/dist/src/mcpl/references.js.map +1 -1
  136. package/dist/src/mcpl/server-connection.d.ts +406 -31
  137. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  138. package/dist/src/mcpl/server-connection.js +1331 -272
  139. package/dist/src/mcpl/server-connection.js.map +1 -1
  140. package/dist/src/mcpl/server-registry.d.ts +35 -6
  141. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  142. package/dist/src/mcpl/server-registry.js +63 -13
  143. package/dist/src/mcpl/server-registry.js.map +1 -1
  144. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  145. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  146. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  147. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  148. package/dist/src/mcpl/tool-result-normalize.d.ts +54 -0
  149. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -0
  150. package/dist/src/mcpl/tool-result-normalize.js +232 -0
  151. package/dist/src/mcpl/tool-result-normalize.js.map +1 -0
  152. package/dist/src/mcpl/transport.d.ts +104 -12
  153. package/dist/src/mcpl/transport.d.ts.map +1 -1
  154. package/dist/src/mcpl/transport.js +315 -85
  155. package/dist/src/mcpl/transport.js.map +1 -1
  156. package/dist/src/mcpl/types.d.ts +76 -19
  157. package/dist/src/mcpl/types.d.ts.map +1 -1
  158. package/dist/src/mcpl/types.js.map +1 -1
  159. package/dist/src/mcpl/visible-content.d.ts +16 -0
  160. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  161. package/dist/src/mcpl/visible-content.js +26 -0
  162. package/dist/src/mcpl/visible-content.js.map +1 -1
  163. package/dist/src/module-registry.d.ts +34 -6
  164. package/dist/src/module-registry.d.ts.map +1 -1
  165. package/dist/src/module-registry.js +119 -31
  166. package/dist/src/module-registry.js.map +1 -1
  167. package/dist/src/modules/api/index.d.ts +1 -1
  168. package/dist/src/modules/api/index.d.ts.map +1 -1
  169. package/dist/src/modules/api/index.js +3 -2
  170. package/dist/src/modules/api/index.js.map +1 -1
  171. package/dist/src/modules/discord/discord-js-client.d.ts +43 -2
  172. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  173. package/dist/src/modules/discord/discord-js-client.js +286 -67
  174. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  175. package/dist/src/modules/discord/index.d.ts +53 -5
  176. package/dist/src/modules/discord/index.d.ts.map +1 -1
  177. package/dist/src/modules/discord/index.js +712 -186
  178. package/dist/src/modules/discord/index.js.map +1 -1
  179. package/dist/src/modules/discord/refusal.d.ts +14 -0
  180. package/dist/src/modules/discord/refusal.d.ts.map +1 -0
  181. package/dist/src/modules/discord/refusal.js +14 -0
  182. package/dist/src/modules/discord/refusal.js.map +1 -0
  183. package/dist/src/modules/discord/types.d.ts +27 -4
  184. package/dist/src/modules/discord/types.d.ts.map +1 -1
  185. package/dist/src/modules/health/index.d.ts.map +1 -1
  186. package/dist/src/modules/health/index.js +8 -3
  187. package/dist/src/modules/health/index.js.map +1 -1
  188. package/dist/src/modules/history/index.d.ts +3 -1
  189. package/dist/src/modules/history/index.d.ts.map +1 -1
  190. package/dist/src/modules/history/index.js +359 -127
  191. package/dist/src/modules/history/index.js.map +1 -1
  192. package/dist/src/modules/history/semantic.d.ts +28 -1
  193. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  194. package/dist/src/modules/history/semantic.js +177 -32
  195. package/dist/src/modules/history/semantic.js.map +1 -1
  196. package/dist/src/modules/index.d.ts +1 -1
  197. package/dist/src/modules/index.d.ts.map +1 -1
  198. package/dist/src/modules/index.js +1 -1
  199. package/dist/src/modules/index.js.map +1 -1
  200. package/dist/src/modules/unknown-tool.d.ts +10 -0
  201. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  202. package/dist/src/modules/unknown-tool.js +17 -0
  203. package/dist/src/modules/unknown-tool.js.map +1 -0
  204. package/dist/src/modules/workspace/disk-agreement.d.ts +57 -4
  205. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  206. package/dist/src/modules/workspace/disk-agreement.js +75 -7
  207. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  208. package/dist/src/modules/workspace/effects.d.ts +66 -7
  209. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  210. package/dist/src/modules/workspace/effects.js +303 -46
  211. package/dist/src/modules/workspace/effects.js.map +1 -1
  212. package/dist/src/modules/workspace/index.d.ts +313 -45
  213. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  214. package/dist/src/modules/workspace/index.js +1404 -304
  215. package/dist/src/modules/workspace/index.js.map +1 -1
  216. package/dist/src/modules/workspace/observe.d.ts +315 -16
  217. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  218. package/dist/src/modules/workspace/observe.js +727 -37
  219. package/dist/src/modules/workspace/observe.js.map +1 -1
  220. package/dist/src/modules/workspace/reconcile.d.ts +58 -7
  221. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  222. package/dist/src/modules/workspace/reconcile.js +521 -56
  223. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  224. package/dist/src/modules/workspace/types.d.ts +28 -5
  225. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  226. package/dist/src/modules/workspace/types.js.map +1 -1
  227. package/dist/src/modules/workspace/watcher.d.ts +49 -2
  228. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  229. package/dist/src/modules/workspace/watcher.js +158 -29
  230. package/dist/src/modules/workspace/watcher.js.map +1 -1
  231. package/dist/src/prose-drafts.d.ts +240 -0
  232. package/dist/src/prose-drafts.d.ts.map +1 -0
  233. package/dist/src/prose-drafts.js +362 -0
  234. package/dist/src/prose-drafts.js.map +1 -0
  235. package/dist/src/prose-segments.d.ts +21 -2
  236. package/dist/src/prose-segments.d.ts.map +1 -1
  237. package/dist/src/prose-segments.js +46 -9
  238. package/dist/src/prose-segments.js.map +1 -1
  239. package/dist/src/recovery/offline-branch.d.ts +1 -0
  240. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  241. package/dist/src/recovery/offline-branch.js +3 -1
  242. package/dist/src/recovery/offline-branch.js.map +1 -1
  243. package/dist/src/safe-slice.d.ts +5 -1
  244. package/dist/src/safe-slice.d.ts.map +1 -1
  245. package/dist/src/safe-slice.js +9 -1
  246. package/dist/src/safe-slice.js.map +1 -1
  247. package/dist/src/silent-heartbeat.d.ts +112 -39
  248. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  249. package/dist/src/silent-heartbeat.js +100 -29
  250. package/dist/src/silent-heartbeat.js.map +1 -1
  251. package/dist/src/tool-args.d.ts +40 -0
  252. package/dist/src/tool-args.d.ts.map +1 -0
  253. package/dist/src/tool-args.js +207 -0
  254. package/dist/src/tool-args.js.map +1 -0
  255. package/dist/src/tool-image-ledger.d.ts +87 -19
  256. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  257. package/dist/src/tool-image-ledger.js +135 -32
  258. package/dist/src/tool-image-ledger.js.map +1 -1
  259. package/dist/src/tool-presentation.d.ts +9 -0
  260. package/dist/src/tool-presentation.d.ts.map +1 -1
  261. package/dist/src/tool-presentation.js +266 -62
  262. package/dist/src/tool-presentation.js.map +1 -1
  263. package/dist/src/tool-result-guard.d.ts.map +1 -1
  264. package/dist/src/tool-result-guard.js +7 -10
  265. package/dist/src/tool-result-guard.js.map +1 -1
  266. package/dist/src/tool-result-history.d.ts +32 -3
  267. package/dist/src/tool-result-history.d.ts.map +1 -1
  268. package/dist/src/tool-result-history.js +106 -19
  269. package/dist/src/tool-result-history.js.map +1 -1
  270. package/dist/src/tune-out/coordinator.d.ts +25 -1
  271. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  272. package/dist/src/tune-out/coordinator.js +260 -65
  273. package/dist/src/tune-out/coordinator.js.map +1 -1
  274. package/dist/src/types/agent.d.ts +44 -22
  275. package/dist/src/types/agent.d.ts.map +1 -1
  276. package/dist/src/types/events.d.ts +25 -0
  277. package/dist/src/types/events.d.ts.map +1 -1
  278. package/dist/src/types/framework.d.ts +38 -7
  279. package/dist/src/types/framework.d.ts.map +1 -1
  280. package/dist/src/types/inference-log.d.ts +1 -1
  281. package/dist/src/types/inference-log.d.ts.map +1 -1
  282. package/dist/src/types/module.d.ts +77 -14
  283. package/dist/src/types/module.d.ts.map +1 -1
  284. package/dist/src/types/trace.d.ts +97 -8
  285. package/dist/src/types/trace.d.ts.map +1 -1
  286. package/dist/src/usage/usage-tracker.d.ts +5 -0
  287. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  288. package/dist/src/usage/usage-tracker.js +35 -8
  289. package/dist/src/usage/usage-tracker.js.map +1 -1
  290. package/package.json +5 -4
  291. package/src/agent.ts +257 -72
  292. package/src/api/mcp-server-core.ts +1 -1
  293. package/src/api/server.ts +4 -1
  294. package/src/api/types.ts +1 -1
  295. package/src/branch-names.ts +19 -0
  296. package/src/closest-names.ts +94 -0
  297. package/src/code-execution/index.ts +1 -0
  298. package/src/code-execution/py-runner.ts +304 -93
  299. package/src/code-execution/runtime-py.ts +94 -8
  300. package/src/code-execution/tool-definition.ts +85 -7
  301. package/src/config-checks.ts +50 -0
  302. package/src/deadline-timer.ts +44 -0
  303. package/src/error-message.ts +48 -0
  304. package/src/framework.ts +8625 -1984
  305. package/src/gate/event-gate.ts +38 -5
  306. package/src/held-writes.ts +59 -0
  307. package/src/index.ts +29 -5
  308. package/src/mcpl/capability-grant.ts +1 -1
  309. package/src/mcpl/channel-registry.ts +1095 -411
  310. package/src/mcpl/checkpoint-manager.ts +104 -29
  311. package/src/mcpl/conversation-router.ts +28 -8
  312. package/src/mcpl/convert-block.ts +105 -0
  313. package/src/mcpl/errors.ts +28 -0
  314. package/src/mcpl/hook-orchestrator.ts +84 -77
  315. package/src/mcpl/inbound-source.ts +156 -0
  316. package/src/mcpl/index.ts +23 -1
  317. package/src/mcpl/inference-router.ts +179 -103
  318. package/src/mcpl/log-value.ts +10 -0
  319. package/src/mcpl/modern-connection.ts +1182 -0
  320. package/src/mcpl/prefix-rule.ts +32 -0
  321. package/src/mcpl/protocol-family.ts +691 -0
  322. package/src/mcpl/push-coalescer.ts +110 -36
  323. package/src/mcpl/push-handler.ts +20 -90
  324. package/src/mcpl/reference-fetcher.ts +318 -67
  325. package/src/mcpl/references.ts +183 -34
  326. package/src/mcpl/server-connection.ts +1404 -280
  327. package/src/mcpl/server-registry.ts +63 -14
  328. package/src/mcpl/skip-reply-wake.ts +73 -0
  329. package/src/mcpl/tool-result-normalize.ts +294 -0
  330. package/src/mcpl/transport.ts +320 -76
  331. package/src/mcpl/types.ts +77 -19
  332. package/src/mcpl/visible-content.ts +28 -0
  333. package/src/module-registry.ts +126 -36
  334. package/src/modules/api/index.ts +3 -2
  335. package/src/modules/discord/discord-js-client.ts +306 -74
  336. package/src/modules/discord/index.ts +737 -201
  337. package/src/modules/discord/refusal.ts +13 -0
  338. package/src/modules/discord/types.ts +27 -6
  339. package/src/modules/health/index.ts +8 -3
  340. package/src/modules/history/index.ts +410 -120
  341. package/src/modules/history/semantic.ts +189 -32
  342. package/src/modules/index.ts +1 -1
  343. package/src/modules/unknown-tool.ts +16 -0
  344. package/src/modules/workspace/disk-agreement.ts +97 -8
  345. package/src/modules/workspace/effects.ts +291 -36
  346. package/src/modules/workspace/index.ts +1469 -321
  347. package/src/modules/workspace/observe.ts +784 -41
  348. package/src/modules/workspace/reconcile.ts +619 -64
  349. package/src/modules/workspace/types.ts +28 -5
  350. package/src/modules/workspace/watcher.ts +154 -30
  351. package/src/prose-drafts.ts +481 -0
  352. package/src/prose-segments.ts +51 -8
  353. package/src/recovery/offline-branch.ts +4 -1
  354. package/src/safe-slice.ts +9 -1
  355. package/src/silent-heartbeat.ts +184 -59
  356. package/src/tool-args.ts +213 -0
  357. package/src/tool-image-ledger.ts +153 -34
  358. package/src/tool-presentation.ts +233 -42
  359. package/src/tool-result-guard.ts +7 -8
  360. package/src/tool-result-history.ts +124 -20
  361. package/src/tune-out/coordinator.ts +259 -65
  362. package/src/types/agent.ts +44 -24
  363. package/src/types/events.ts +25 -0
  364. package/src/types/framework.ts +38 -7
  365. package/src/types/inference-log.ts +1 -1
  366. package/src/types/module.ts +79 -14
  367. package/src/types/trace.ts +103 -8
  368. package/src/usage/usage-tracker.ts +32 -8
  369. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  370. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  371. package/dist/test/activation-request-sanitize.test.js +0 -89
  372. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  373. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  374. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  375. package/dist/test/agent-cache-ttl.test.js +0 -41
  376. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  377. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  378. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  379. package/dist/test/agent-runtime-settings.test.js +0 -1598
  380. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  381. package/dist/test/api-admin-gate.test.d.ts +0 -2
  382. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  383. package/dist/test/api-admin-gate.test.js +0 -116
  384. package/dist/test/api-admin-gate.test.js.map +0 -1
  385. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  386. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  387. package/dist/test/assistant-persist-refusal.test.js +0 -63
  388. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  389. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  390. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  391. package/dist/test/batched-wake-locus.test.js +0 -166
  392. package/dist/test/batched-wake-locus.test.js.map +0 -1
  393. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  394. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  395. package/dist/test/busy-agent-scheduler.test.js +0 -92
  396. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  397. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  398. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  399. package/dist/test/channel-incoming-targeting.test.js +0 -81
  400. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  401. package/dist/test/channel-label-history.test.d.ts +0 -2
  402. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  403. package/dist/test/channel-label-history.test.js +0 -424
  404. package/dist/test/channel-label-history.test.js.map +0 -1
  405. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  406. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  407. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  408. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  409. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  410. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  411. package/dist/test/channel-registry-routing.test.js +0 -446
  412. package/dist/test/channel-registry-routing.test.js.map +0 -1
  413. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  414. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  415. package/dist/test/channel-registry-typing.test.js +0 -91
  416. package/dist/test/channel-registry-typing.test.js.map +0 -1
  417. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  418. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  419. package/dist/test/channels-changed-respond-first.test.js +0 -74
  420. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  421. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  422. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  423. package/dist/test/checkpoint-attribution.test.js +0 -200
  424. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  425. package/dist/test/code-execution.test.d.ts +0 -11
  426. package/dist/test/code-execution.test.d.ts.map +0 -1
  427. package/dist/test/code-execution.test.js +0 -1272
  428. package/dist/test/code-execution.test.js.map +0 -1
  429. package/dist/test/component-defaults.test.d.ts +0 -2
  430. package/dist/test/component-defaults.test.d.ts.map +0 -1
  431. package/dist/test/component-defaults.test.js +0 -54
  432. package/dist/test/component-defaults.test.js.map +0 -1
  433. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  434. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  435. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  436. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  437. package/dist/test/conversation-router.test.d.ts +0 -2
  438. package/dist/test/conversation-router.test.d.ts.map +0 -1
  439. package/dist/test/conversation-router.test.js +0 -174
  440. package/dist/test/conversation-router.test.js.map +0 -1
  441. package/dist/test/conversation-routing.test.d.ts +0 -7
  442. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  443. package/dist/test/conversation-routing.test.js +0 -621
  444. package/dist/test/conversation-routing.test.js.map +0 -1
  445. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  446. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  447. package/dist/test/deferred-wake-integrity.test.js +0 -320
  448. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  449. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  450. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  451. package/dist/test/discord-awareness-outbox.test.js +0 -371
  452. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  453. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  454. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  455. package/dist/test/discord-history-delete-window.test.js +0 -81
  456. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  457. package/dist/test/discord-history-sync.test.d.ts +0 -2
  458. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  459. package/dist/test/discord-history-sync.test.js +0 -56
  460. package/dist/test/discord-history-sync.test.js.map +0 -1
  461. package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
  462. package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
  463. package/dist/test/discord-typing-keepalive.test.js +0 -77
  464. package/dist/test/discord-typing-keepalive.test.js.map +0 -1
  465. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  466. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  467. package/dist/test/ephemeral-settle.test.js +0 -354
  468. package/dist/test/ephemeral-settle.test.js.map +0 -1
  469. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  470. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  471. package/dist/test/event-gate-passthrough.test.js +0 -289
  472. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  473. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  474. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  475. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  476. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  477. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  478. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  479. package/dist/test/event-gate-rate-limit.test.js +0 -627
  480. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  481. package/dist/test/event-gate-script.test.d.ts +0 -2
  482. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  483. package/dist/test/event-gate-script.test.js +0 -95
  484. package/dist/test/event-gate-script.test.js.map +0 -1
  485. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  486. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  487. package/dist/test/event-gate-selfwake.test.js +0 -125
  488. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  489. package/dist/test/event-gate-tags.test.d.ts +0 -2
  490. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  491. package/dist/test/event-gate-tags.test.js +0 -76
  492. package/dist/test/event-gate-tags.test.js.map +0 -1
  493. package/dist/test/event-gate.test.d.ts +0 -2
  494. package/dist/test/event-gate.test.d.ts.map +0 -1
  495. package/dist/test/event-gate.test.js +0 -919
  496. package/dist/test/event-gate.test.js.map +0 -1
  497. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  498. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  499. package/dist/test/explicit-prose-routing.test.js +0 -1715
  500. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  501. package/dist/test/framework.test.d.ts +0 -5
  502. package/dist/test/framework.test.d.ts.map +0 -1
  503. package/dist/test/framework.test.js +0 -1048
  504. package/dist/test/framework.test.js.map +0 -1
  505. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  506. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  507. package/dist/test/gather-context-timeout.test.js +0 -120
  508. package/dist/test/gather-context-timeout.test.js.map +0 -1
  509. package/dist/test/health-snapshot.test.d.ts +0 -2
  510. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  511. package/dist/test/health-snapshot.test.js +0 -74
  512. package/dist/test/health-snapshot.test.js.map +0 -1
  513. package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
  514. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  515. package/dist/test/helpers/coalescing-fixture.js +0 -152
  516. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  517. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  518. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  519. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  520. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  521. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  522. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  523. package/dist/test/helpers/mock-membrane.js +0 -168
  524. package/dist/test/helpers/mock-membrane.js.map +0 -1
  525. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  526. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  527. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  528. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  529. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  530. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  531. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  532. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  533. package/dist/test/hide-then-turn.test.d.ts +0 -2
  534. package/dist/test/hide-then-turn.test.d.ts.map +0 -1
  535. package/dist/test/hide-then-turn.test.js +0 -101
  536. package/dist/test/hide-then-turn.test.js.map +0 -1
  537. package/dist/test/history-module-overview.test.d.ts +0 -2
  538. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  539. package/dist/test/history-module-overview.test.js +0 -711
  540. package/dist/test/history-module-overview.test.js.map +0 -1
  541. package/dist/test/history-module-realstore.test.d.ts +0 -2
  542. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  543. package/dist/test/history-module-realstore.test.js +0 -363
  544. package/dist/test/history-module-realstore.test.js.map +0 -1
  545. package/dist/test/history-module-semantic.test.d.ts +0 -2
  546. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  547. package/dist/test/history-module-semantic.test.js +0 -213
  548. package/dist/test/history-module-semantic.test.js.map +0 -1
  549. package/dist/test/history-module-ux.test.d.ts +0 -2
  550. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  551. package/dist/test/history-module-ux.test.js +0 -304
  552. package/dist/test/history-module-ux.test.js.map +0 -1
  553. package/dist/test/history-module.test.d.ts +0 -2
  554. package/dist/test/history-module.test.d.ts.map +0 -1
  555. package/dist/test/history-module.test.js +0 -619
  556. package/dist/test/history-module.test.js.map +0 -1
  557. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  558. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  559. package/dist/test/history-semantic-review-findings.test.js +0 -338
  560. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  561. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  562. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  563. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  564. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  565. package/dist/test/host-quiesce.test.d.ts +0 -2
  566. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  567. package/dist/test/host-quiesce.test.js +0 -988
  568. package/dist/test/host-quiesce.test.js.map +0 -1
  569. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  570. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  571. package/dist/test/inference-exhaustion-rewind.test.js +0 -387
  572. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  573. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  574. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  575. package/dist/test/inference-failure-bounds.test.js +0 -1202
  576. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  577. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  578. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  579. package/dist/test/inference-failure-observability.test.js +0 -84
  580. package/dist/test/inference-failure-observability.test.js.map +0 -1
  581. package/dist/test/journal-tool.test.d.ts +0 -2
  582. package/dist/test/journal-tool.test.d.ts.map +0 -1
  583. package/dist/test/journal-tool.test.js +0 -33
  584. package/dist/test/journal-tool.test.js.map +0 -1
  585. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  586. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  587. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  588. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  589. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  590. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  591. package/dist/test/kv-unified-wiring.test.js +0 -95
  592. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  593. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  594. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  595. package/dist/test/late-events-after-stop.test.js +0 -122
  596. package/dist/test/late-events-after-stop.test.js.map +0 -1
  597. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  598. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  599. package/dist/test/liveness-watchdog.test.js +0 -94
  600. package/dist/test/liveness-watchdog.test.js.map +0 -1
  601. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  602. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  603. package/dist/test/machine-close-provenance.test.js +0 -116
  604. package/dist/test/machine-close-provenance.test.js.map +0 -1
  605. package/dist/test/maintenance-timer.test.d.ts +0 -2
  606. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  607. package/dist/test/maintenance-timer.test.js +0 -1827
  608. package/dist/test/maintenance-timer.test.js.map +0 -1
  609. package/dist/test/mcp-server-bin.test.d.ts +0 -2
  610. package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
  611. package/dist/test/mcp-server-bin.test.js +0 -142
  612. package/dist/test/mcp-server-bin.test.js.map +0 -1
  613. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  614. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  615. package/dist/test/mcpl-access-provider.test.js +0 -20
  616. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  617. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  618. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  619. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  620. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  621. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  622. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  623. package/dist/test/mcpl-capability-grant.test.js +0 -111
  624. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  625. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  626. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  627. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  628. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  629. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  630. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  631. package/dist/test/mcpl-child-env.test.js +0 -158
  632. package/dist/test/mcpl-child-env.test.js.map +0 -1
  633. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
  634. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
  635. package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
  636. package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
  637. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  638. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  639. package/dist/test/mcpl-coalescing.test.js +0 -1125
  640. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  641. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  642. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  643. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  644. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  645. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  646. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  647. package/dist/test/mcpl-empty-content.test.js +0 -368
  648. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  649. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  650. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  651. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  652. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  653. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  654. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  655. package/dist/test/mcpl-list-status.test.js +0 -82
  656. package/dist/test/mcpl-list-status.test.js.map +0 -1
  657. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  658. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  659. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  660. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  661. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  662. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  663. package/dist/test/mcpl-push-convert.test.js +0 -42
  664. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  665. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  666. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  667. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  668. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  669. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  670. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  671. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
  672. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  673. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  674. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  675. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  676. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  677. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  678. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  679. package/dist/test/mcpl-reference-fields.test.js +0 -149
  680. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  681. package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
  682. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
  683. package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
  684. package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
  685. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  686. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  687. package/dist/test/mcpl-request-timeout.test.js +0 -138
  688. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  689. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  690. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  691. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  692. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  693. package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
  694. package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
  695. package/dist/test/mcpl-stderr-tail.test.js +0 -210
  696. package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
  697. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  698. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  699. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  700. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  701. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  702. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  703. package/dist/test/mcpl-tool-classes.test.js +0 -80
  704. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  705. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  706. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  707. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  708. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  709. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  710. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  711. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  712. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  713. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  714. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  715. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  716. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  717. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  718. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  719. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  720. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  721. package/dist/test/mcpl-transport-close.test.d.ts +0 -2
  722. package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
  723. package/dist/test/mcpl-transport-close.test.js +0 -373
  724. package/dist/test/mcpl-transport-close.test.js.map +0 -1
  725. package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
  726. package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
  727. package/dist/test/mcpl-transport-spawn.test.js +0 -58
  728. package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
  729. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  730. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  731. package/dist/test/mcpl-ws-transport.test.js +0 -259
  732. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  733. package/dist/test/module-context-integration.test.d.ts +0 -18
  734. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  735. package/dist/test/module-context-integration.test.js +0 -181
  736. package/dist/test/module-context-integration.test.js.map +0 -1
  737. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  738. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  739. package/dist/test/offline-recovery-branch.test.js +0 -570
  740. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  741. package/dist/test/operator-surgery.test.d.ts +0 -2
  742. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  743. package/dist/test/operator-surgery.test.js +0 -595
  744. package/dist/test/operator-surgery.test.js.map +0 -1
  745. package/dist/test/ops-alert.test.d.ts +0 -2
  746. package/dist/test/ops-alert.test.d.ts.map +0 -1
  747. package/dist/test/ops-alert.test.js +0 -153
  748. package/dist/test/ops-alert.test.js.map +0 -1
  749. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  750. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  751. package/dist/test/overbudget-drain-breaker.test.js +0 -198
  752. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  753. package/dist/test/pack-installs-alone.test.d.ts +0 -2
  754. package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
  755. package/dist/test/pack-installs-alone.test.js +0 -284
  756. package/dist/test/pack-installs-alone.test.js.map +0 -1
  757. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  758. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  759. package/dist/test/per-agent-delivery.test.js +0 -108
  760. package/dist/test/per-agent-delivery.test.js.map +0 -1
  761. package/dist/test/physical-window-restart.test.d.ts +0 -16
  762. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  763. package/dist/test/physical-window-restart.test.js +0 -487
  764. package/dist/test/physical-window-restart.test.js.map +0 -1
  765. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  766. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  767. package/dist/test/placed-reaction-baseline.test.js +0 -312
  768. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  769. package/dist/test/present-while-acting.test.d.ts +0 -19
  770. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  771. package/dist/test/present-while-acting.test.js +0 -1649
  772. package/dist/test/present-while-acting.test.js.map +0 -1
  773. package/dist/test/prose-segments.test.d.ts +0 -2
  774. package/dist/test/prose-segments.test.d.ts.map +0 -1
  775. package/dist/test/prose-segments.test.js +0 -52
  776. package/dist/test/prose-segments.test.js.map +0 -1
  777. package/dist/test/prose-stream-router.test.d.ts +0 -2
  778. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  779. package/dist/test/prose-stream-router.test.js +0 -171
  780. package/dist/test/prose-stream-router.test.js.map +0 -1
  781. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  782. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  783. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  784. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  785. package/dist/test/provider-waits.test.d.ts +0 -2
  786. package/dist/test/provider-waits.test.d.ts.map +0 -1
  787. package/dist/test/provider-waits.test.js +0 -2259
  788. package/dist/test/provider-waits.test.js.map +0 -1
  789. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  790. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  791. package/dist/test/puppet-tool-call.test.js +0 -226
  792. package/dist/test/puppet-tool-call.test.js.map +0 -1
  793. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  794. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  795. package/dist/test/puppet-turn-reservation.test.js +0 -139
  796. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  797. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  798. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  799. package/dist/test/reaction-baseline-platform.test.js +0 -124
  800. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  801. package/dist/test/record-journal.test.d.ts +0 -6
  802. package/dist/test/record-journal.test.d.ts.map +0 -1
  803. package/dist/test/record-journal.test.js +0 -269
  804. package/dist/test/record-journal.test.js.map +0 -1
  805. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  806. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  807. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  808. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  809. package/dist/test/refusal-rewind.test.d.ts +0 -2
  810. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  811. package/dist/test/refusal-rewind.test.js +0 -117
  812. package/dist/test/refusal-rewind.test.js.map +0 -1
  813. package/dist/test/refusal-single-call.test.d.ts +0 -2
  814. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  815. package/dist/test/refusal-single-call.test.js +0 -177
  816. package/dist/test/refusal-single-call.test.js.map +0 -1
  817. package/dist/test/release-bump.test.d.ts +0 -2
  818. package/dist/test/release-bump.test.d.ts.map +0 -1
  819. package/dist/test/release-bump.test.js +0 -248
  820. package/dist/test/release-bump.test.js.map +0 -1
  821. package/dist/test/release-changelog.test.d.ts +0 -2
  822. package/dist/test/release-changelog.test.d.ts.map +0 -1
  823. package/dist/test/release-changelog.test.js +0 -159
  824. package/dist/test/release-changelog.test.js.map +0 -1
  825. package/dist/test/release-holds.test.d.ts +0 -2
  826. package/dist/test/release-holds.test.d.ts.map +0 -1
  827. package/dist/test/release-holds.test.js +0 -456
  828. package/dist/test/release-holds.test.js.map +0 -1
  829. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  830. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  831. package/dist/test/same-round-think-policy.test.js +0 -853
  832. package/dist/test/same-round-think-policy.test.js.map +0 -1
  833. package/dist/test/save-recent-image.test.d.ts +0 -26
  834. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  835. package/dist/test/save-recent-image.test.js +0 -974
  836. package/dist/test/save-recent-image.test.js.map +0 -1
  837. package/dist/test/shutdown-errors.test.d.ts +0 -2
  838. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  839. package/dist/test/shutdown-errors.test.js +0 -662
  840. package/dist/test/shutdown-errors.test.js.map +0 -1
  841. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  842. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  843. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  844. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  845. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  846. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  847. package/dist/test/silent-heartbeat.test.js +0 -165
  848. package/dist/test/silent-heartbeat.test.js.map +0 -1
  849. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  850. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  851. package/dist/test/state-scaling-e2e.test.js +0 -206
  852. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  853. package/dist/test/state-scaling.test.d.ts +0 -12
  854. package/dist/test/state-scaling.test.d.ts.map +0 -1
  855. package/dist/test/state-scaling.test.js +0 -251
  856. package/dist/test/state-scaling.test.js.map +0 -1
  857. package/dist/test/stop-with-host-operations.test.d.ts +0 -20
  858. package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
  859. package/dist/test/stop-with-host-operations.test.js +0 -1174
  860. package/dist/test/stop-with-host-operations.test.js.map +0 -1
  861. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  862. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  863. package/dist/test/stop-with-work-in-flight.test.js +0 -784
  864. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  865. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  866. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  867. package/dist/test/surgery-marks-receipt.test.js +0 -540
  868. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  869. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  870. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  871. package/dist/test/thinking-token-stamp.test.js +0 -129
  872. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  873. package/dist/test/timezone.test.d.ts +0 -2
  874. package/dist/test/timezone.test.d.ts.map +0 -1
  875. package/dist/test/timezone.test.js +0 -18
  876. package/dist/test/timezone.test.js.map +0 -1
  877. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  878. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  879. package/dist/test/tool-batch-hook.test.js +0 -275
  880. package/dist/test/tool-batch-hook.test.js.map +0 -1
  881. package/dist/test/tool-policy.test.d.ts +0 -2
  882. package/dist/test/tool-policy.test.d.ts.map +0 -1
  883. package/dist/test/tool-policy.test.js +0 -51
  884. package/dist/test/tool-policy.test.js.map +0 -1
  885. package/dist/test/tool-presentation.test.d.ts +0 -2
  886. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  887. package/dist/test/tool-presentation.test.js +0 -268
  888. package/dist/test/tool-presentation.test.js.map +0 -1
  889. package/dist/test/tool-result-content.test.d.ts +0 -2
  890. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  891. package/dist/test/tool-result-content.test.js +0 -37
  892. package/dist/test/tool-result-content.test.js.map +0 -1
  893. package/dist/test/tool-result-guard.test.d.ts +0 -2
  894. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  895. package/dist/test/tool-result-guard.test.js +0 -3729
  896. package/dist/test/tool-result-guard.test.js.map +0 -1
  897. package/dist/test/tool-result-history.test.d.ts +0 -7
  898. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  899. package/dist/test/tool-result-history.test.js +0 -156
  900. package/dist/test/tool-result-history.test.js.map +0 -1
  901. package/dist/test/tool-result-spill.test.d.ts +0 -13
  902. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  903. package/dist/test/tool-result-spill.test.js +0 -586
  904. package/dist/test/tool-result-spill.test.js.map +0 -1
  905. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  906. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  907. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  908. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  909. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  910. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  911. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  912. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  913. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  914. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  915. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  916. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  917. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  918. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  919. package/dist/test/trunk-channel-routing.test.js +0 -652
  920. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  921. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  922. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  923. package/dist/test/tune-out-e2e.test.js +0 -142
  924. package/dist/test/tune-out-e2e.test.js.map +0 -1
  925. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  926. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  927. package/dist/test/tune-out-gate-composition.test.js +0 -221
  928. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  929. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  930. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  931. package/dist/test/tune-out-lifecycle.test.js +0 -121
  932. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  933. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  934. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  935. package/dist/test/turn-trigger-provenance.test.js +0 -137
  936. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  937. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  938. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  939. package/dist/test/unconvertible-tool-result.test.js +0 -358
  940. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  941. package/dist/test/unsent-compiles.test.d.ts +0 -2
  942. package/dist/test/unsent-compiles.test.d.ts.map +0 -1
  943. package/dist/test/unsent-compiles.test.js +0 -139
  944. package/dist/test/unsent-compiles.test.js.map +0 -1
  945. package/dist/test/usage-accounting.test.d.ts +0 -2
  946. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  947. package/dist/test/usage-accounting.test.js +0 -13
  948. package/dist/test/usage-accounting.test.js.map +0 -1
  949. package/dist/test/usage-tracker.test.d.ts +0 -2
  950. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  951. package/dist/test/usage-tracker.test.js +0 -129
  952. package/dist/test/usage-tracker.test.js.map +0 -1
  953. package/dist/test/utils-surface.test.d.ts +0 -2
  954. package/dist/test/utils-surface.test.d.ts.map +0 -1
  955. package/dist/test/utils-surface.test.js +0 -145
  956. package/dist/test/utils-surface.test.js.map +0 -1
  957. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  958. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  959. package/dist/test/workspace-branch-continuation.test.js +0 -183
  960. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  961. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  962. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  963. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  964. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  965. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  966. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  967. package/dist/test/workspace-path-containment.test.js +0 -91
  968. package/dist/test/workspace-path-containment.test.js.map +0 -1
  969. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  970. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  971. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  972. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  973. package/dist/test/workspace-read-image.test.d.ts +0 -2
  974. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  975. package/dist/test/workspace-read-image.test.js +0 -597
  976. package/dist/test/workspace-read-image.test.js.map +0 -1
  977. package/dist/test/workspace-read-text.test.d.ts +0 -2
  978. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  979. package/dist/test/workspace-read-text.test.js +0 -101
  980. package/dist/test/workspace-read-text.test.js.map +0 -1
  981. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  982. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  983. package/dist/test/workspace-restore-order.test.js +0 -140
  984. package/dist/test/workspace-restore-order.test.js.map +0 -1
  985. package/dist/test/workspace-three-way.test.d.ts +0 -12
  986. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  987. package/dist/test/workspace-three-way.test.js +0 -4094
  988. package/dist/test/workspace-three-way.test.js.map +0 -1
  989. package/dist/test/workspace-watcher.test.d.ts +0 -2
  990. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  991. package/dist/test/workspace-watcher.test.js +0 -281
  992. package/dist/test/workspace-watcher.test.js.map +0 -1
  993. package/dist/test/xml-round-persist.test.d.ts +0 -19
  994. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  995. package/dist/test/xml-round-persist.test.js +0 -235
  996. package/dist/test/xml-round-persist.test.js.map +0 -1
  997. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -7,8 +7,12 @@
7
7
  * - Tools for explicit Discord operations (send, DM, react, etc.)
8
8
  */
9
9
  import { formatZonedDateTime, resolveTimeZone } from '../../timezone.js';
10
+ import { logValue } from '../../mcpl/log-value.js';
11
+ import { unknownToolMessage } from '../unknown-tool.js';
12
+ import { DiscordRefusal } from './refusal.js';
10
13
  export * from './types.js';
11
14
  export { DiscordJsClient } from './discord-js-client.js';
15
+ export { DiscordRefusal } from './refusal.js';
12
16
  const DEFAULT_CONFIG = {
13
17
  handleDMs: true,
14
18
  ignoreBots: true,
@@ -24,6 +28,13 @@ const TYPING_REFRESH_MS = 8_000;
24
28
  // is somehow missed (crash, dropped stream), this guarantees typing can't get
25
29
  // stuck on forever.
26
30
  const TYPING_MAX_MS = 5 * 60_000;
31
+ // The most agents whose latest post the module keeps for their turn's
32
+ // thoughts (lastPosts). Past it, the entry recorded longest ago goes, which
33
+ // bounds the entries of agents that are gone, such as closed conversations'.
34
+ // An entry is wanted only until its turn's speech arrives, so one goes while
35
+ // still wanted only if this many other agents post in that time, and its
36
+ // turn's thoughts then go into no message.
37
+ const MAX_LAST_POSTS = 1_024;
27
38
  // Trace event types that end an agent's turn (onTrace's contract) — any of
28
39
  // these stops the typing keepalive. A turn's tool-call cycles stay inside its
29
40
  // stream, so they come after them. inference:completed is the normal end.
@@ -48,6 +59,223 @@ const TEXT_FILE_EXTENSIONS = [
48
59
  '.html', '.css', '.xml', '.csv', '.log', '.sh', '.bash',
49
60
  ];
50
61
  const CONVERSATION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
62
+ /** A text attachment this many bytes or larger isn't read into the agent's context. */
63
+ const TEXT_FILE_LIMIT_BYTES = 100_000;
64
+ /** activate's and deactivate's answer before the module has started, or once it has stopped. */
65
+ const NOT_RUNNING = "Speech handling wasn't changed: the Discord module hasn't started on this host, or has stopped. Calling it again won't change that until it runs; ask whoever runs this host if you need Discord.";
66
+ /** What the agent reads for a text attachment whose contents couldn't be
67
+ * fetched: by an error, or by an answer that wasn't OK. */
68
+ function attachmentNotFetched(filename) {
69
+ return {
70
+ type: 'text',
71
+ text: `[Attachment: ${filename} couldn't be fetched, so its text isn't in your context. If you need it, ask the sender in the channel to paste it.]`,
72
+ };
73
+ }
74
+ /**
75
+ * What the agent reads when a tool stops on an error that isn't a refusal
76
+ * (DiscordRefusal), such as a network failure: its outcome is unknown. A send
77
+ * repeated blindly can post twice, so those say how to check; the other tools
78
+ * are safe to repeat, since a second call changes nothing more.
79
+ */
80
+ function unexpectedFailure(call, error) {
81
+ const stopped = `discord--${call.name} stopped on an unexpected error: ${errorText(error)}.`;
82
+ const channelId = call.input?.channelId;
83
+ switch (call.name) {
84
+ case 'send':
85
+ return `${stopped} Whether the message was sent is unknown: discord--fetch_history with channelId ${String(channelId)} shows it, so check before you send it again.`;
86
+ case 'dm':
87
+ return `${stopped} Whether the message was sent is unknown. If an earlier discord--dm to this user gave you its channelId, discord--fetch_history with it shows whether this one went; without it, sending it again may send it twice.`;
88
+ case 'edit':
89
+ case 'react':
90
+ case 'delete':
91
+ case 'create_thread':
92
+ return `${stopped} Whether it took effect is unknown. Calling it again is safe: if it did, the second call changes nothing more.`;
93
+ case 'list_guilds':
94
+ case 'list_channels':
95
+ case 'fetch_history':
96
+ return `${stopped} It changes nothing on Discord, so you can call it again.`;
97
+ default:
98
+ return `${stopped} Calling it again is safe.`;
99
+ }
100
+ }
101
+ /** An error's own words, for a sentence about it. */
102
+ function errorText(error) {
103
+ return error instanceof Error ? error.message : String(error);
104
+ }
105
+ /** A count of seconds, for the agent's eyes. */
106
+ function seconds(n) {
107
+ return `${n} second${n === 1 ? '' : 's'}`;
108
+ }
109
+ /** What a stored message reads when it has no text of its own and nothing the
110
+ * host added, so the agent still sees that a message was there. */
111
+ const EMPTY_MESSAGE = '[Empty message]';
112
+ /**
113
+ * Metadata on a stored Discord message whose first block is its own text,
114
+ * '' when it has none, and every block after it the host's: images, inlined
115
+ * file text, attachment notes and EMPTY_MESSAGE. An edit replaces content and
116
+ * can't touch metadata, so the mark holds for the row's life, and it tells an
117
+ * edit or a sync which block is Discord's to replace. For the same reason a
118
+ * row stored before the mark never gets it, even once a history sync
119
+ * rebuilds it in this layout. The request builder drops an empty text block,
120
+ * as it does wherever one occurs.
121
+ */
122
+ const TEXT_FIRST = 'discordTextFirst';
123
+ /** A message's own text as a stored row holds it: '' when it's blank. */
124
+ function ownText(content) {
125
+ return content.trim() ? content : '';
126
+ }
127
+ const isEmptyMarker = (block) => block.type === 'text' && block.text === EMPTY_MESSAGE;
128
+ const isBlankText = (block) => block.type === 'text' && !block.text.trim();
129
+ /**
130
+ * A text-first row's content: the message's own text, '' when it has none,
131
+ * then `after`, what the host added; EMPTY_MESSAGE when there's neither.
132
+ */
133
+ function textFirst(text, after = []) {
134
+ return [{ type: 'text', text }, ...(text || after.length > 0 ? after : [{ type: 'text', text: EMPTY_MESSAGE }])];
135
+ }
136
+ /** A text-first row's content with `text` as the message's own text, in
137
+ * place of its first block, keeping what the host added after it. */
138
+ function withOwnText(content, text) {
139
+ return textFirst(ownText(text), content.slice(1).filter((block) => !isEmptyMarker(block)));
140
+ }
141
+ /**
142
+ * The content a stored Discord message should hold once Discord's text for it
143
+ * is `discordText`, or null when it holds that already.
144
+ *
145
+ * A text-first row keeps every block the host added and replaces only its own
146
+ * text; EMPTY_MESSAGE goes once there is text, and comes back when nothing is
147
+ * left. A row stored before that layout held its own text first when it had
148
+ * any, but a message without text began with what the host added, which may
149
+ * be text, a file's contents or a note, so its first block can't be told
150
+ * apart. Such a row is left alone unless Discord's text is there and differs
151
+ * from its first block, and then that text takes the first block's place
152
+ * when it's text, and goes before it when it isn't, and EMPTY_MESSAGE goes.
153
+ * So a message without text, stored beginning with a file's contents or a
154
+ * note, loses that block. The history sync, which has the message's
155
+ * attachments, rebuilds such a row instead (needsRebuild).
156
+ */
157
+ function contentForText(stored, discordText) {
158
+ const text = ownText(discordText);
159
+ const [first, ...rest] = stored.content;
160
+ if (stored.metadata?.[TEXT_FIRST] === true) {
161
+ if (first?.type === 'text' && first.text === text)
162
+ return null;
163
+ return withOwnText(stored.content, text);
164
+ }
165
+ if (!text || (first?.type === 'text' && first.text === discordText))
166
+ return null;
167
+ return [{ type: 'text', text: discordText }, ...(first?.type === 'text' ? rest : stored.content).filter((block) => !isEmptyMarker(block))];
168
+ }
169
+ /**
170
+ * Whether a stored row lacks or holds more than the live conversion gives
171
+ * Discord's `message` now, its own text aside, so the history sync stores it
172
+ * through that conversion again. The conversion gives each attachment one
173
+ * block: an image, a file's text, or a note when it isn't fetched or isn't
174
+ * read. So a text-first row holds one block per attachment after its text,
175
+ * EMPTY_MESSAGE aside, which the text's rule keeps. A row stored before that
176
+ * layout held its text, when it had any, then those blocks, or EMPTY_MESSAGE
177
+ * alone; one that doesn't was blanked or stripped by an earlier edit or sync,
178
+ * or its message gained or lost text, or lost an attachment. Its first block,
179
+ * when it is the message's own text, counts as that text, even when the text
180
+ * reads as EMPTY_MESSAGE. For a message without text of its own, a first
181
+ * block that's text may be what the host added or a caption the message has
182
+ * since lost, which no count tells apart, so that row is stored again; it
183
+ * then begins with its own '' text. One whose text alone changed is left to
184
+ * the text's rule, which keeps the blocks after it. History without
185
+ * attachments can't be compared, so it rebuilds nothing.
186
+ */
187
+ function needsRebuild(stored, message) {
188
+ if (message.attachments === undefined)
189
+ return false;
190
+ const attachments = message.attachments.length;
191
+ const held = (blocks) => blocks.filter((block) => !isBlankText(block) && !isEmptyMarker(block)).length;
192
+ if (stored.metadata?.[TEXT_FIRST] === true)
193
+ return held(stored.content.slice(1)) !== attachments;
194
+ const own = ownText(message.content);
195
+ const [first, ...rest] = stored.content;
196
+ if (!own && first?.type === 'text' && !isBlankText(first) && !isEmptyMarker(first))
197
+ return true;
198
+ const expected = (own ? 1 : 0) + attachments;
199
+ const counted = own && first?.type === 'text' && first.text === own ? 1 + held(rest) : held(stored.content);
200
+ return counted !== expected || (expected === 0 && !stored.content.some(isEmptyMarker));
201
+ }
202
+ /**
203
+ * Whether a row holds non-blank text besides its message's own text now,
204
+ * which may be what a file gave. A marked row whose message's text changed
205
+ * counts its old text here too, which can delay its rebuild until a fetch
206
+ * succeeds but never loses text.
207
+ */
208
+ function holdsOtherText(stored, message) {
209
+ const own = ownText(message.content);
210
+ const [first, ...rest] = stored.content;
211
+ const others = own && first?.type === 'text' && first.text === own ? rest : stored.content;
212
+ return others.some((block) => block.type === 'text' && !isBlankText(block) && !isEmptyMarker(block));
213
+ }
214
+ /** Whether a message's conversion holds, after the message's own text, the
215
+ * note for an attachment of its whose text wasn't fetched. */
216
+ function holdsNotFetched(content, message) {
217
+ const notes = new Set((message.attachments ?? []).map((attachment) => attachmentNotFetched(attachment.filename).text));
218
+ return content.slice(1).some((block) => block.type === 'text' && notes.has(block.text));
219
+ }
220
+ /** A URL without its query or fragment: Discord's CDN signs an attachment's
221
+ * URL afresh in each payload, so only its path names the attachment. */
222
+ function urlPath(url) {
223
+ return url.split(/[?#]/)[0] ?? url;
224
+ }
225
+ /**
226
+ * Whether a block the host added for an attachment stands for none of
227
+ * `attachments`, matched as the live conversion writes it: an image by its
228
+ * URL, a file's text by its header, and a note by the filename it opens
229
+ * with, as every note for an attachment does, whatever its words after it.
230
+ * A block that stands for no attachment, such as EMPTY_MESSAGE, never does.
231
+ */
232
+ function forGoneAttachment(block, attachments) {
233
+ if (block.type === 'image') {
234
+ const source = block.source;
235
+ if (source?.type !== 'url' || typeof source.url !== 'string')
236
+ return false;
237
+ const path = urlPath(source.url);
238
+ return !attachments.some((attachment) => urlPath(attachment.url) === path);
239
+ }
240
+ if (block.type !== 'text')
241
+ return false;
242
+ const { text } = block;
243
+ const opening = text.startsWith('\n--- File: ') ? (filename) => `\n--- File: ${filename} ---\n`
244
+ : text.startsWith('[Attachment: ') ? (filename) => `[Attachment: ${filename} `
245
+ : null;
246
+ return opening !== null && !attachments.some((attachment) => text.startsWith(opening(attachment.filename)));
247
+ }
248
+ /**
249
+ * A text-first row's content without the blocks the host added for
250
+ * attachments its message no longer has, or `content` itself when none is
251
+ * gone. A block stays while an attachment it may stand for remains, so of two
252
+ * files of one name, removing one keeps both blocks, and a block that names
253
+ * no attachment stays. EMPTY_MESSAGE comes back once nothing is left.
254
+ */
255
+ function withoutGoneAttachments(content, attachments) {
256
+ const [first, ...rest] = content;
257
+ if (first?.type !== 'text')
258
+ return content;
259
+ const kept = rest.filter((block) => !forGoneAttachment(block, attachments));
260
+ if (kept.length === rest.length)
261
+ return content;
262
+ return textFirst(first.text, kept);
263
+ }
264
+ /** The ids to remove for a message's rows: each row once, and a body stored
265
+ * in shards once, since removing one shard takes out its whole body. */
266
+ function oncePerBody(rows) {
267
+ const bodies = new Set();
268
+ const ids = [];
269
+ for (const row of rows) {
270
+ if (row.bodyGroupId) {
271
+ if (bodies.has(row.bodyGroupId))
272
+ continue;
273
+ bodies.add(row.bodyGroupId);
274
+ }
275
+ ids.push(row.id);
276
+ }
277
+ return ids;
278
+ }
51
279
  /**
52
280
  * Discord module for the agent framework.
53
281
  */
@@ -65,8 +293,39 @@ export class DiscordModule {
65
293
  };
66
294
  // Track current reply context per agent
67
295
  replyContexts = new Map();
68
- // Last message sent by a tool (for thought-editing)
69
- lastSentMessage = null;
296
+ // Where a tool turn's thoughts go (onAgentSpeech edits them in as a
297
+ // spoiler): each agent's latest message posted in a turn, with that turn's
298
+ // id (notePost). A `send` or `reply` records the message its client call
299
+ // returns, and so does the module's own speech. Thoughts go into it only
300
+ // when their speech ends the same turn, and taking them removes it. So
301
+ // neither another agent's message nor one from an earlier turn takes them,
302
+ // and an attempt in the turn that is refused or throws leaves the turn's own
303
+ // post in place. Turn ids increase, so an earlier turn's post that finishes
304
+ // after a later turn's leaves the later one in place. A DM records nothing,
305
+ // since its channel isn't known here, and neither does a post whose call or
306
+ // speech carries no turn. An entry goes when its thoughts are taken, when
307
+ // the agent's next post replaces it, or past MAX_LAST_POSTS agents.
308
+ lastPosts = new Map();
309
+ // One set per running history sync: the Discord messages the live path
310
+ // stored, edited or was told were deleted since that sync began. What the
311
+ // live path saw is newer than what the sync fetched, so the sync leaves
312
+ // those messages alone.
313
+ liveDuringSync = new Set();
314
+ // The Discord messages whose live create is on its way to the store: from
315
+ // the moment the live path takes one up, through its conversion and the
316
+ // event queue, until a query shows the row onProcess answered with, stored
317
+ // or held. The framework holds a module's writes while the agent's turn is
318
+ // alive or the host is quiesced, and queryMessages shows a held write's
319
+ // row, marked held, so the next Discord event finds it either way and ends
320
+ // its entry. A history sync stores none of these messages, so no message is
321
+ // stored twice. A delete before the create is handled drops it: nothing is
322
+ // stored and nobody is woken. Any message's entry ends when its delete is
323
+ // handled, since a sync running then skips the message (noteLive) and a
324
+ // later one doesn't fetch it. An edit while it converts is folded into it.
325
+ // A create whose answer the framework fails to write or hold shows no row,
326
+ // and keeps its entry, and the sync off its message, until its delete is
327
+ // handled or the module stops.
328
+ creating = new Map();
70
329
  // Typing keepalive: agentName -> { interval, deadline }. Started on
71
330
  // inference:started, stopped on the turn's terminal trace event. Discord's
72
331
  // typing indicator auto-expires after ~10s, so we re-trigger on an interval
@@ -109,7 +368,7 @@ export class DiscordModule {
109
368
  }
110
369
  // Set up Discord event handlers
111
370
  this.client.onMessage((message) => this.handleDiscordMessage(message));
112
- this.client.onMessageEdit((id, content) => this.handleDiscordMessageEdit(id, content));
371
+ this.client.onMessageEdit((id, content, attachments) => this.handleDiscordMessageEdit(id, content, attachments));
113
372
  this.client.onMessageDelete((id) => this.handleDiscordMessageDelete(id));
114
373
  // Set up ready handler for history sync on (re)connect
115
374
  if (this.client.onReady) {
@@ -143,8 +402,13 @@ export class DiscordModule {
143
402
  clearInterval(interval);
144
403
  }
145
404
  this.typingLoops.clear();
146
- await this.client.disconnect();
405
+ // From here the module writes nothing, though the framework may dispatch
406
+ // to it until stop() resolves: the live path takes nothing up, a create
407
+ // still converting finds the context gone, and onProcess answers nothing.
408
+ // The record of creates on their way goes with the context.
147
409
  this.ctx = null;
410
+ this.creating.clear();
411
+ await this.client.disconnect();
148
412
  }
149
413
  /**
150
414
  * Start (or refresh) the typing keepalive for an agent's reply channel.
@@ -328,7 +592,7 @@ export class DiscordModule {
328
592
  },
329
593
  {
330
594
  name: 'list_channels',
331
- description: 'List all channels in a Discord server',
595
+ description: 'List the channels in a Discord server, with their ids. Threads aren\'t listed.',
332
596
  inputSchema: {
333
597
  type: 'object',
334
598
  properties: {
@@ -339,7 +603,7 @@ export class DiscordModule {
339
603
  },
340
604
  {
341
605
  name: 'fetch_history',
342
- description: 'Fetch recent message history from a channel',
606
+ description: 'Fetch recent message history from a channel. A message with attachments lists each one\'s filename, type and size; their contents aren\'t included.',
343
607
  inputSchema: {
344
608
  type: 'object',
345
609
  properties: {
@@ -356,11 +620,11 @@ export class DiscordModule {
356
620
  try {
357
621
  switch (call.name) {
358
622
  case 'send':
359
- return await this.handleSend(call.input);
623
+ return await this.handleSend(call.input, call);
360
624
  case 'dm':
361
625
  return await this.handleDM(call.input);
362
626
  case 'reply':
363
- return await this.handleReply(call.input);
627
+ return await this.handleReply(call.input, call);
364
628
  case 'react':
365
629
  return await this.handleReact(call.input);
366
630
  case 'edit':
@@ -372,7 +636,8 @@ export class DiscordModule {
372
636
  case 'set_reply_context':
373
637
  return await this.handleSetReplyContext(call.input);
374
638
  case 'activate':
375
- return await this.handleActivate(call.input);
639
+ // A call with no input at all activates with the defaults.
640
+ return await this.handleActivate((call.input ?? {}));
376
641
  case 'deactivate':
377
642
  return await this.handleDeactivate();
378
643
  case 'list_guilds':
@@ -382,21 +647,38 @@ export class DiscordModule {
382
647
  case 'fetch_history':
383
648
  return await this.handleFetchHistory(call.input);
384
649
  default:
385
- return { success: false, error: `Unknown tool: ${call.name}`, isError: true };
650
+ return { success: false, error: unknownToolMessage(this.name, call.name, this.getTools()), isError: true };
386
651
  }
387
652
  }
388
653
  catch (error) {
654
+ // A refusal says what was refused and what to do instead; anything else
655
+ // is unexpected, and its outcome is worded by the tool.
389
656
  return {
390
657
  success: false,
391
- error: error instanceof Error ? error.message : String(error),
658
+ error: error instanceof DiscordRefusal ? error.message : unexpectedFailure(call, error),
392
659
  isError: true,
393
660
  };
394
661
  }
395
662
  }
396
663
  async onProcess(event, state) {
664
+ // A stopped module answers nothing (stop()).
665
+ if (!this.ctx)
666
+ return {};
667
+ // Each Discord event ends the entries whose row a query shows now, stored
668
+ // or held.
669
+ if (event.type.startsWith('discord:')) {
670
+ for (const [id, live] of this.creating)
671
+ if (live.phase === 'writing' && this.storedRow(id))
672
+ this.creating.delete(id);
673
+ }
397
674
  // Handle new Discord message
398
675
  if (event.type === 'discord:message') {
399
676
  const msg = event;
677
+ const live = this.creating.get(msg.discordMessageId);
678
+ // Deleted while its create waited in the queue: nothing is stored, and
679
+ // nobody is woken by a message its author took back.
680
+ if (live?.deleted)
681
+ return {};
400
682
  const currentState = state.getState() ?? {
401
683
  connectedGuilds: [],
402
684
  conversations: {},
@@ -435,21 +717,33 @@ export class DiscordModule {
435
717
  console.warn('Discord: Failed to send typing indicator:', err);
436
718
  });
437
719
  }
438
- // Build content blocks from message and attachments
439
- const contentBlocks = msg.contentBlocks ?? [{ type: 'text', text: msg.content }];
720
+ // Built from the message and its attachments, or from its text alone
721
+ // when an event carries no blocks; text first, as TEXT_FIRST marks.
722
+ const contentBlocks = msg.contentBlocks ?? textFirst(ownText(msg.content));
723
+ this.noteLive(msg.discordMessageId);
724
+ // A message a history sync wrote first, stored or held, keeps that row:
725
+ // the create adds none, and still sets the reply context and asks for
726
+ // inference, since the message arrived live. Either way the entry waits
727
+ // until a Discord event finds the message's row.
728
+ const stored = this.storedRow(msg.discordMessageId);
729
+ if (live)
730
+ live.phase = 'writing';
440
731
  // Return declarative response - framework applies all writes atomically
441
732
  return {
442
- addMessages: [
443
- {
444
- participant: msg.authorName,
445
- content: contentBlocks,
446
- metadata: {
447
- external: { source: 'discord', id: msg.discordMessageId },
448
- channelId: msg.channelId,
449
- timestamp: msg.timestamp,
733
+ ...(stored ? {} : {
734
+ addMessages: [
735
+ {
736
+ participant: msg.authorName,
737
+ content: contentBlocks,
738
+ metadata: {
739
+ external: { source: 'discord', id: msg.discordMessageId },
740
+ channelId: msg.channelId,
741
+ timestamp: msg.timestamp,
742
+ [TEXT_FIRST]: true,
743
+ },
450
744
  },
451
- },
452
- ],
745
+ ],
746
+ }),
453
747
  stateUpdate: newState,
454
748
  requestInference: msg.shouldTriggerInference ?? true,
455
749
  };
@@ -457,28 +751,50 @@ export class DiscordModule {
457
751
  // Handle Discord message edit
458
752
  if (event.type === 'discord:edit') {
459
753
  const edit = event;
460
- const internalId = state.findMessageByExternalId('discord', edit.discordMessageId);
461
- if (!internalId) {
754
+ // The message's row, not only its id: an edit changes the message's own
755
+ // text and keeps what the host added beside it, but for the blocks of
756
+ // attachments the edit took away, when the client passed the edit's
757
+ // attachments and the row is text-first.
758
+ // An edit that comes while the live path converts the message's create
759
+ // is folded into that create (handleDiscordMessageEdit). Any other edit
760
+ // to a message with no row, stored or held, is dropped: a history sync
761
+ // running now stores the text it fetched, and the next sync's text rule
762
+ // changes it. A row stored in shards can't be edited one shard at a
763
+ // time, so it keeps its text, and the host's log says so. Only an edit
764
+ // that writes marks the message live, so an update that changes nothing
765
+ // leaves a running sync free to rewrite the row.
766
+ const stored = this.storedRow(edit.discordMessageId);
767
+ if (!stored)
768
+ return {};
769
+ if (stored.bodyGroupId) {
770
+ console.warn(`Discord: An update to message ${edit.discordMessageId} wasn't applied: it's stored in shards, which can't be edited, so if its text changed, the agent still has the earlier text`);
462
771
  return {};
463
772
  }
464
- return {
465
- editMessages: [
466
- {
467
- messageId: internalId,
468
- content: [{ type: 'text', text: edit.newContent }],
469
- },
470
- ],
471
- };
773
+ const texted = contentForText(stored, edit.newContent);
774
+ const content = edit.attachments && stored.metadata?.[TEXT_FIRST] === true
775
+ ? withoutGoneAttachments(texted ?? stored.content, edit.attachments)
776
+ : texted;
777
+ if (!content || content === stored.content)
778
+ return {};
779
+ this.noteLive(edit.discordMessageId);
780
+ return { editMessages: [{ messageId: stored.id, content }] };
472
781
  }
473
782
  // Handle Discord message delete
474
783
  if (event.type === 'discord:delete') {
475
784
  const del = event;
476
- const internalId = state.findMessageByExternalId('discord', del.discordMessageId);
477
- if (!internalId) {
785
+ // Every row of the message goes, so one stored twice doesn't outlive
786
+ // its delete as a copy. A row stored in shards is removed whole: the
787
+ // framework takes out the body a shard belongs to. The message is the
788
+ // live path's even when no row is found, so a running sync doesn't
789
+ // store what its fetch may still hold, and that ends its entry.
790
+ this.noteLive(del.discordMessageId);
791
+ this.creating.delete(del.discordMessageId);
792
+ const rows = this.storedRows(del.discordMessageId);
793
+ if (rows.length === 0) {
478
794
  return {};
479
795
  }
480
796
  return {
481
- removeMessages: [internalId],
797
+ removeMessages: oncePerBody(rows),
482
798
  };
483
799
  }
484
800
  return {};
@@ -487,13 +803,15 @@ export class DiscordModule {
487
803
  * Handle agent speech - send to Discord.
488
804
  */
489
805
  async onAgentSpeech(agentName, content, context) {
490
- // If thoughts are present and we have a recently sent message, edit it
491
- // to prepend the thoughts as spoiler text.
492
- if (context.thoughts && context.thoughts.length > 0 && this.lastSentMessage) {
806
+ // If thoughts are present and this agent posted a message in the turn
807
+ // they end (lastPosts), edit it to prepend the thoughts as spoiler text.
808
+ const post = this.lastPosts.get(agentName);
809
+ if (context.thoughts && context.thoughts.length > 0 && post && post.turnId === context.turnId) {
810
+ this.lastPosts.delete(agentName);
493
811
  const thoughtText = this.extractText(context.thoughts);
494
812
  if (thoughtText) {
495
813
  try {
496
- const { channelId, messageId } = this.lastSentMessage;
814
+ const { channelId, messageId } = post;
497
815
  // Fetch the current message content via the client isn't available,
498
816
  // but we know the tool just sent it — reconstruct by editing with
499
817
  // spoiler-wrapped thoughts prepended.
@@ -506,7 +824,6 @@ export class DiscordModule {
506
824
  console.warn('Discord: Failed to edit thoughts into message:', err);
507
825
  }
508
826
  }
509
- this.lastSentMessage = null;
510
827
  }
511
828
  // Get reply context for this agent
512
829
  const replyCtx = this.replyContexts.get(agentName) ?? this.replyContexts.get('default');
@@ -521,7 +838,7 @@ export class DiscordModule {
521
838
  return;
522
839
  }
523
840
  // Check rate limit
524
- if (!this.checkRateLimit(replyCtx.channelId)) {
841
+ if (this.rateLimitWait(replyCtx.channelId) > 0) {
525
842
  console.warn(`Discord: Rate limited for channel ${replyCtx.channelId}`);
526
843
  return;
527
844
  }
@@ -530,8 +847,8 @@ export class DiscordModule {
530
847
  const result = await this.client.sendMessage(targetChannel, text, {
531
848
  replyTo: replyCtx.replyToMessageId,
532
849
  });
533
- // Track for thought-editing (in case of chained speech)
534
- this.lastSentMessage = { channelId: targetChannel, messageId: result.messageId };
850
+ // Track for thought-editing (in case of chained speech in the same turn)
851
+ this.notePost(agentName, context.turnId, targetChannel, result.messageId);
535
852
  // Clear reply-to after sending (don't keep replying to same message)
536
853
  replyCtx.replyToMessageId = undefined;
537
854
  replyCtx.lastActivity = Date.now();
@@ -612,114 +929,190 @@ export class DiscordModule {
612
929
  }
613
930
  /**
614
931
  * Sync history for a specific channel - compare with Context Manager and update.
615
- * Uses queryMessages() to enumerate stored messages for proper edit/delete detection.
932
+ * Uses queryMessages() to enumerate the channel's rows, a held write's among
933
+ * them, for proper edit/delete detection.
934
+ *
935
+ * The live path can store, edit or remove a row whenever the sync awaits.
936
+ * So the sync fetches history and runs the live conversion it needs first,
937
+ * then reads the store again and writes with no await between that read
938
+ * and its writes. It leaves alone any message the live path stored,
939
+ * changed or was told was deleted while it ran (liveDuringSync), an update
940
+ * that changed nothing aside. A live edit to a message nobody had stored
941
+ * yet is dropped, so a message the sync stores holds the text it fetched
942
+ * until the next sync's text rule changes it. The framework writes an
943
+ * event's response only once every module has processed the event, so an
944
+ * event processed before the sync began can still be written after the
945
+ * sync's own writes, which this doesn't cover.
616
946
  */
617
947
  async syncChannelHistory(channelId) {
618
- if (!this.ctx) {
619
- return { newMessages: 0, editedMessages: 0, deletedMessages: 0 };
948
+ const unchanged = { newMessages: 0, editedMessages: 0, deletedMessages: 0 };
949
+ const ctx = this.ctx;
950
+ if (!ctx) {
951
+ return unchanged;
620
952
  }
621
- const scrollback = this.config.historyScrollback ?? 50;
622
- // Fetch recent history from Discord
623
- const discordMessages = await this.client.fetchHistory(channelId, { limit: scrollback });
624
- // Build a map of Discord message ID -> content for comparison
625
- const discordMessageMap = new Map();
626
- for (const msg of discordMessages) {
627
- discordMessageMap.set(msg.id, {
628
- content: msg.content,
629
- authorName: msg.authorName,
630
- timestamp: msg.timestamp,
631
- });
632
- }
633
- // Query Context Manager for all Discord messages from this channel
634
- const { messages: storedMessages } = this.ctx.queryMessages({
635
- source: 'discord',
636
- metadata: { channelId },
637
- });
638
- // Build a map of stored message external IDs -> internal data
639
- const storedMessageMap = new Map();
640
- for (const msg of storedMessages) {
641
- const external = msg.metadata?.external;
642
- if (external?.id) {
643
- // Extract text content for comparison
644
- const textContent = msg.content
645
- .filter((block) => block.type === 'text')
646
- .map((block) => block.text)
647
- .join('\n');
648
- storedMessageMap.set(external.id, {
649
- internalId: msg.id,
650
- content: textContent,
651
- sourceTimestamp: typeof msg.metadata?.timestamp === 'number'
652
- ? msg.metadata.timestamp
653
- : undefined,
654
- });
953
+ const live = new Set();
954
+ this.liveDuringSync.add(live);
955
+ try {
956
+ const scrollback = this.config.historyScrollback ?? 50;
957
+ // The rows stored as the sync begins. One that is gone by the time it
958
+ // writes was removed meanwhile, and isn't the sync's to bring back.
959
+ const before = this.storedByDiscordId(ctx, channelId);
960
+ // Fetch recent history from Discord. If the module stopped or restarted
961
+ // meanwhile, this sync's context is gone and it writes nothing.
962
+ const discordMessages = await this.client.fetchHistory(channelId, { limit: scrollback });
963
+ if (this.ctx !== ctx)
964
+ return unchanged;
965
+ // Discord's ids, for delete detection inside the fetched window.
966
+ const discordIds = new Set(discordMessages.map((msg) => msg.id));
967
+ // Discord history is newest-first, while addMessage appends to the stored
968
+ // conversation. Process oldest-first so missed messages retain chronology.
969
+ const chronologicalMessages = [...discordMessages].reverse();
970
+ // The live conversion, which fetches attachments, for a message the
971
+ // store lacks and for a row that doesn't hold what the conversion gives
972
+ // the message now (needsRebuild). A row stored in shards can't be
973
+ // edited one shard at a time, so it's left as it is.
974
+ const converted = new Map();
975
+ for (const discordMsg of chronologicalMessages) {
976
+ const stored = before.get(discordMsg.id)?.[0];
977
+ if (stored && (stored.bodyGroupId || !needsRebuild(stored, discordMsg)))
978
+ continue;
979
+ const content = await this.convertMessageToContent(discordMsg);
980
+ if (this.ctx !== ctx)
981
+ return unchanged;
982
+ // A failed fetch never costs a row the text it holds: when a file
983
+ // wasn't fetched, a row holding other text, which may be that file's,
984
+ // keeps what it holds, and the next sync tries again.
985
+ if (stored && holdsNotFetched(content, discordMsg) && holdsOtherText(stored, discordMsg))
986
+ continue;
987
+ converted.set(discordMsg.id, content);
655
988
  }
656
- }
657
- let newMessages = 0;
658
- let editedMessages = 0;
659
- let deletedMessages = 0;
660
- // Discord history is newest-first, while addMessage appends to the stored
661
- // conversation. Process oldest-first so missed messages retain chronology.
662
- const chronologicalMessages = [...discordMessages].reverse();
663
- for (const discordMsg of chronologicalMessages) {
664
- const stored = storedMessageMap.get(discordMsg.id);
665
- if (!stored) {
666
- // New message we haven't seen
667
- // For bot's own messages (from previous sessions), use the agent name
668
- // This ensures continuity of conversation history
669
- const isBotMessage = discordMsg.authorId === this.state.botUserId;
670
- const agentName = this.ctx.getAgents()[0]?.name;
671
- const participantName = isBotMessage
672
- ? (agentName ?? discordMsg.authorName)
673
- : discordMsg.authorName;
674
- this.ctx.addMessage(participantName, [{ type: 'text', text: discordMsg.content }], {
675
- external: { source: 'discord', id: discordMsg.id },
676
- channelId,
677
- timestamp: discordMsg.timestamp.getTime(),
678
- isOwnMessage: isBotMessage, // Mark as our own for reference
679
- });
680
- newMessages++;
989
+ // From here on nothing awaits, so the rows read now are the rows written.
990
+ const storedMessageMap = this.storedByDiscordId(ctx, channelId);
991
+ let newMessages = 0;
992
+ let editedMessages = 0;
993
+ let deletedMessages = 0;
994
+ for (const discordMsg of chronologicalMessages) {
995
+ if (live.has(discordMsg.id))
996
+ continue;
997
+ const stored = storedMessageMap.get(discordMsg.id)?.[0];
998
+ if (!stored) {
999
+ // A message we haven't stored. One that was stored when the sync
1000
+ // began, and isn't now, was removed meanwhile: not the sync's to
1001
+ // bring back. One the live path is storing (creating) is the live
1002
+ // path's, so it isn't stored twice.
1003
+ const content = converted.get(discordMsg.id);
1004
+ if (!content || before.has(discordMsg.id) || this.creating.has(discordMsg.id))
1005
+ continue;
1006
+ // For bot's own messages (from previous sessions), use the agent name
1007
+ // This ensures continuity of conversation history
1008
+ const isBotMessage = discordMsg.authorId === this.state.botUserId;
1009
+ const agentName = ctx.getAgents()[0]?.name;
1010
+ const participantName = isBotMessage
1011
+ ? (agentName ?? discordMsg.authorName)
1012
+ : discordMsg.authorName;
1013
+ ctx.addMessage(participantName, content, {
1014
+ external: { source: 'discord', id: discordMsg.id },
1015
+ channelId,
1016
+ timestamp: discordMsg.timestamp.getTime(),
1017
+ isOwnMessage: isBotMessage, // Mark as our own for reference
1018
+ [TEXT_FIRST]: true,
1019
+ });
1020
+ newMessages++;
1021
+ }
1022
+ else if (!stored.bodyGroupId) {
1023
+ // We have this message - check whether it changed (offline edit detection)
1024
+ const content = needsRebuild(stored, discordMsg)
1025
+ ? converted.get(discordMsg.id)
1026
+ : contentForText(stored, discordMsg.content);
1027
+ if (content) {
1028
+ ctx.editMessage(stored.id, content);
1029
+ editedMessages++;
1030
+ }
1031
+ }
681
1032
  }
682
- else {
683
- // We have this message - check if content changed (offline edit detection)
684
- if (stored.content !== discordMsg.content) {
685
- this.ctx.editMessage(stored.internalId, [
686
- { type: 'text', text: discordMsg.content },
687
- ]);
688
- editedMessages++;
1033
+ // Check for deletes only inside the history we actually fetched. A short
1034
+ // response covers complete available history; a full page has an older
1035
+ // boundary whose tied millisecond must be preserved.
1036
+ const fetchedCompleteHistory = discordMessages.length < scrollback;
1037
+ const oldestFetchedTimestamp = discordMessages.length > 0
1038
+ ? Math.min(...discordMessages.map((msg) => msg.timestamp.getTime()))
1039
+ : undefined;
1040
+ for (const [externalId, rows] of storedMessageMap) {
1041
+ const stored = rows[0];
1042
+ const sourceTimestamp = typeof stored.metadata?.timestamp === 'number'
1043
+ ? stored.metadata.timestamp
1044
+ : undefined;
1045
+ const isInsideFetchedWindow = fetchedCompleteHistory || (sourceTimestamp !== undefined &&
1046
+ oldestFetchedTimestamp !== undefined &&
1047
+ // Discord timestamps are millisecond-precise, so an older message just
1048
+ // beyond the page can tie the oldest fetched timestamp. Preserve ties
1049
+ // rather than guessing that an unseen boundary message was deleted.
1050
+ sourceTimestamp > oldestFetchedTimestamp);
1051
+ if (isInsideFetchedWindow && !discordIds.has(externalId) && !live.has(externalId)) {
1052
+ // Message exists in our store but not in Discord - it was deleted
1053
+ // Note: This only catches deletes within the scrollback window.
1054
+ // A message the live path wrote while the sync ran is newer than
1055
+ // the fetch, so it stays. Every row of the message goes, and one
1056
+ // shard's id takes out a body whole.
1057
+ for (const id of oncePerBody(rows))
1058
+ ctx.removeMessage(id);
1059
+ deletedMessages++;
689
1060
  }
690
1061
  }
691
- }
692
- // Check for deletes only inside the history we actually fetched. A short
693
- // response covers complete available history; a full page has an older
694
- // boundary whose tied millisecond must be preserved.
695
- const fetchedCompleteHistory = discordMessages.length < scrollback;
696
- const oldestFetchedTimestamp = discordMessages.length > 0
697
- ? Math.min(...discordMessages.map((msg) => msg.timestamp.getTime()))
698
- : undefined;
699
- for (const [externalId, stored] of storedMessageMap) {
700
- const isInsideFetchedWindow = fetchedCompleteHistory || (stored.sourceTimestamp !== undefined &&
701
- oldestFetchedTimestamp !== undefined &&
702
- // Discord timestamps are millisecond-precise, so an older message just
703
- // beyond the page can tie the oldest fetched timestamp. Preserve ties
704
- // rather than guessing that an unseen boundary message was deleted.
705
- stored.sourceTimestamp > oldestFetchedTimestamp);
706
- if (isInsideFetchedWindow && !discordMessageMap.has(externalId)) {
707
- // Message exists in our store but not in Discord - it was deleted
708
- // Note: This only catches deletes within the scrollback window
709
- this.ctx.removeMessage(stored.internalId);
710
- deletedMessages++;
1062
+ // Update last read
1063
+ if (discordMessages.length > 0) {
1064
+ // Messages are typically newest first
1065
+ const newestId = discordMessages[0].id;
1066
+ this.state.lastReadMessageId[channelId] = newestId;
711
1067
  }
1068
+ return { newMessages, editedMessages, deletedMessages };
1069
+ }
1070
+ finally {
1071
+ this.liveDuringSync.delete(live);
712
1072
  }
713
- // Update last read
714
- if (discordMessages.length > 0) {
715
- // Messages are typically newest first
716
- const newestId = discordMessages[0].id;
717
- this.state.lastReadMessageId[channelId] = newestId;
1073
+ }
1074
+ /** A channel's stored Discord rows by their Discord message id, whole: an
1075
+ * edit replaces a row's own text and keeps what the host added beside it.
1076
+ * Each id's rows are in store order. A message stored twice holds two,
1077
+ * of which a sync acts on the first, as the live path does (storedRow),
1078
+ * and a delete takes both. A write the framework holds is a row too
1079
+ * (storedRows). */
1080
+ storedByDiscordId(ctx, channelId) {
1081
+ const rows = new Map();
1082
+ for (const msg of ctx.queryMessages({ source: 'discord', metadata: { channelId } }).messages) {
1083
+ const external = msg.metadata?.external;
1084
+ if (!external?.id)
1085
+ continue;
1086
+ const held = rows.get(external.id);
1087
+ if (held)
1088
+ held.push(msg);
1089
+ else
1090
+ rows.set(external.id, [msg]);
718
1091
  }
719
- return { newMessages, editedMessages, deletedMessages };
1092
+ return rows;
1093
+ }
1094
+ /** The stored rows of a Discord message, by its id in the row's metadata,
1095
+ * in store order. A write the framework holds comes after them as a held
1096
+ * row, which an edit or removal reaches as it does a stored one. The
1097
+ * module context's findMessageByExternalId holds only rows the module
1098
+ * stored through its context, not those a process response added, so it
1099
+ * misses what the live path stored. */
1100
+ storedRows(discordMessageId) {
1101
+ return this.ctx?.queryMessages({ source: 'discord', externalIds: [discordMessageId] }).messages ?? [];
1102
+ }
1103
+ /** The row an edit or a create acts on: a message's first row, a stored
1104
+ * one before a held one (storedRows). */
1105
+ storedRow(discordMessageId) {
1106
+ return this.storedRows(discordMessageId)[0];
1107
+ }
1108
+ /** Marks a Discord message as the live path's for every history sync running now. */
1109
+ noteLive(discordMessageId) {
1110
+ for (const live of this.liveDuringSync)
1111
+ live.add(discordMessageId);
720
1112
  }
721
1113
  async handleDiscordMessage(message) {
722
- if (!this.ctx)
1114
+ const ctx = this.ctx;
1115
+ if (!ctx)
723
1116
  return;
724
1117
  // ALWAYS ignore our own messages to prevent feedback loops
725
1118
  if (this.state.botUserId && message.authorId === this.state.botUserId) {
@@ -746,35 +1139,65 @@ export class DiscordModule {
746
1139
  }
747
1140
  // Determine if this message should trigger inference
748
1141
  const shouldTrigger = this.shouldTriggerInference(message, isDM);
1142
+ // From here the message is the live path's to store (creating). A second
1143
+ // delivery of a message whose create is already on its way is ignored.
1144
+ if (this.creating.has(message.id))
1145
+ return;
1146
+ const live = { phase: 'converting', deleted: false };
1147
+ this.creating.set(message.id, live);
1148
+ const drop = () => { if (this.creating.get(message.id) === live)
1149
+ this.creating.delete(message.id); };
749
1150
  // Convert message content including attachments
750
- const contentBlocks = await this.convertMessageToContent(message);
1151
+ let contentBlocks;
1152
+ try {
1153
+ contentBlocks = await this.convertMessageToContent(message);
1154
+ }
1155
+ catch (error) {
1156
+ drop();
1157
+ throw error;
1158
+ }
1159
+ // A module stopped or started again meanwhile writes nothing.
1160
+ if (this.ctx !== ctx) {
1161
+ drop();
1162
+ return;
1163
+ }
1164
+ // A delete that came meanwhile drops the create: nothing is stored,
1165
+ // nobody is woken. Its entry stays until that delete is handled, so no
1166
+ // sync stores the message before then.
1167
+ if (live.deleted)
1168
+ return;
1169
+ // An edit that came meanwhile: its text, and the blocks of the
1170
+ // attachments it left.
1171
+ if (live.text !== undefined)
1172
+ contentBlocks = withOwnText(contentBlocks, live.text);
1173
+ if (live.attachments)
1174
+ contentBlocks = withoutGoneAttachments(contentBlocks, live.attachments);
1175
+ live.phase = 'queued';
751
1176
  // Only queue the event - all state writes happen in onProcess()
752
- this.ctx.pushEvent({
1177
+ ctx.pushEvent({
753
1178
  type: 'discord:message',
754
1179
  discordMessageId: message.id,
755
1180
  channelId: message.channelId,
756
1181
  guildId: message.guildId,
757
1182
  authorId: message.authorId,
758
1183
  authorName: message.authorName,
759
- content: message.content,
1184
+ content: live.text ?? message.content,
760
1185
  contentBlocks,
761
1186
  timestamp: message.timestamp.getTime(),
762
- attachments: message.attachments,
1187
+ attachments: live.attachments ?? message.attachments,
763
1188
  shouldTriggerInference: shouldTrigger,
764
1189
  });
765
1190
  }
766
1191
  /**
767
- * Convert Discord message to ContentBlock array.
768
- * Includes text content and any supported attachments (images, text files).
1192
+ * Convert a Discord message, live or from history, to ContentBlock array:
1193
+ * its own text first, '' when it has none, then any supported attachments
1194
+ * (images, text files) and notes for the rest, as TEXT_FIRST marks.
769
1195
  */
770
1196
  async convertMessageToContent(message) {
1197
+ // What the host adds after the message's own text: one block per attachment.
771
1198
  const blocks = [];
772
- // Add text content if present
773
- if (message.content.trim()) {
774
- blocks.push({ type: 'text', text: message.content });
775
- }
776
1199
  // Process attachments
777
- for (const attachment of message.attachments) {
1200
+ for (const attachment of message.attachments ?? []) {
778
1201
  const contentType = attachment.contentType?.toLowerCase() ?? '';
779
1202
  const filename = attachment.filename.toLowerCase();
780
1203
  // Check for supported image types
@@ -790,7 +1213,15 @@ export class DiscordModule {
790
1213
  }
791
1214
  // Check for text files - fetch and include content
792
1215
  const isTextFile = TEXT_FILE_EXTENSIONS.some(ext => filename.endsWith(ext));
793
- if (isTextFile && attachment.size && attachment.size < 100_000) { // < 100KB
1216
+ if (isTextFile && attachment.size >= TEXT_FILE_LIMIT_BYTES) {
1217
+ blocks.push({
1218
+ type: 'text',
1219
+ text: `[Attachment: ${attachment.filename} (${attachment.size.toLocaleString('en-US')} bytes): this host reads text files under ${TEXT_FILE_LIMIT_BYTES.toLocaleString('en-US')} bytes into your context, so its text isn't here. If you need it, ask the sender to paste the part that matters.]`,
1220
+ });
1221
+ continue;
1222
+ }
1223
+ // A text file of size 0 gets the note below, as before.
1224
+ if (isTextFile && attachment.size) {
794
1225
  try {
795
1226
  const response = await fetch(attachment.url);
796
1227
  if (response.ok) {
@@ -800,27 +1231,26 @@ export class DiscordModule {
800
1231
  text: `\n--- File: ${attachment.filename} ---\n${textContent}\n--- End of ${attachment.filename} ---\n`,
801
1232
  });
802
1233
  }
1234
+ else {
1235
+ // An answer whose body is never read holds its connection.
1236
+ await response.body?.cancel().catch(() => { });
1237
+ console.warn(`Discord: Attachment ${logValue(attachment.filename)} in message ${message.id} wasn't fetched: HTTP ${response.status}`);
1238
+ blocks.push(attachmentNotFetched(attachment.filename));
1239
+ }
803
1240
  }
804
1241
  catch (error) {
805
- // If fetch fails, just note the attachment
806
- blocks.push({
807
- type: 'text',
808
- text: `[Attachment: ${attachment.filename} (failed to fetch)]`,
809
- });
1242
+ console.warn(`Discord: Attachment ${logValue(attachment.filename)} in message ${message.id} wasn't fetched:`, error);
1243
+ blocks.push(attachmentNotFetched(attachment.filename));
810
1244
  }
811
1245
  continue;
812
1246
  }
813
1247
  // For unsupported attachments, add a note
814
1248
  blocks.push({
815
1249
  type: 'text',
816
- text: `[Attachment: ${attachment.filename} (${contentType || 'unknown type'})]`,
1250
+ text: `[Attachment: ${attachment.filename} (${contentType || 'unknown type'}): this host didn't read its contents in, so they aren't in your context. If you need them, ask the sender for them in a form you can read, such as pasted text.]`,
817
1251
  });
818
1252
  }
819
- // Ensure we have at least empty text if nothing else
820
- if (blocks.length === 0) {
821
- blocks.push({ type: 'text', text: '[Empty message]' });
822
- }
823
- return blocks;
1253
+ return textFirst(ownText(message.content), blocks);
824
1254
  }
825
1255
  /**
826
1256
  * Determine if a message should trigger agent inference based on config.
@@ -854,19 +1284,35 @@ export class DiscordModule {
854
1284
  return isMentioned || isReplyToBot;
855
1285
  }
856
1286
  }
857
- handleDiscordMessageEdit(messageId, newContent) {
1287
+ handleDiscordMessageEdit(messageId, newContent, attachments) {
858
1288
  if (!this.ctx)
859
1289
  return;
1290
+ // An edit while the message's create converts is folded into that
1291
+ // create, which would otherwise store the text it was sent with; one that
1292
+ // comes after follows the create through the queue. Its event still goes,
1293
+ // for a row a history sync stored first.
1294
+ const live = this.creating.get(messageId);
1295
+ if (live) {
1296
+ live.text = newContent;
1297
+ if (attachments)
1298
+ live.attachments = attachments;
1299
+ }
860
1300
  // Only queue the event - state writes happen in onProcess()
861
1301
  this.ctx.pushEvent({
862
1302
  type: 'discord:edit',
863
1303
  discordMessageId: messageId,
864
1304
  newContent,
1305
+ ...(attachments ? { attachments } : {}),
865
1306
  });
866
1307
  }
867
1308
  handleDiscordMessageDelete(messageId) {
868
1309
  if (!this.ctx)
869
1310
  return;
1311
+ // A delete before the message's create is handled drops that create.
1312
+ // Its event still goes, for a row a history sync stored first.
1313
+ const live = this.creating.get(messageId);
1314
+ if (live)
1315
+ live.deleted = true;
870
1316
  // Only queue the event - state writes happen in onProcess()
871
1317
  this.ctx.pushEvent({
872
1318
  type: 'discord:delete',
@@ -876,40 +1322,100 @@ export class DiscordModule {
876
1322
  // ==========================================================================
877
1323
  // Tool Handlers
878
1324
  // ==========================================================================
879
- async handleSend(input) {
880
- if (!this.checkRateLimit(input.channelId)) {
881
- return { success: false, error: 'Rate limited', isError: true };
1325
+ /** Record a message the agent posted in a turn, for that turn's thoughts (lastPosts). */
1326
+ notePost(agentName, turnId, channelId, messageId) {
1327
+ if (agentName === undefined || turnId === undefined)
1328
+ return;
1329
+ // A later turn's post stays: this one's turn has ended, and its thoughts
1330
+ // have gone or will go nowhere.
1331
+ const current = this.lastPosts.get(agentName);
1332
+ if (current && current.turnId > turnId)
1333
+ return;
1334
+ // Set it last, so the entry recorded longest ago is the first to go.
1335
+ this.lastPosts.delete(agentName);
1336
+ this.lastPosts.set(agentName, { turnId, channelId, messageId });
1337
+ if (this.lastPosts.size > MAX_LAST_POSTS) {
1338
+ const oldest = this.lastPosts.keys().next().value;
1339
+ if (oldest !== undefined)
1340
+ this.lastPosts.delete(oldest);
1341
+ }
1342
+ }
1343
+ async handleSend(input, call) {
1344
+ const wait = this.rateLimitWait(input.channelId);
1345
+ if (wait > 0) {
1346
+ return {
1347
+ success: false,
1348
+ error: `Nothing was sent: channel ${input.channelId} has reached ${this.rateLimitRule()}. Send it again in ${seconds(wait)}; other channels have their own limit.`,
1349
+ isError: true,
1350
+ };
882
1351
  }
883
1352
  const result = await this.client.sendMessage(input.channelId, input.content, {
884
1353
  replyTo: input.replyTo,
885
1354
  createThread: input.createThread,
886
1355
  });
887
- // Track for thought-editing
888
- this.lastSentMessage = { channelId: input.channelId, messageId: result.messageId };
889
- return { success: true, data: { messageId: result.messageId } };
1356
+ // Track for thought-editing: the message is out, whether or not its
1357
+ // thread was started.
1358
+ this.notePost(call.callerAgentName, call.callerTurnId, input.channelId, result.messageId);
1359
+ if (result.threadFailure !== undefined) {
1360
+ return { success: false, error: result.threadFailure, isError: true };
1361
+ }
1362
+ return { success: true, data: { messageId: result.messageId, ...(result.note !== undefined ? { note: result.note } : {}) } };
890
1363
  }
891
1364
  async handleDM(input) {
892
1365
  const result = await this.client.sendDM(input.userId, input.content);
893
- return { success: true, data: { messageId: result.messageId } };
1366
+ // The direct message channel's id is what fetch_history reads it by, should a later dm need checking.
1367
+ return { success: true, data: { messageId: result.messageId, ...(result.channelId !== undefined ? { channelId: result.channelId } : {}) } };
894
1368
  }
895
- async handleReply(input) {
1369
+ async handleReply(input, call) {
896
1370
  const replyCtx = this.replyContexts.get('default');
897
1371
  if (!replyCtx) {
898
- return { success: false, error: 'No conversation context', isError: true };
1372
+ return {
1373
+ success: false,
1374
+ error: 'Nothing was sent: there\'s no conversation to reply in. Use discord--send with a channelId to post somewhere, or discord--set_reply_context with a channelId to choose where replies go, then reply again.',
1375
+ isError: true,
1376
+ };
899
1377
  }
900
- if (!this.checkRateLimit(replyCtx.channelId)) {
901
- return { success: false, error: 'Rate limited', isError: true };
1378
+ // The reply goes to the context's thread when it names one, an empty id
1379
+ // as well, as it always has; the words name an empty id as what it is.
1380
+ const thread = replyCtx.threadId;
1381
+ const wait = this.rateLimitWait(replyCtx.channelId);
1382
+ if (wait > 0) {
1383
+ return {
1384
+ success: false,
1385
+ error: `Nothing was sent: this conversation's channel (${replyCtx.channelId}) has reached ${this.rateLimitRule()}`
1386
+ + `${thread ? `, which counts its replies in thread ${thread} too` : ''}. Reply again in ${seconds(wait)}.`,
1387
+ isError: true,
1388
+ };
902
1389
  }
903
1390
  let content = input.content;
904
1391
  if (input.mention && replyCtx.userId) {
905
1392
  content = `<@${replyCtx.userId}> ${content}`;
906
1393
  }
907
- const targetChannel = replyCtx.threadId ?? replyCtx.channelId;
908
- const result = await this.client.sendMessage(targetChannel, content, {
909
- replyTo: replyCtx.replyToMessageId,
910
- });
1394
+ // reply's call names no channel or message: its reply context does, as it
1395
+ // stands now, before the send, which an incoming message can change.
1396
+ const targetChannel = thread ?? replyCtx.channelId;
1397
+ const pointsAt = thread === undefined ? `channel ${targetChannel}` : thread === '' ? 'an empty thread id' : `thread ${targetChannel}`;
1398
+ const check = thread === ''
1399
+ ? `, and its reply context names an empty thread id: discord--set_reply_context with a threadId, or without one to reply in channel ${replyCtx.channelId}, puts that right`
1400
+ : `: discord--fetch_history with channelId ${targetChannel}, its reply context's ${thread !== undefined ? 'thread' : 'channel'}, shows it, so check before you reply again`;
1401
+ const replyTo = replyCtx.replyToMessageId;
1402
+ let result;
1403
+ try {
1404
+ result = await this.client.sendMessage(targetChannel, content, { replyTo });
1405
+ }
1406
+ catch (error) {
1407
+ if (error instanceof DiscordRefusal) {
1408
+ throw new DiscordRefusal(`${error.message} A reply goes where its reply context points: ${pointsAt}`
1409
+ + `${replyTo ? `, answering message ${replyTo}` : ''}. discord--set_reply_context changes that.`, { cause: error });
1410
+ }
1411
+ return {
1412
+ success: false,
1413
+ error: `discord--reply stopped on an unexpected error: ${errorText(error)}. Whether the reply was sent is unknown${check}.`,
1414
+ isError: true,
1415
+ };
1416
+ }
911
1417
  // Track for thought-editing
912
- this.lastSentMessage = { channelId: targetChannel, messageId: result.messageId };
1418
+ this.notePost(call.callerAgentName, call.callerTurnId, targetChannel, result.messageId);
913
1419
  // Clear reply-to after sending
914
1420
  replyCtx.replyToMessageId = undefined;
915
1421
  replyCtx.lastActivity = Date.now();
@@ -957,14 +1463,14 @@ export class DiscordModule {
957
1463
  }
958
1464
  async handleActivate(input) {
959
1465
  if (!this.ctx) {
960
- return { success: false, error: 'Module not started', isError: true };
1466
+ return { success: false, error: NOT_RUNNING, isError: true };
961
1467
  }
962
1468
  this.ctx.registerSpeechHandler('*', { additive: input.additive });
963
1469
  return { success: true, data: { message: 'Discord is now handling speech' } };
964
1470
  }
965
1471
  async handleDeactivate() {
966
1472
  if (!this.ctx) {
967
- return { success: false, error: 'Module not started', isError: true };
1473
+ return { success: false, error: NOT_RUNNING, isError: true };
968
1474
  }
969
1475
  this.ctx.unregisterSpeechHandler();
970
1476
  return { success: true, data: { message: 'Discord is no longer handling speech' } };
@@ -1020,6 +1526,11 @@ export class DiscordModule {
1020
1526
  authorId: m.authorId,
1021
1527
  isBot: m.isBot,
1022
1528
  content: m.content,
1529
+ // Not their URLs: a link Discord signs works for anyone holding it
1530
+ // until it expires, and an agent could repeat it where others read.
1531
+ ...(m.attachments?.length
1532
+ ? { attachments: m.attachments.map(({ filename, contentType, size }) => ({ filename, contentType, size })) }
1533
+ : {}),
1023
1534
  timestamp: formatZonedDateTime(m.timestamp, this.timeZone),
1024
1535
  replyTo: m.replyTo,
1025
1536
  })),
@@ -1035,8 +1546,23 @@ export class DiscordModule {
1035
1546
  .map((block) => block.text)
1036
1547
  .join('\n');
1037
1548
  }
1038
- checkRateLimit(channelId) {
1039
- const limit = this.config.rateLimitPerMinute ?? 30;
1549
+ rateLimitPerMinute() {
1550
+ return this.config.rateLimitPerMinute ?? 30;
1551
+ }
1552
+ /** The limit, as the rate-limit refusals word it. */
1553
+ rateLimitRule() {
1554
+ const limit = this.rateLimitPerMinute();
1555
+ return `this host's limit of ${limit} send${limit === 1 ? '' : 's'} a minute from the bot`;
1556
+ }
1557
+ /**
1558
+ * Counts a send to `channelId` against its limit and returns 0, or, when
1559
+ * the channel has had its sends for this window, returns the whole seconds
1560
+ * until the window ends, at least 1. A window is the minute from the first
1561
+ * send this lets through, and each send it lets through counts, whether or
1562
+ * not Discord then takes it.
1563
+ */
1564
+ rateLimitWait(channelId) {
1565
+ const limit = this.rateLimitPerMinute();
1040
1566
  const now = Date.now();
1041
1567
  const windowMs = 60 * 1000;
1042
1568
  let info = this.state.rateLimits[channelId];
@@ -1046,10 +1572,10 @@ export class DiscordModule {
1046
1572
  this.state.rateLimits[channelId] = info;
1047
1573
  }
1048
1574
  if (info.count >= limit) {
1049
- return false;
1575
+ return Math.max(1, Math.ceil((info.windowStart + windowMs - now) / 1000));
1050
1576
  }
1051
1577
  info.count++;
1052
- return true;
1578
+ return 0;
1053
1579
  }
1054
1580
  cleanupOldConversations() {
1055
1581
  const now = Date.now();