@theowlops/boombot 0.0.0-stage → 1.1.1

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 (544) hide show
  1. package/README.md +69 -3
  2. package/dist/LICENSE +21 -0
  3. package/dist/bin/boombot.js +16 -0
  4. package/dist/cli/api-key.js +123 -0
  5. package/dist/cli/bin-bun.js +17 -0
  6. package/dist/cli/bin.js +34 -0
  7. package/dist/cli/cli/cli-error.js +61 -0
  8. package/dist/cli/cli/dispatch.js +138 -0
  9. package/dist/cli/cli/help.js +83 -0
  10. package/dist/cli/cli/parser.js +89 -0
  11. package/dist/cli/cli/registry.js +185 -0
  12. package/dist/cli/cli/spec.js +41 -0
  13. package/dist/cli/cli/suggest.js +125 -0
  14. package/dist/cli/cli/version.js +30 -0
  15. package/dist/cli/cli/vocabulary.js +87 -0
  16. package/dist/cli/commands/doctor.js +365 -0
  17. package/dist/cli/commands/gateway.js +290 -0
  18. package/dist/cli/commands/migrate.js +270 -0
  19. package/dist/cli/commands/plugin.js +97 -0
  20. package/dist/cli/commands/setup.js +244 -0
  21. package/dist/cli/config-file.js +133 -0
  22. package/dist/cli/master-key.js +78 -0
  23. package/dist/cli/open-browser.js +59 -0
  24. package/dist/cli/web-assets.js +58 -0
  25. package/dist/gateway/auth.js +46 -0
  26. package/dist/gateway/config-format.js +202 -0
  27. package/dist/gateway/config.js +427 -0
  28. package/dist/gateway/context.js +1 -0
  29. package/dist/gateway/env-file.js +129 -0
  30. package/dist/gateway/events.js +172 -0
  31. package/dist/gateway/guard/tagging.js +505 -0
  32. package/dist/gateway/handlers/index.js +66 -0
  33. package/dist/gateway/handlers/memory.js +54 -0
  34. package/dist/gateway/handlers/providers.js +73 -0
  35. package/dist/gateway/handlers/registry.js +39 -0
  36. package/dist/gateway/handlers/sessions.js +188 -0
  37. package/dist/gateway/handlers/system.js +12 -0
  38. package/dist/gateway/handlers/tasks.js +191 -0
  39. package/dist/gateway/handlers/turn.js +100 -0
  40. package/dist/gateway/index.js +126 -0
  41. package/dist/gateway/main.js +23 -0
  42. package/dist/gateway/server.js +354 -0
  43. package/dist/gateway/services/context-limits.js +75 -0
  44. package/dist/gateway/services/history-normalizer.js +42 -0
  45. package/dist/gateway/services/image-pipeline.js +56 -0
  46. package/dist/gateway/services/key-roulette-client.js +43 -0
  47. package/dist/gateway/services/llm-client.js +593 -0
  48. package/dist/gateway/services/llm-decoder.js +200 -0
  49. package/dist/gateway/services/local-fallback.js +135 -0
  50. package/dist/gateway/services/memory.js +511 -0
  51. package/dist/gateway/services/persistent-memory.js +196 -0
  52. package/dist/gateway/services/providers.js +44 -0
  53. package/dist/gateway/services/tasks.js +237 -0
  54. package/dist/gateway/services/verification.js +124 -0
  55. package/dist/gateway/services/vision-payload.js +48 -0
  56. package/dist/gateway/services.js +257 -0
  57. package/dist/gateway/session-snapshot.js +19 -0
  58. package/dist/gateway/sessions.js +701 -0
  59. package/dist/gateway/static.js +239 -0
  60. package/dist/gateway/turns/clarify-tool.js +57 -0
  61. package/dist/gateway/turns/cronjob-tool.js +96 -0
  62. package/dist/gateway/turns/deferred-tools.js +154 -0
  63. package/dist/gateway/turns/execute-code-tool.js +84 -0
  64. package/dist/gateway/turns/file-tool-defs.js +178 -0
  65. package/dist/gateway/turns/file-tool-ops.js +178 -0
  66. package/dist/gateway/turns/file-tool-scan.js +182 -0
  67. package/dist/gateway/turns/file-tool.js +302 -0
  68. package/dist/gateway/turns/index.js +416 -0
  69. package/dist/gateway/turns/jev-middleware.js +91 -0
  70. package/dist/gateway/turns/local-reply.js +156 -0
  71. package/dist/gateway/turns/mcp-client.js +643 -0
  72. package/dist/gateway/turns/memory-tool.js +171 -0
  73. package/dist/gateway/turns/middleware.js +96 -0
  74. package/dist/gateway/turns/python-core.js +397 -0
  75. package/dist/gateway/turns/routing/index.js +3 -0
  76. package/dist/gateway/turns/routing/intent-classifier.js +60 -0
  77. package/dist/gateway/turns/routing/planner-adapter.js +91 -0
  78. package/dist/gateway/turns/routing/types.js +1 -0
  79. package/dist/gateway/turns/runtime-deps.js +228 -0
  80. package/dist/gateway/turns/self-config-tool.js +277 -0
  81. package/dist/gateway/turns/service-types.js +11 -0
  82. package/dist/gateway/turns/service.js +820 -0
  83. package/dist/gateway/turns/session-adapter.js +158 -0
  84. package/dist/gateway/turns/session-tool.js +49 -0
  85. package/dist/gateway/turns/shell-tool.js +190 -0
  86. package/dist/gateway/turns/skill-tool.js +239 -0
  87. package/dist/gateway/turns/spillover.js +201 -0
  88. package/dist/gateway/turns/subagent-tool.js +140 -0
  89. package/dist/gateway/turns/system-prompt.js +121 -0
  90. package/dist/gateway/turns/tool-schema.js +90 -0
  91. package/dist/gateway/turns/turn-utils.js +58 -0
  92. package/dist/gateway/turns/vision-tool.js +120 -0
  93. package/dist/gateway/types.js +1 -0
  94. package/dist/native/win32-x64/boombot-native-host.exe +0 -0
  95. package/dist/node_modules/@boombot/channels/handlers.d.ts +9 -0
  96. package/dist/node_modules/@boombot/channels/handlers.js +109 -0
  97. package/dist/node_modules/@boombot/channels/index.d.ts +12 -0
  98. package/dist/node_modules/@boombot/channels/index.js +12 -0
  99. package/dist/node_modules/@boombot/channels/package.json +9 -0
  100. package/dist/node_modules/@boombot/channels/service.d.ts +62 -0
  101. package/dist/node_modules/@boombot/channels/service.js +608 -0
  102. package/dist/node_modules/@boombot/channels/tool.d.ts +10 -0
  103. package/dist/node_modules/@boombot/channels/tool.js +46 -0
  104. package/dist/node_modules/@boombot/channels/transports/common/dedup.d.ts +17 -0
  105. package/dist/node_modules/@boombot/channels/transports/common/dedup.js +42 -0
  106. package/dist/node_modules/@boombot/channels/transports/common/handoff.d.ts +19 -0
  107. package/dist/node_modules/@boombot/channels/transports/common/handoff.js +32 -0
  108. package/dist/node_modules/@boombot/channels/transports/common/index.d.ts +3 -0
  109. package/dist/node_modules/@boombot/channels/transports/common/index.js +3 -0
  110. package/dist/node_modules/@boombot/channels/transports/common/types.d.ts +99 -0
  111. package/dist/node_modules/@boombot/channels/transports/common/types.js +54 -0
  112. package/dist/node_modules/@boombot/channels/transports/common.d.ts +2 -0
  113. package/dist/node_modules/@boombot/channels/transports/common.js +2 -0
  114. package/dist/node_modules/@boombot/channels/transports/dedup/index.d.ts +1 -0
  115. package/dist/node_modules/@boombot/channels/transports/dedup/index.js +1 -0
  116. package/dist/node_modules/@boombot/channels/transports/dedup.d.ts +2 -0
  117. package/dist/node_modules/@boombot/channels/transports/dedup.js +2 -0
  118. package/dist/node_modules/@boombot/channels/transports/discord/index.d.ts +1 -0
  119. package/dist/node_modules/@boombot/channels/transports/discord/index.js +1 -0
  120. package/dist/node_modules/@boombot/channels/transports/discord/transport.d.ts +36 -0
  121. package/dist/node_modules/@boombot/channels/transports/discord/transport.js +199 -0
  122. package/dist/node_modules/@boombot/channels/transports/discord.d.ts +2 -0
  123. package/dist/node_modules/@boombot/channels/transports/discord.js +2 -0
  124. package/dist/node_modules/@boombot/channels/transports/handoff.d.ts +2 -0
  125. package/dist/node_modules/@boombot/channels/transports/handoff.js +2 -0
  126. package/dist/node_modules/@boombot/channels/transports/messenger/index.d.ts +1 -0
  127. package/dist/node_modules/@boombot/channels/transports/messenger/index.js +1 -0
  128. package/dist/node_modules/@boombot/channels/transports/messenger/transport.d.ts +34 -0
  129. package/dist/node_modules/@boombot/channels/transports/messenger/transport.js +180 -0
  130. package/dist/node_modules/@boombot/channels/transports/messenger.d.ts +2 -0
  131. package/dist/node_modules/@boombot/channels/transports/messenger.js +2 -0
  132. package/dist/node_modules/@boombot/channels/transports/slack/index.d.ts +1 -0
  133. package/dist/node_modules/@boombot/channels/transports/slack/index.js +1 -0
  134. package/dist/node_modules/@boombot/channels/transports/slack/transport.d.ts +35 -0
  135. package/dist/node_modules/@boombot/channels/transports/slack/transport.js +183 -0
  136. package/dist/node_modules/@boombot/channels/transports/slack.d.ts +2 -0
  137. package/dist/node_modules/@boombot/channels/transports/slack.js +2 -0
  138. package/dist/node_modules/@boombot/channels/transports/streamer/index.d.ts +1 -0
  139. package/dist/node_modules/@boombot/channels/transports/streamer/index.js +1 -0
  140. package/dist/node_modules/@boombot/channels/transports/streamer/streamer.d.ts +31 -0
  141. package/dist/node_modules/@boombot/channels/transports/streamer/streamer.js +141 -0
  142. package/dist/node_modules/@boombot/channels/transports/streamer.d.ts +2 -0
  143. package/dist/node_modules/@boombot/channels/transports/streamer.js +2 -0
  144. package/dist/node_modules/@boombot/channels/transports/telegram/index.d.ts +1 -0
  145. package/dist/node_modules/@boombot/channels/transports/telegram/index.js +1 -0
  146. package/dist/node_modules/@boombot/channels/transports/telegram/transport.d.ts +75 -0
  147. package/dist/node_modules/@boombot/channels/transports/telegram/transport.js +395 -0
  148. package/dist/node_modules/@boombot/channels/transports/telegram.d.ts +2 -0
  149. package/dist/node_modules/@boombot/channels/transports/telegram.js +2 -0
  150. package/dist/node_modules/@boombot/channels/transports/tiktok/index.d.ts +1 -0
  151. package/dist/node_modules/@boombot/channels/transports/tiktok/index.js +1 -0
  152. package/dist/node_modules/@boombot/channels/transports/tiktok/transport.d.ts +27 -0
  153. package/dist/node_modules/@boombot/channels/transports/tiktok/transport.js +97 -0
  154. package/dist/node_modules/@boombot/channels/transports/tiktok.d.ts +2 -0
  155. package/dist/node_modules/@boombot/channels/transports/tiktok.js +2 -0
  156. package/dist/node_modules/@boombot/channels/transports/whatsapp/index.d.ts +1 -0
  157. package/dist/node_modules/@boombot/channels/transports/whatsapp/index.js +1 -0
  158. package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.d.ts +38 -0
  159. package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.js +189 -0
  160. package/dist/node_modules/@boombot/channels/transports/whatsapp.d.ts +2 -0
  161. package/dist/node_modules/@boombot/channels/transports/whatsapp.js +2 -0
  162. package/dist/node_modules/@boombot/channels/transports/zalo/index.d.ts +2 -0
  163. package/dist/node_modules/@boombot/channels/transports/zalo/index.js +2 -0
  164. package/dist/node_modules/@boombot/channels/transports/zalo/oa.d.ts +28 -0
  165. package/dist/node_modules/@boombot/channels/transports/zalo/oa.js +94 -0
  166. package/dist/node_modules/@boombot/channels/transports/zalo/personal.d.ts +106 -0
  167. package/dist/node_modules/@boombot/channels/transports/zalo/personal.js +171 -0
  168. package/dist/node_modules/@boombot/channels/transports/zalo-personal.d.ts +2 -0
  169. package/dist/node_modules/@boombot/channels/transports/zalo-personal.js +2 -0
  170. package/dist/node_modules/@boombot/channels/transports/zalo.d.ts +2 -0
  171. package/dist/node_modules/@boombot/channels/transports/zalo.js +2 -0
  172. package/dist/node_modules/@boombot/core/agent-core/index.d.ts +12 -0
  173. package/dist/node_modules/@boombot/core/agent-core/index.js +12 -0
  174. package/dist/node_modules/@boombot/core/agent-core/middleware.d.ts +60 -0
  175. package/dist/node_modules/@boombot/core/agent-core/middleware.js +152 -0
  176. package/dist/node_modules/@boombot/core/agent-core/routing.d.ts +128 -0
  177. package/dist/node_modules/@boombot/core/agent-core/routing.js +437 -0
  178. package/dist/node_modules/@boombot/core/agent-core/runtime-deps.d.ts +88 -0
  179. package/dist/node_modules/@boombot/core/agent-core/runtime-deps.js +8 -0
  180. package/dist/node_modules/@boombot/core/agent-core/tool-errors.d.ts +44 -0
  181. package/dist/node_modules/@boombot/core/agent-core/tool-errors.js +60 -0
  182. package/dist/node_modules/@boombot/core/agent-core/turn-loop.d.ts +21 -0
  183. package/dist/node_modules/@boombot/core/agent-core/turn-loop.js +561 -0
  184. package/dist/node_modules/@boombot/core/agent-core/types.d.ts +189 -0
  185. package/dist/node_modules/@boombot/core/agent-core/types.js +7 -0
  186. package/dist/node_modules/@boombot/core/compaction/compactor.d.ts +19 -0
  187. package/dist/node_modules/@boombot/core/compaction/compactor.js +127 -0
  188. package/dist/node_modules/@boombot/core/compaction/estimator.d.ts +10 -0
  189. package/dist/node_modules/@boombot/core/compaction/estimator.js +48 -0
  190. package/dist/node_modules/@boombot/core/compaction/index.d.ts +3 -0
  191. package/dist/node_modules/@boombot/core/compaction/index.js +3 -0
  192. package/dist/node_modules/@boombot/core/compaction/types.d.ts +31 -0
  193. package/dist/node_modules/@boombot/core/compaction/types.js +7 -0
  194. package/dist/node_modules/@boombot/core/index.d.ts +9 -0
  195. package/dist/node_modules/@boombot/core/index.js +9 -0
  196. package/dist/node_modules/@boombot/core/jev/index.d.ts +2 -0
  197. package/dist/node_modules/@boombot/core/jev/index.js +2 -0
  198. package/dist/node_modules/@boombot/core/jev/jev.d.ts +30 -0
  199. package/dist/node_modules/@boombot/core/jev/jev.js +164 -0
  200. package/dist/node_modules/@boombot/core/jev/types.d.ts +22 -0
  201. package/dist/node_modules/@boombot/core/jev/types.js +1 -0
  202. package/dist/node_modules/@boombot/core/memory/consolidation/budget.d.ts +63 -0
  203. package/dist/node_modules/@boombot/core/memory/consolidation/budget.js +94 -0
  204. package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.d.ts +152 -0
  205. package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.js +318 -0
  206. package/dist/node_modules/@boombot/core/memory/consolidation/index.d.ts +12 -0
  207. package/dist/node_modules/@boombot/core/memory/consolidation/index.js +12 -0
  208. package/dist/node_modules/@boombot/core/memory/curation.d.ts +86 -0
  209. package/dist/node_modules/@boombot/core/memory/curation.js +120 -0
  210. package/dist/node_modules/@boombot/core/memory/errors.d.ts +12 -0
  211. package/dist/node_modules/@boombot/core/memory/errors.js +20 -0
  212. package/dist/node_modules/@boombot/core/memory/hybrid-search.d.ts +29 -0
  213. package/dist/node_modules/@boombot/core/memory/hybrid-search.js +52 -0
  214. package/dist/node_modules/@boombot/core/memory/index.d.ts +23 -0
  215. package/dist/node_modules/@boombot/core/memory/index.js +23 -0
  216. package/dist/node_modules/@boombot/core/memory/indexer.d.ts +47 -0
  217. package/dist/node_modules/@boombot/core/memory/indexer.js +138 -0
  218. package/dist/node_modules/@boombot/core/memory/lifecycle.d.ts +12 -0
  219. package/dist/node_modules/@boombot/core/memory/lifecycle.js +66 -0
  220. package/dist/node_modules/@boombot/core/memory/provider.d.ts +84 -0
  221. package/dist/node_modules/@boombot/core/memory/provider.js +117 -0
  222. package/dist/node_modules/@boombot/core/memory/recall.d.ts +7 -0
  223. package/dist/node_modules/@boombot/core/memory/recall.js +111 -0
  224. package/dist/node_modules/@boombot/core/memory/scope-resolver.d.ts +27 -0
  225. package/dist/node_modules/@boombot/core/memory/scope-resolver.js +60 -0
  226. package/dist/node_modules/@boombot/core/memory/search.d.ts +37 -0
  227. package/dist/node_modules/@boombot/core/memory/search.js +158 -0
  228. package/dist/node_modules/@boombot/core/memory/store.d.ts +60 -0
  229. package/dist/node_modules/@boombot/core/memory/store.js +289 -0
  230. package/dist/node_modules/@boombot/core/memory/types.d.ts +121 -0
  231. package/dist/node_modules/@boombot/core/memory/types.js +21 -0
  232. package/dist/node_modules/@boombot/core/memory/vector-index.d.ts +34 -0
  233. package/dist/node_modules/@boombot/core/memory/vector-index.js +65 -0
  234. package/dist/node_modules/@boombot/core/package.json +9 -0
  235. package/dist/node_modules/@boombot/core/reflection/classifier.d.ts +13 -0
  236. package/dist/node_modules/@boombot/core/reflection/classifier.js +77 -0
  237. package/dist/node_modules/@boombot/core/reflection/index.d.ts +2 -0
  238. package/dist/node_modules/@boombot/core/reflection/index.js +2 -0
  239. package/dist/node_modules/@boombot/core/reflection/types.d.ts +13 -0
  240. package/dist/node_modules/@boombot/core/reflection/types.js +4 -0
  241. package/dist/node_modules/@boombot/core/research/config.d.ts +89 -0
  242. package/dist/node_modules/@boombot/core/research/config.js +206 -0
  243. package/dist/node_modules/@boombot/core/research/crawl.d.ts +36 -0
  244. package/dist/node_modules/@boombot/core/research/crawl.js +179 -0
  245. package/dist/node_modules/@boombot/core/research/factory.d.ts +26 -0
  246. package/dist/node_modules/@boombot/core/research/factory.js +46 -0
  247. package/dist/node_modules/@boombot/core/research/http.d.ts +87 -0
  248. package/dist/node_modules/@boombot/core/research/http.js +250 -0
  249. package/dist/node_modules/@boombot/core/research/index.d.ts +17 -0
  250. package/dist/node_modules/@boombot/core/research/index.js +10 -0
  251. package/dist/node_modules/@boombot/core/research/orchestrator.d.ts +113 -0
  252. package/dist/node_modules/@boombot/core/research/orchestrator.js +252 -0
  253. package/dist/node_modules/@boombot/core/research/parse/docx.d.ts +24 -0
  254. package/dist/node_modules/@boombot/core/research/parse/docx.js +183 -0
  255. package/dist/node_modules/@boombot/core/research/parse/guards.d.ts +89 -0
  256. package/dist/node_modules/@boombot/core/research/parse/guards.js +171 -0
  257. package/dist/node_modules/@boombot/core/research/parse/index.d.ts +68 -0
  258. package/dist/node_modules/@boombot/core/research/parse/index.js +219 -0
  259. package/dist/node_modules/@boombot/core/research/parse/pdf.d.ts +38 -0
  260. package/dist/node_modules/@boombot/core/research/parse/pdf.js +567 -0
  261. package/dist/node_modules/@boombot/core/research/providers/arxiv.d.ts +9 -0
  262. package/dist/node_modules/@boombot/core/research/providers/arxiv.js +72 -0
  263. package/dist/node_modules/@boombot/core/research/providers/brave.d.ts +11 -0
  264. package/dist/node_modules/@boombot/core/research/providers/brave.js +36 -0
  265. package/dist/node_modules/@boombot/core/research/providers/duckduckgo.d.ts +9 -0
  266. package/dist/node_modules/@boombot/core/research/providers/duckduckgo.js +78 -0
  267. package/dist/node_modules/@boombot/core/research/providers/exa.d.ts +11 -0
  268. package/dist/node_modules/@boombot/core/research/providers/exa.js +34 -0
  269. package/dist/node_modules/@boombot/core/research/providers/firecrawl.d.ts +16 -0
  270. package/dist/node_modules/@boombot/core/research/providers/firecrawl.js +47 -0
  271. package/dist/node_modules/@boombot/core/research/providers/index.d.ts +17 -0
  272. package/dist/node_modules/@boombot/core/research/providers/index.js +11 -0
  273. package/dist/node_modules/@boombot/core/research/providers/jina.d.ts +13 -0
  274. package/dist/node_modules/@boombot/core/research/providers/jina.js +114 -0
  275. package/dist/node_modules/@boombot/core/research/providers/openalex.d.ts +14 -0
  276. package/dist/node_modules/@boombot/core/research/providers/openalex.js +66 -0
  277. package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.d.ts +9 -0
  278. package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.js +56 -0
  279. package/dist/node_modules/@boombot/core/research/providers/tavily.d.ts +11 -0
  280. package/dist/node_modules/@boombot/core/research/providers/tavily.js +35 -0
  281. package/dist/node_modules/@boombot/core/research/providers/wikipedia.d.ts +12 -0
  282. package/dist/node_modules/@boombot/core/research/providers/wikipedia.js +47 -0
  283. package/dist/node_modules/@boombot/core/research/tools.d.ts +27 -0
  284. package/dist/node_modules/@boombot/core/research/tools.js +251 -0
  285. package/dist/node_modules/@boombot/core/research/types.d.ts +133 -0
  286. package/dist/node_modules/@boombot/core/research/types.js +8 -0
  287. package/dist/node_modules/@boombot/core/research/wiring.d.ts +25 -0
  288. package/dist/node_modules/@boombot/core/research/wiring.js +51 -0
  289. package/dist/node_modules/@boombot/core/scheduler/cron.d.ts +34 -0
  290. package/dist/node_modules/@boombot/core/scheduler/cron.js +147 -0
  291. package/dist/node_modules/@boombot/core/scheduler/evaluator.d.ts +42 -0
  292. package/dist/node_modules/@boombot/core/scheduler/evaluator.js +148 -0
  293. package/dist/node_modules/@boombot/core/scheduler/index.d.ts +15 -0
  294. package/dist/node_modules/@boombot/core/scheduler/index.js +15 -0
  295. package/dist/node_modules/@boombot/core/scheduler/registry.d.ts +81 -0
  296. package/dist/node_modules/@boombot/core/scheduler/registry.js +263 -0
  297. package/dist/node_modules/@boombot/core/scheduler/runner.d.ts +59 -0
  298. package/dist/node_modules/@boombot/core/scheduler/runner.js +257 -0
  299. package/dist/node_modules/@boombot/core/scheduler/store.d.ts +150 -0
  300. package/dist/node_modules/@boombot/core/scheduler/store.js +526 -0
  301. package/dist/node_modules/@boombot/core/scheduler/timezone.d.ts +35 -0
  302. package/dist/node_modules/@boombot/core/scheduler/timezone.js +96 -0
  303. package/dist/node_modules/@boombot/core/scheduler/types.d.ts +157 -0
  304. package/dist/node_modules/@boombot/core/scheduler/types.js +89 -0
  305. package/dist/node_modules/@boombot/core/task-runner/index.d.ts +7 -0
  306. package/dist/node_modules/@boombot/core/task-runner/index.js +5 -0
  307. package/dist/node_modules/@boombot/core/task-runner/planner.d.ts +37 -0
  308. package/dist/node_modules/@boombot/core/task-runner/planner.js +154 -0
  309. package/dist/node_modules/@boombot/core/task-runner/runner.d.ts +8 -0
  310. package/dist/node_modules/@boombot/core/task-runner/runner.js +564 -0
  311. package/dist/node_modules/@boombot/core/task-runner/store.d.ts +28 -0
  312. package/dist/node_modules/@boombot/core/task-runner/store.js +126 -0
  313. package/dist/node_modules/@boombot/core/task-runner/types.d.ts +138 -0
  314. package/dist/node_modules/@boombot/core/task-runner/types.js +11 -0
  315. package/dist/node_modules/@boombot/core/workflow/dag.d.ts +16 -0
  316. package/dist/node_modules/@boombot/core/workflow/dag.js +109 -0
  317. package/dist/node_modules/@boombot/core/workflow/engine.d.ts +34 -0
  318. package/dist/node_modules/@boombot/core/workflow/engine.js +304 -0
  319. package/dist/node_modules/@boombot/core/workflow/index.d.ts +3 -0
  320. package/dist/node_modules/@boombot/core/workflow/index.js +3 -0
  321. package/dist/node_modules/@boombot/core/workflow/types.d.ts +98 -0
  322. package/dist/node_modules/@boombot/core/workflow/types.js +21 -0
  323. package/dist/node_modules/@boombot/cronjobs/handlers.d.ts +11 -0
  324. package/dist/node_modules/@boombot/cronjobs/handlers.js +174 -0
  325. package/dist/node_modules/@boombot/cronjobs/index.d.ts +2 -0
  326. package/dist/node_modules/@boombot/cronjobs/index.js +2 -0
  327. package/dist/node_modules/@boombot/cronjobs/package.json +9 -0
  328. package/dist/node_modules/@boombot/cronjobs/service.d.ts +53 -0
  329. package/dist/node_modules/@boombot/cronjobs/service.js +200 -0
  330. package/dist/node_modules/@boombot/plugins/index.d.ts +2 -0
  331. package/dist/node_modules/@boombot/plugins/index.js +2 -0
  332. package/dist/node_modules/@boombot/plugins/installer.d.ts +45 -0
  333. package/dist/node_modules/@boombot/plugins/installer.js +487 -0
  334. package/dist/node_modules/@boombot/plugins/manager.d.ts +23 -0
  335. package/dist/node_modules/@boombot/plugins/manager.js +243 -0
  336. package/dist/node_modules/@boombot/plugins/package.json +9 -0
  337. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.d.ts +125 -0
  338. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.js +76 -0
  339. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.d.ts +220 -0
  340. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.js +60 -0
  341. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.d.ts +33 -0
  342. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.js +25 -0
  343. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.d.ts +4349 -0
  344. package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.js +563 -0
  345. package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.d.ts +32 -0
  346. package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.js +61 -0
  347. package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.d.ts +13 -0
  348. package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.js +16 -0
  349. package/dist/node_modules/@boombot/protocol/gateway-protocol/events.d.ts +567 -0
  350. package/dist/node_modules/@boombot/protocol/gateway-protocol/events.js +395 -0
  351. package/dist/node_modules/@boombot/protocol/gateway-protocol/index.d.ts +20 -0
  352. package/dist/node_modules/@boombot/protocol/gateway-protocol/index.js +13 -0
  353. package/dist/node_modules/@boombot/protocol/gateway-protocol/types.d.ts +91 -0
  354. package/dist/node_modules/@boombot/protocol/gateway-protocol/types.js +7 -0
  355. package/dist/node_modules/@boombot/protocol/index.d.ts +4 -0
  356. package/dist/node_modules/@boombot/protocol/index.js +3 -0
  357. package/dist/node_modules/@boombot/protocol/package.json +9 -0
  358. package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.d.ts +41 -0
  359. package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.js +17 -0
  360. package/dist/node_modules/@boombot/protocol/plugin-sdk/index.d.ts +10 -0
  361. package/dist/node_modules/@boombot/protocol/plugin-sdk/index.js +10 -0
  362. package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.d.ts +58 -0
  363. package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.js +116 -0
  364. package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.d.ts +25 -0
  365. package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.js +1 -0
  366. package/dist/node_modules/@boombot/protocol/providers/cipher.d.ts +7 -0
  367. package/dist/node_modules/@boombot/protocol/providers/cipher.js +52 -0
  368. package/dist/node_modules/@boombot/protocol/providers/index.d.ts +19 -0
  369. package/dist/node_modules/@boombot/protocol/providers/index.js +19 -0
  370. package/dist/node_modules/@boombot/protocol/providers/key-roulette.d.ts +79 -0
  371. package/dist/node_modules/@boombot/protocol/providers/key-roulette.js +81 -0
  372. package/dist/node_modules/@boombot/protocol/providers/probe.d.ts +29 -0
  373. package/dist/node_modules/@boombot/protocol/providers/probe.js +96 -0
  374. package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.d.ts +11 -0
  375. package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.js +11 -0
  376. package/dist/node_modules/@boombot/protocol/providers/profiles/custom.d.ts +22 -0
  377. package/dist/node_modules/@boombot/protocol/providers/profiles/custom.js +222 -0
  378. package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.d.ts +7 -0
  379. package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.js +9 -0
  380. package/dist/node_modules/@boombot/protocol/providers/profiles/google.d.ts +10 -0
  381. package/dist/node_modules/@boombot/protocol/providers/profiles/google.js +10 -0
  382. package/dist/node_modules/@boombot/protocol/providers/profiles/index.d.ts +31 -0
  383. package/dist/node_modules/@boombot/protocol/providers/profiles/index.js +56 -0
  384. package/dist/node_modules/@boombot/protocol/providers/profiles/local.d.ts +10 -0
  385. package/dist/node_modules/@boombot/protocol/providers/profiles/local.js +11 -0
  386. package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.d.ts +7 -0
  387. package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.js +9 -0
  388. package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.d.ts +10 -0
  389. package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.js +10 -0
  390. package/dist/node_modules/@boombot/protocol/providers/profiles/openai.d.ts +7 -0
  391. package/dist/node_modules/@boombot/protocol/providers/profiles/openai.js +11 -0
  392. package/dist/node_modules/@boombot/protocol/providers/profiles/types.d.ts +58 -0
  393. package/dist/node_modules/@boombot/protocol/providers/profiles/types.js +1 -0
  394. package/dist/node_modules/@boombot/protocol/providers/profiles/xai.d.ts +7 -0
  395. package/dist/node_modules/@boombot/protocol/providers/profiles/xai.js +10 -0
  396. package/dist/node_modules/@boombot/protocol/providers/request-profile.d.ts +102 -0
  397. package/dist/node_modules/@boombot/protocol/providers/request-profile.js +118 -0
  398. package/dist/node_modules/@boombot/protocol/providers/service.d.ts +2 -0
  399. package/dist/node_modules/@boombot/protocol/providers/service.js +418 -0
  400. package/dist/node_modules/@boombot/protocol/providers/types.d.ts +141 -0
  401. package/dist/node_modules/@boombot/protocol/providers/types.js +16 -0
  402. package/dist/node_modules/@boombot/sandbox/fs-path.d.ts +13 -0
  403. package/dist/node_modules/@boombot/sandbox/fs-path.js +79 -0
  404. package/dist/node_modules/@boombot/sandbox/index.d.ts +13 -0
  405. package/dist/node_modules/@boombot/sandbox/index.js +13 -0
  406. package/dist/node_modules/@boombot/sandbox/native.d.ts +39 -0
  407. package/dist/node_modules/@boombot/sandbox/native.js +224 -0
  408. package/dist/node_modules/@boombot/sandbox/package.json +9 -0
  409. package/dist/node_modules/@boombot/sandbox/sandbox.d.ts +92 -0
  410. package/dist/node_modules/@boombot/sandbox/sandbox.js +229 -0
  411. package/dist/node_modules/@boombot/security/harness-guard/boundary.d.ts +22 -0
  412. package/dist/node_modules/@boombot/security/harness-guard/boundary.js +105 -0
  413. package/dist/node_modules/@boombot/security/harness-guard/gates.d.ts +90 -0
  414. package/dist/node_modules/@boombot/security/harness-guard/gates.js +791 -0
  415. package/dist/node_modules/@boombot/security/harness-guard/index.d.ts +23 -0
  416. package/dist/node_modules/@boombot/security/harness-guard/index.js +23 -0
  417. package/dist/node_modules/@boombot/security/harness-guard/policy.d.ts +20 -0
  418. package/dist/node_modules/@boombot/security/harness-guard/policy.js +70 -0
  419. package/dist/node_modules/@boombot/security/harness-guard/sanitizer.d.ts +53 -0
  420. package/dist/node_modules/@boombot/security/harness-guard/sanitizer.js +145 -0
  421. package/dist/node_modules/@boombot/security/harness-guard/types.d.ts +71 -0
  422. package/dist/node_modules/@boombot/security/harness-guard/types.js +10 -0
  423. package/dist/node_modules/@boombot/security/harness-guard/verifier.d.ts +59 -0
  424. package/dist/node_modules/@boombot/security/harness-guard/verifier.js +169 -0
  425. package/dist/node_modules/@boombot/security/index.d.ts +5 -0
  426. package/dist/node_modules/@boombot/security/index.js +9 -0
  427. package/dist/node_modules/@boombot/security/package.json +9 -0
  428. package/dist/node_modules/@boombot/security/policy/approval.d.ts +117 -0
  429. package/dist/node_modules/@boombot/security/policy/approval.js +223 -0
  430. package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.d.ts +10 -0
  431. package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.js +124 -0
  432. package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.d.ts +25 -0
  433. package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.js +70 -0
  434. package/dist/node_modules/@boombot/security/reasoning-guard/gate.d.ts +38 -0
  435. package/dist/node_modules/@boombot/security/reasoning-guard/gate.js +61 -0
  436. package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.d.ts +12 -0
  437. package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.js +234 -0
  438. package/dist/node_modules/@boombot/security/reasoning-guard/index.d.ts +17 -0
  439. package/dist/node_modules/@boombot/security/reasoning-guard/index.js +13 -0
  440. package/dist/node_modules/@boombot/security/reasoning-guard/policy.d.ts +15 -0
  441. package/dist/node_modules/@boombot/security/reasoning-guard/policy.js +39 -0
  442. package/dist/node_modules/@boombot/security/reasoning-guard/types.d.ts +46 -0
  443. package/dist/node_modules/@boombot/security/reasoning-guard/types.js +7 -0
  444. package/dist/node_modules/@boombot/security/security/approval.d.ts +63 -0
  445. package/dist/node_modules/@boombot/security/security/approval.js +140 -0
  446. package/dist/node_modules/@boombot/security/security/audit.d.ts +39 -0
  447. package/dist/node_modules/@boombot/security/security/audit.js +70 -0
  448. package/dist/node_modules/@boombot/security/security/index.d.ts +12 -0
  449. package/dist/node_modules/@boombot/security/security/index.js +12 -0
  450. package/dist/node_modules/@boombot/security/security/trust.d.ts +79 -0
  451. package/dist/node_modules/@boombot/security/security/trust.js +87 -0
  452. package/dist/package.json +5 -0
  453. package/dist/proto/agent_core.proto +56 -0
  454. package/dist/python/agent-core-python/pyproject.toml +40 -0
  455. package/dist/python/agent-core-python/src/boombot_agent/__init__.py +5 -0
  456. package/dist/python/agent-core-python/src/boombot_agent/generated/__init__.py +0 -0
  457. package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2.py +54 -0
  458. package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2_grpc.py +140 -0
  459. package/dist/python/agent-core-python/src/boombot_agent/rpc.py +265 -0
  460. package/dist/python/agent-core-python/src/boombot_agent/turn.py +577 -0
  461. package/dist/python/agent-core-python/src/boombot_ml/__init__.py +12 -0
  462. package/dist/python/agent-core-python/src/boombot_ml/embeddings.py +58 -0
  463. package/dist/python/agent-core-python/src/boombot_ml/training.py +156 -0
  464. package/dist/python/agent-core-python/uv.lock +2856 -0
  465. package/dist/web/assets/ChatView-BZrSD-64.js +262 -0
  466. package/dist/web/assets/ChatView-Cahbjez6.css +1 -0
  467. package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  468. package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  469. package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  470. package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  471. package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  472. package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  473. package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  474. package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  475. package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  476. package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  477. package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  478. package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  479. package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  480. package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  481. package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  482. package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  483. package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  484. package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  485. package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  486. package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  487. package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  488. package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  489. package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  490. package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  491. package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  492. package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  493. package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  494. package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  495. package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  496. package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  497. package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  498. package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  499. package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  500. package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  501. package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  502. package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  503. package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  504. package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  505. package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  506. package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  507. package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  508. package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  509. package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  510. package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  511. package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  512. package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  513. package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  514. package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  515. package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  516. package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  517. package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  518. package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  519. package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  520. package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  521. package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  522. package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  523. package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  524. package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  525. package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  526. package/dist/web/assets/MemoryView-BJbXqqs7.css +1 -0
  527. package/dist/web/assets/MemoryView-BfMq_6CD.js +1 -0
  528. package/dist/web/assets/SchedulesView-B22QmPR-.js +1 -0
  529. package/dist/web/assets/SchedulesView-C6XK7A8a.css +1 -0
  530. package/dist/web/assets/SessionsView-C1dqdhxI.css +1 -0
  531. package/dist/web/assets/SessionsView-DS0-aH2a.js +1 -0
  532. package/dist/web/assets/TasksView-Dtasg4hd.css +1 -0
  533. package/dist/web/assets/TasksView-ey0FLsW8.js +2 -0
  534. package/dist/web/assets/Textarea-DN3Zybl1.js +1 -0
  535. package/dist/web/assets/Tooltip-Bn2cqFPz.css +1 -0
  536. package/dist/web/assets/Tooltip-MzV2Gqnc.js +1 -0
  537. package/dist/web/assets/browser-Bnt73-Ub.js +8 -0
  538. package/dist/web/assets/index-BL8bi7Yt.css +1 -0
  539. package/dist/web/assets/index-CuPR73Tn.js +73 -0
  540. package/dist/web/favicon.svg +4 -0
  541. package/dist/web/index.html +26 -0
  542. package/dist/web/logo.jpg +0 -0
  543. package/dist/web/logo.png +0 -0
  544. package/package.json +51 -4
@@ -0,0 +1,820 @@
1
+ /**
2
+ * Turn service: runs agent-core turns, bridges loop events to
3
+ * `@boombot/protocol` stream events, persists session messages,
4
+ * and tracks in-flight turns for cancellation.
5
+ *
6
+ * Lifecycle: `submit()` resolves (or creates) the session, persists the
7
+ * user message, then runs the turn in the background and returns
8
+ * `{ turnId, sessionId }` immediately — the client follows progress over
9
+ * SSE (`GET /v1/turns/:turnId/events`). Terminal stream events
10
+ * (`agent.turn.done` / `agent.turn.error`) are published only after the
11
+ * turn's new messages have been persisted.
12
+ *
13
+ * HITL approvals: the service implements agent-core's `ApprovalPort`
14
+ * itself and injects it into the turn's runtime deps, so a policy `'ask'`
15
+ * pauses the turn here instead of being denied. The pause is announced
16
+ * over SSE as `turn.approval_required`; the client answers with the
17
+ * `turn.approve` command (`approveTurn`), or the request times out and
18
+ * the turn ends with an `approval_timeout` error. Each request is
19
+ * persisted in `@boombot/security`'s `ApprovalStore`
20
+ * (`pending` → `approved` | `denied` | `expired`) — the single source of
21
+ * truth for the request lifecycle — so a restart re-announces survivors
22
+ * instead of failing them silently (P1-2).
23
+ */
24
+ import { randomUUID } from 'node:crypto';
25
+ import { ApprovalTimeoutError, ContextCompactor } from '@boombot/core';
26
+ import { createPolicy } from '@boombot/security';
27
+ import { hashSystemPrompt } from '../session-snapshot.js';
28
+ import { simpleLocalReply } from './local-reply.js';
29
+ import { createTurnMiddlewares } from './middleware.js';
30
+ import { IntentClassifier, executePlannedTurn } from './routing/index.js';
31
+ import { attachHarnessGuard, getToolDefinitions } from './runtime-deps.js';
32
+ import { DEFAULT_APPROVAL_TIMEOUT_MS, } from './service-types.js';
33
+ import { buildSystemPrompt } from './system-prompt.js';
34
+ import { deriveTitle, lastAssistantText, parseApprovalArgs, selectNamedSkill, settleWithin, unrefTimer, } from './turn-utils.js';
35
+ // Re-exported from ./service-types.js for backward compatibility.
36
+ // Existing importers (`./index.ts`, `session-adapter.ts`, `session-tool.ts`,
37
+ // `../types.ts`, `../handlers/turn.ts`) keep importing from './service.js'.
38
+ export { DEFAULT_APPROVAL_TIMEOUT_MS, } from './service-types.js';
39
+ export function createTurnService(deps) {
40
+ return new GatewayTurnService(deps);
41
+ }
42
+ class GatewayTurnService {
43
+ deps;
44
+ activeTurns = new Map();
45
+ activeTasks = new Map();
46
+ /**
47
+ * Recently settled terminal events, keyed by turnId, so a late SSE
48
+ * subscriber can replay a terminal event published before it connected.
49
+ * Bounded: a turn id is unique per process, old entries are evicted.
50
+ */
51
+ terminalEvents = new Map();
52
+ progressEvents = new Map();
53
+ static MAX_TERMINAL_EVENTS = 200;
54
+ /** In-flight HITL approvals, keyed by runId. */
55
+ pendingApprovals = new Map();
56
+ /** runId → owning turn, so the ApprovalPort can publish SSE with the turnId. */
57
+ runToTurn = new Map();
58
+ constructor(deps) {
59
+ this.deps = deps;
60
+ }
61
+ getTerminalEvent(turnId) {
62
+ return this.terminalEvents.get(turnId);
63
+ }
64
+ getProgressEvents(turnId) {
65
+ return this.progressEvents.get(turnId) ?? [];
66
+ }
67
+ async submit(input) {
68
+ const text = input.input.trim();
69
+ if (text === '') {
70
+ throw new Error('turn input must be a non-empty string');
71
+ }
72
+ // 1. Resolve (or create) the session.
73
+ let sessionId = input.sessionId;
74
+ let history;
75
+ if (sessionId !== undefined) {
76
+ let session = await this.deps.sessions.getSession(sessionId);
77
+ if (session === null) {
78
+ if (input.channel === 'scheduler') {
79
+ const title = `[Lịch chạy] ${input.replyTo ? text.slice(0, 30) + '…' : 'Tự động'}`;
80
+ const created = await this.deps.sessions.createSession({ id: sessionId, title });
81
+ session = created;
82
+ }
83
+ else {
84
+ throw new Error(`session not found: ${sessionId}`);
85
+ }
86
+ }
87
+ // For scheduler turns: do not carry massive prior tool outputs/history from previous days/hours
88
+ history = input.channel === 'scheduler' ? [] : session.messages;
89
+ }
90
+ else {
91
+ const created = await this.deps.sessions.createSession({ title: deriveTitle(text) });
92
+ sessionId = created.id;
93
+ history = created.messages;
94
+ }
95
+ // 2. Persist the user message immediately so history survives turn failures.
96
+ const userMessage = {
97
+ role: 'user',
98
+ content: text,
99
+ ...(input.images?.length ? { images: input.images } : {}),
100
+ };
101
+ // Fresh runtime deps per turn, so provider/memory/plugin changes are picked up live.
102
+ const runtimeDeps = await this.deps.getDeps({ sessionId, channel: input.channel, replyTo: input.replyTo });
103
+ const tools = await getToolDefinitions(runtimeDeps);
104
+ const toolInfos = tools.map((t) => ({ name: t.name, description: t.description }));
105
+ const providerSnapshot = await this.deps.describeProvider?.().catch(() => undefined);
106
+ const localReply = simpleLocalReply(text, {
107
+ tools: toolInfos,
108
+ activeProvider: providerSnapshot?.model,
109
+ contextLimit: 257_000,
110
+ port: 8788,
111
+ });
112
+ let skillCatalog;
113
+ let loadedSkill;
114
+ if (localReply === undefined && this.deps.getSkillCatalog !== undefined) {
115
+ try {
116
+ skillCatalog = await this.deps.getSkillCatalog();
117
+ const namedSkill = selectNamedSkill(text, skillCatalog);
118
+ const semanticMatch = namedSkill === undefined
119
+ ? await this.deps.matchSkill?.(text, skillCatalog)
120
+ : undefined;
121
+ const selectedSkill = namedSkill ?? semanticMatch?.name;
122
+ if (selectedSkill !== undefined && this.deps.readSkill !== undefined) {
123
+ const skill = await this.deps.readSkill(selectedSkill);
124
+ loadedSkill = {
125
+ name: selectedSkill,
126
+ content: skill.content,
127
+ reason: namedSkill === undefined
128
+ ? `semantic match ${semanticMatch?.score.toFixed(3) ?? ''}`.trim()
129
+ : 'the user named it',
130
+ };
131
+ }
132
+ }
133
+ catch {
134
+ // Skill discovery is advisory; a broken user skill directory must not block chat.
135
+ }
136
+ }
137
+ // E3 wire-up: prepend the guard-tagging context notice (when a previous
138
+ // turn of this session left untrusted tool outputs) so the model treats
139
+ // them as data, never as instructions. The notice is derived fresh per
140
+ // turn and never persisted: persistNewMessages filters it out because it
141
+ // is part of baseMessages (same reference).
142
+ const tagNotice = this.deps.guardContext?.buildContextNotice(sessionId);
143
+ const memoryPrompt = this.deps.persistentMemory?.formatForSystemPrompt();
144
+ const baseMessages = [
145
+ {
146
+ role: 'system',
147
+ content: buildSystemPrompt(skillCatalog, loadedSkill, this.deps.assistantInstructions, memoryPrompt, toolInfos),
148
+ },
149
+ ...(tagNotice === undefined ? [] : [{ role: 'system', content: tagNotice }]),
150
+ ...history, userMessage,
151
+ ];
152
+ await this.deps.sessions.appendMessages(sessionId, [userMessage]);
153
+ const runId = randomUUID();
154
+ // Moved up: the middleware chain needs the turn id at registration.
155
+ const turnId = `turn_${randomUUID()}`;
156
+ this.progressList(turnId);
157
+ const turnContext = {
158
+ sessionId,
159
+ messages: baseMessages,
160
+ // Policy-filtered: a tool this turn's policy would deny is never
161
+ // offered, so the model cannot choose its way into a stopped turn.
162
+ tools,
163
+ runId,
164
+ budget: {
165
+ maxRounds: input.channel === 'scheduler' ? 4 : 20,
166
+ maxToolCalls: input.channel === 'scheduler' ? 8 : 64,
167
+ },
168
+ // PR #12 + PR #14 wire-up: run every turn through the middleware
169
+ // onion (adaptive routing + reasoning-guard verification). Built per
170
+ // turn so the verification-snapshot holder never leaks across turns;
171
+ // an empty chain would keep the pipeline's zero-cost old path.
172
+ middlewares: createTurnMiddlewares({
173
+ routingEnabled: this.deps.routing?.enabled ?? true,
174
+ verificationConfig: this.deps.reasoningGuard ?? { mode: 'warn' },
175
+ turnId,
176
+ runId,
177
+ publish: (event) => this.safePublish(event),
178
+ }),
179
+ };
180
+ // 3c. Config snapshot (P1-7): record once per session the exact model,
181
+ // provider, tool count and system prompt serving its turns. The adapter
182
+ // keeps the first capture, so later config drift cannot rewrite history.
183
+ // Best-effort by design: a snapshot failure must never break a turn.
184
+ try {
185
+ const describeProvider = this.deps.describeProvider;
186
+ const providerPart = describeProvider === undefined
187
+ ? undefined
188
+ : await describeProvider().catch(() => undefined);
189
+ const systemPrompt = baseMessages[0]?.content ?? '';
190
+ await this.deps.sessions.captureConfigSnapshot?.(sessionId, {
191
+ ...(providerPart ?? {}),
192
+ toolsEnabled: turnContext.tools.length,
193
+ systemPromptHash: hashSystemPrompt(systemPrompt),
194
+ });
195
+ }
196
+ catch {
197
+ // Observability only — the turn proceeds without a snapshot.
198
+ }
199
+ // 4. Track the turn and run it in the background; submit returns now.
200
+ const controller = new AbortController();
201
+ this.activeTurns.set(turnId, controller);
202
+ this.runToTurn.set(runId, { turnId, sessionId });
203
+ const state = { memoryHits: 0, toolStartedAt: new Map() };
204
+ // 3b. Harness guard: deterministic pre-execution gate for every tool
205
+ // call. Denials never reach the tool handler and are announced on the
206
+ // wire as `guard.blocked` so the client knows why the call was refused.
207
+ attachHarnessGuard(runtimeDeps, createPolicy({
208
+ mode: this.deps.harnessGuard?.mode ?? 'warn',
209
+ writeZones: this.deps.harnessGuard?.writeZones ?? [],
210
+ }), (info) => this.safePublish({
211
+ type: 'guard.blocked',
212
+ payload: {
213
+ turnId,
214
+ runId,
215
+ toolName: info.toolName,
216
+ layer: info.layer,
217
+ reason: info.reason,
218
+ },
219
+ }));
220
+ const task = this.runInBackground(turnId, runId, controller, sessionId, baseMessages, state, runtimeDeps, turnContext, localReply);
221
+ this.activeTasks.set(turnId, task);
222
+ void task.finally(() => this.activeTasks.delete(turnId));
223
+ return { turnId, sessionId };
224
+ }
225
+ async cancel(turnId) {
226
+ const controller = this.activeTurns.get(turnId);
227
+ if (controller === undefined) {
228
+ return false;
229
+ }
230
+ this.activeTurns.delete(turnId);
231
+ controller.abort();
232
+ return true;
233
+ }
234
+ async shutdown() {
235
+ // Fail pending HITL approvals first: the human can no longer answer,
236
+ // so each waiting turn ends with an `approval_timeout` error instead of
237
+ // hanging on a promise whose resolvers are about to be dropped.
238
+ const pendings = [...this.pendingApprovals.entries()];
239
+ const pendingTasks = pendings.flatMap(([runId]) => {
240
+ const turnId = this.runToTurn.get(runId)?.turnId;
241
+ const task = turnId === undefined ? undefined : this.activeTasks.get(turnId);
242
+ return task === undefined ? [] : [task];
243
+ });
244
+ this.pendingApprovals.clear();
245
+ const store = this.deps.approvalStore;
246
+ for (const [runId, pending] of pendings) {
247
+ // The waiter is about to be dropped: expire the persisted request so
248
+ // a later boot scan does not resurrect it. Best-effort — the turn
249
+ // still fails even if the store is broken.
250
+ try {
251
+ store?.expireApproval(runId);
252
+ }
253
+ catch {
254
+ // fall through: the rejection below still ends the turn
255
+ }
256
+ const err = new ApprovalTimeoutError(runId, 0);
257
+ err.message = `gateway shutting down: ${err.message}`;
258
+ pending.reject(err);
259
+ }
260
+ await settleWithin(pendingTasks, 2_000);
261
+ this.runToTurn.clear();
262
+ const controllers = [...this.activeTurns.values()];
263
+ this.activeTurns.clear();
264
+ for (const controller of controllers) {
265
+ controller.abort();
266
+ }
267
+ }
268
+ // -- ApprovalPort (HITL) -------------------------------------------------
269
+ async requestApproval(req, signal) {
270
+ const owner = this.runToTurn.get(req.runId);
271
+ if (owner === undefined) {
272
+ throw new Error(`no active turn for approval run ${req.runId}`);
273
+ }
274
+ const timeoutMs = this.deps.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
275
+ const { turnId, sessionId } = owner;
276
+ // Announce the pause on the wire so the client can prompt a human.
277
+ this.safePublish({
278
+ type: 'turn.approval_required',
279
+ payload: {
280
+ turnId,
281
+ runId: req.runId,
282
+ toolName: req.toolName,
283
+ args: { ...req.args },
284
+ reason: req.reason,
285
+ },
286
+ });
287
+ // Persist first: the state machine is the single source of truth for
288
+ // the request lifecycle; the in-memory waiter below is ephemeral. A
289
+ // broken store degrades to in-memory-only — it never breaks the turn.
290
+ const store = this.deps.approvalStore;
291
+ if (store !== undefined) {
292
+ try {
293
+ store.requestApproval({
294
+ id: req.runId,
295
+ turnId,
296
+ sessionId,
297
+ toolName: req.toolName,
298
+ argsJson: JSON.stringify(req.args),
299
+ reason: req.reason,
300
+ expiresAtMs: Date.now() + timeoutMs,
301
+ });
302
+ }
303
+ catch {
304
+ // degraded: the waiter below still works; the boot scan just won't see it
305
+ }
306
+ }
307
+ return new Promise((resolve, reject) => {
308
+ const cleanup = () => {
309
+ clearTimeout(timer);
310
+ signal?.removeEventListener('abort', onAbort);
311
+ };
312
+ const onAbort = () => {
313
+ this.pendingApprovals.delete(req.runId);
314
+ cleanup();
315
+ const err = new Error('turn cancelled while awaiting approval');
316
+ err.name = 'AbortError';
317
+ reject(err);
318
+ };
319
+ const timer = setTimeout(() => {
320
+ this.pendingApprovals.delete(req.runId);
321
+ // Enforce the deadline on the persisted row too, so the boot scan
322
+ // never resurrects a request this process already killed.
323
+ try {
324
+ store?.sweepExpired();
325
+ }
326
+ catch {
327
+ // degraded: the turn still fails below
328
+ }
329
+ reject(new ApprovalTimeoutError(req.runId, timeoutMs));
330
+ }, timeoutMs);
331
+ // The approval timer must not pin the process (or a test worker) open.
332
+ unrefTimer(timer);
333
+ this.pendingApprovals.set(req.runId, {
334
+ resolve: (verdict) => {
335
+ cleanup();
336
+ resolve(verdict);
337
+ },
338
+ reject: (err) => {
339
+ cleanup();
340
+ reject(err);
341
+ },
342
+ turnId,
343
+ toolName: req.toolName,
344
+ });
345
+ if (signal?.aborted === true) {
346
+ onAbort();
347
+ return;
348
+ }
349
+ signal?.addEventListener('abort', onAbort, { once: true });
350
+ });
351
+ }
352
+ async approveTurn(runId, approved, editedArgs) {
353
+ const pending = this.pendingApprovals.get(runId);
354
+ if (pending !== undefined) {
355
+ this.pendingApprovals.delete(runId);
356
+ try {
357
+ this.deps.approvalStore?.resolveApproval(runId, approved ? 'approved' : 'denied');
358
+ }
359
+ catch {
360
+ // The verdict was already delivered; persistence is best-effort.
361
+ }
362
+ pending.resolve({ approved, editedArgs });
363
+ return { found: true, resumed: true };
364
+ }
365
+ // Restart path: no live waiter, but the request may still be `pending`
366
+ // in the store. Record the human's decision; the turn itself cannot
367
+ // resume (its loop state died with the previous process).
368
+ try {
369
+ const resolved = this.deps.approvalStore?.resolveApproval(runId, approved ? 'approved' : 'denied');
370
+ if (resolved !== undefined) {
371
+ return { found: true, resumed: false };
372
+ }
373
+ }
374
+ catch {
375
+ // fall through to not-found
376
+ }
377
+ return { found: false, resumed: false };
378
+ }
379
+ /**
380
+ * Re-announce approvals still `pending` from the previous process
381
+ * (P1-2 approval resume). Called once at gateway boot, before the HTTP
382
+ * server starts listening: every survivor is re-published as
383
+ * `turn.approval_required` (payload flag `resumed: true`) and retained in
384
+ * the per-turn progress replay, so a client opening
385
+ * `GET /v1/turns/:turnId/events` after boot still sees the pending
386
+ * approval instead of a silent timeout. Past-deadline requests are marked
387
+ * `expired` by the store and are NOT re-announced. Returns the number
388
+ * re-announced.
389
+ *
390
+ * Honest limit: the turn that requested the approval is gone (its loop
391
+ * state died with the old process). Answering via `turn.approve` records
392
+ * the human's decision; `approveTurn` reports `{ found: true, resumed:
393
+ * false }` for these.
394
+ */
395
+ reannouncePendingApprovals() {
396
+ const store = this.deps.approvalStore;
397
+ if (store === undefined)
398
+ return 0;
399
+ let resumed;
400
+ try {
401
+ resumed = store.resumePendingOnBoot().resumed;
402
+ }
403
+ catch {
404
+ return 0;
405
+ }
406
+ for (const req of resumed) {
407
+ // Retain for late SSE subscribers: the turn never ran in this
408
+ // process, so no progress list exists for it yet.
409
+ this.progressList(req.turnId);
410
+ this.safePublish({
411
+ type: 'turn.approval_required',
412
+ payload: {
413
+ turnId: req.turnId,
414
+ runId: req.id,
415
+ toolName: req.toolName,
416
+ args: parseApprovalArgs(req.argsJson),
417
+ reason: req.reason,
418
+ resumed: true,
419
+ },
420
+ });
421
+ }
422
+ return resumed.length;
423
+ }
424
+ /**
425
+ * Get-or-create the per-turn progress replay list, evicting the oldest
426
+ * turn when the bound is hit. Used for live turns and for approvals
427
+ * re-announced after a restart (their turns never ran in this process).
428
+ */
429
+ progressList(turnId) {
430
+ let list = this.progressEvents.get(turnId);
431
+ if (list === undefined) {
432
+ if (this.progressEvents.size >= GatewayTurnService.MAX_TERMINAL_EVENTS) {
433
+ const oldest = this.progressEvents.keys().next();
434
+ if (!oldest.done)
435
+ this.progressEvents.delete(oldest.value);
436
+ }
437
+ list = [];
438
+ this.progressEvents.set(turnId, list);
439
+ }
440
+ return list;
441
+ }
442
+ async runInBackground(turnId, runId, controller, sessionId, baseMessages, state, runtimeDeps, turnContext, localReply) {
443
+ try {
444
+ if (localReply !== undefined) {
445
+ this.bridgeEvent(turnId, state, { type: 'turn.start', sessionId, runId });
446
+ const result = {
447
+ status: 'completed',
448
+ messages: [...baseMessages, { role: 'assistant', content: localReply }],
449
+ rounds: 0,
450
+ toolCallsExecuted: 0,
451
+ toolCallsDenied: 0,
452
+ };
453
+ await this.persistNewMessages(turnId, sessionId, baseMessages, 0, result.messages);
454
+ this.publishTerminal(turnId, result);
455
+ return;
456
+ }
457
+ // Apply Context Auto-Compaction with dynamic provider context limits
458
+ const limits = this.deps.resolveContextLimits
459
+ ? await this.deps.resolveContextLimits()
460
+ : { maxContextTokens: 128_000, maxContextChars: 512_000, triggerThreshold: 0.75, hotZoneRounds: 4 };
461
+ const compactor = new ContextCompactor({
462
+ maxContextTokens: limits.maxContextTokens,
463
+ triggerThreshold: limits.triggerThreshold ?? 0.75,
464
+ hotZoneRounds: limits.hotZoneRounds ?? 4,
465
+ });
466
+ const compactionRes = await compactor.compact(turnContext.messages);
467
+ const effectiveContext = compactionRes.compacted
468
+ ? { ...turnContext, messages: compactionRes.messages }
469
+ : turnContext;
470
+ if (compactionRes.compacted) {
471
+ this.safePublish({
472
+ type: 'turn.compacted',
473
+ payload: {
474
+ turnId,
475
+ prunedTurns: compactionRes.prunedTurnsCount,
476
+ originalTokens: compactionRes.originalTokens,
477
+ compactedTokens: compactionRes.compactedTokens,
478
+ },
479
+ });
480
+ }
481
+ // The service is its own ApprovalPort: a policy 'ask' pauses the turn
482
+ // here until `approveTurn` (or the timeout) resolves the verdict.
483
+ const depsWithApproval = { ...runtimeDeps, approval: this };
484
+ // Dynamic Macro Routing: Evaluate intent to decide between Planner vs ReAct
485
+ const latestUserContent = [...effectiveContext.messages].reverse().find((m) => m.role === 'user')?.content ?? '';
486
+ const decision = IntentClassifier.classify(latestUserContent);
487
+ let result;
488
+ if (decision.mode === 'planner') {
489
+ this.safePublish({
490
+ type: 'turn.routed',
491
+ payload: {
492
+ turnId,
493
+ runId,
494
+ route: 'deep',
495
+ reason: `planner:${decision.reason}`,
496
+ },
497
+ });
498
+ result = await executePlannedTurn({
499
+ goal: decision.goal,
500
+ turnId,
501
+ runId,
502
+ baseContext: effectiveContext,
503
+ runtimeDeps: depsWithApproval,
504
+ runTurn: this.deps.runTurn,
505
+ onEvent: (event) => this.bridgeEvent(turnId, state, event),
506
+ signal: controller.signal,
507
+ onPhaseProgress: (phase, total, title) => {
508
+ this.safePublish({
509
+ type: 'task.progress',
510
+ payload: {
511
+ taskId: turnId,
512
+ stepIndex: phase - 1,
513
+ totalSteps: total,
514
+ detail: title,
515
+ },
516
+ });
517
+ },
518
+ });
519
+ }
520
+ else {
521
+ result = await this.deps.runTurn(depsWithApproval, effectiveContext, (event) => this.bridgeEvent(turnId, state, event), controller.signal);
522
+ }
523
+ // E3 wire-up: tag this turn's tool outputs (trusted/untrusted) into
524
+ // the gateway guard context so the next turn of the same session can
525
+ // see them, and announce each created tag on the wire (`guard.tagged`).
526
+ // Tagging never breaks turn settlement.
527
+ try {
528
+ const records = this.deps.guardContext?.tagTurnOutputs({
529
+ sessionId,
530
+ turnId,
531
+ runId,
532
+ messages: result.messages,
533
+ });
534
+ for (const record of records ?? []) {
535
+ try {
536
+ this.safePublish({
537
+ type: 'guard.tagged',
538
+ payload: {
539
+ turnId,
540
+ runId,
541
+ toolName: record.toolName,
542
+ trust: record.tag.trust,
543
+ callId: record.callId || undefined,
544
+ patterns: record.patterns.length > 0 ? [...record.patterns] : undefined,
545
+ },
546
+ });
547
+ }
548
+ catch {
549
+ // Intentionally swallowed: one broken stream event must not
550
+ // hide the remaining tags or break turn settlement.
551
+ }
552
+ }
553
+ }
554
+ catch {
555
+ // Intentionally swallowed: persistence and terminal events continue.
556
+ }
557
+ await this.persistNewMessages(turnId, sessionId, baseMessages, state.memoryHits, result.messages, compactionRes.compacted, effectiveContext.messages.length);
558
+ this.publishTerminal(turnId, result);
559
+ }
560
+ catch (err) {
561
+ // runTurn is not supposed to reject (cancellation and errors become
562
+ // TurnResult), but a background turn must never die silently.
563
+ this.safePublish({
564
+ type: 'agent.turn.error',
565
+ payload: {
566
+ turnId,
567
+ error: err instanceof Error ? err.message : 'turn failed unexpectedly',
568
+ retryable: true,
569
+ },
570
+ });
571
+ }
572
+ finally {
573
+ if (this.activeTurns.get(turnId) === controller) {
574
+ this.activeTurns.delete(turnId);
575
+ }
576
+ this.runToTurn.delete(runId);
577
+ }
578
+ }
579
+ /**
580
+ * Map loop events to wire stream events. The terminal `turn.end` is NOT
581
+ * published here — it goes out via `publishTerminal` after persistence.
582
+ *
583
+ * Additive mapping: the typed `turn.*` events (turn.start, turn.tool_start,
584
+ * turn.tool_end, turn.approval_required) are published alongside the
585
+ * legacy `agent.turn.*` events, which keep their exact shapes.
586
+ */
587
+ bridgeEvent(turnId, state, event) {
588
+ switch (event.type) {
589
+ case 'turn.start': {
590
+ state.runId = event.runId;
591
+ this.safePublish({
592
+ type: 'turn.start',
593
+ payload: { turnId, runId: event.runId },
594
+ });
595
+ break;
596
+ }
597
+ case 'turn.routed': {
598
+ this.safePublish({
599
+ type: 'turn.routed',
600
+ payload: {
601
+ turnId,
602
+ runId: event.runId,
603
+ route: event.mode,
604
+ reason: event.reason,
605
+ },
606
+ });
607
+ break;
608
+ }
609
+ case 'turn.escalated': {
610
+ // The routing middleware only ever escalates fast → deep, at most
611
+ // once per turn (routing.ts: the `finalize` hook checks
612
+ // `state.mode === 'fast'` and flips it to `'deep'` with reason
613
+ // `guard:unsupported-claims`) — so the wire fields are fixed here
614
+ // rather than carried on the internal event.
615
+ this.safePublish({
616
+ type: 'turn.escalated',
617
+ payload: {
618
+ turnId,
619
+ runId: event.runId,
620
+ from: 'fast',
621
+ to: 'deep',
622
+ trigger: event.reason,
623
+ },
624
+ });
625
+ break;
626
+ }
627
+ case 'text.delta': {
628
+ this.safePublish({
629
+ type: 'agent.turn.token',
630
+ payload: { turnId, delta: event.text },
631
+ });
632
+ break;
633
+ }
634
+ case 'reasoning.delta': {
635
+ // Reasoning fragments are a separate stream from the final answer:
636
+ // they must not flow through `agent.turn.token` (which clients render
637
+ // as the answer) so web UI can render them in a collapsible block.
638
+ this.safePublish({
639
+ type: 'turn.reasoning_delta',
640
+ payload: {
641
+ turnId,
642
+ ...(state.runId !== undefined ? { runId: state.runId } : {}),
643
+ delta: event.text,
644
+ },
645
+ });
646
+ break;
647
+ }
648
+ case 'tool.call': {
649
+ this.safePublish({
650
+ type: 'agent.turn.tool_call',
651
+ payload: {
652
+ turnId,
653
+ toolCall: {
654
+ id: event.call.id,
655
+ name: event.call.name,
656
+ arguments: { ...event.call.arguments },
657
+ startedAt: new Date().toISOString(),
658
+ },
659
+ },
660
+ });
661
+ if (state.runId !== undefined) {
662
+ state.toolStartedAt.set(event.call.id, Date.now());
663
+ this.safePublish({
664
+ type: 'turn.tool_start',
665
+ payload: {
666
+ turnId,
667
+ runId: state.runId,
668
+ toolCallId: event.call.id,
669
+ toolName: event.call.name,
670
+ },
671
+ });
672
+ }
673
+ break;
674
+ }
675
+ case 'tool.result': {
676
+ if (state.runId !== undefined) {
677
+ const startedAt = state.toolStartedAt.get(event.result.callId);
678
+ state.toolStartedAt.delete(event.result.callId);
679
+ this.safePublish({
680
+ type: 'turn.tool_end',
681
+ payload: {
682
+ turnId,
683
+ runId: state.runId,
684
+ toolCallId: event.result.callId,
685
+ toolName: event.result.name,
686
+ durationMs: startedAt === undefined ? 0 : Math.max(0, Date.now() - startedAt),
687
+ status: event.result.error ? 'error' : 'ok',
688
+ },
689
+ });
690
+ }
691
+ break;
692
+ }
693
+ case 'policy.deny': {
694
+ // Hard policy denials surface as warnings. Approval-required calls
695
+ // use the separate `approval.pending` path and never enter this case.
696
+ this.safePublish({
697
+ type: 'agent.turn.error',
698
+ payload: {
699
+ turnId,
700
+ error: `policy: ${event.reason} — tool '${event.call.name}' was blocked (accident-prevention)`,
701
+ retryable: false,
702
+ },
703
+ });
704
+ break;
705
+ }
706
+ case 'memory.hits': {
707
+ state.memoryHits = event.count;
708
+ break;
709
+ }
710
+ case 'turn.end':
711
+ default: {
712
+ break;
713
+ }
714
+ }
715
+ }
716
+ publishTerminal(turnId, result) {
717
+ if (result.status === 'error') {
718
+ this.safePublish({
719
+ type: 'agent.turn.error',
720
+ payload: {
721
+ turnId,
722
+ error: result.error ?? 'turn failed',
723
+ retryable: true,
724
+ },
725
+ });
726
+ return;
727
+ }
728
+ const payload = { turnId, status: result.status };
729
+ const output = lastAssistantText(result.messages);
730
+ if (output !== '') {
731
+ payload.output = output;
732
+ }
733
+ if (result.usage !== undefined)
734
+ payload.usage = result.usage;
735
+ this.safePublish({ type: 'agent.turn.done', payload });
736
+ }
737
+ /**
738
+ * Persist the messages the turn produced. `buildPrompt` unshifts exactly
739
+ * one derived system message when memory hits exist — skip it so derived
740
+ * memory context is not duplicated into history on every turn.
741
+ */
742
+ async persistNewMessages(turnId, sessionId, baseMessages, memoryHits, resultMessages, wasCompacted = false, effectiveContextLength = 0) {
743
+ const withoutInjected = memoryHits > 0 ? resultMessages.slice(1) : resultMessages;
744
+ let added;
745
+ if (wasCompacted && effectiveContextLength > 0) {
746
+ added = withoutInjected.slice(effectiveContextLength);
747
+ // Fallback: đảm bảo không bao giờ bị rơi mất câu trả lời của assistant
748
+ if (added.length === 0 && withoutInjected.length > 0) {
749
+ const last = withoutInjected[withoutInjected.length - 1];
750
+ if (last && last.role === 'assistant')
751
+ added = [last];
752
+ }
753
+ }
754
+ else {
755
+ added = withoutInjected.slice(baseMessages.length);
756
+ }
757
+ if (added.length === 0) {
758
+ return;
759
+ }
760
+ try {
761
+ await this.deps.sessions.appendMessages(sessionId, added);
762
+ }
763
+ catch (err) {
764
+ this.safePublish({
765
+ type: 'agent.turn.error',
766
+ payload: {
767
+ turnId,
768
+ error: `failed to persist turn messages: ${err instanceof Error ? err.message : 'unknown error'}`,
769
+ retryable: false,
770
+ },
771
+ });
772
+ }
773
+ }
774
+ /** The event bus must never break a turn; terminal state still settles. */
775
+ safePublish(event) {
776
+ if (event.type === 'turn.start' ||
777
+ event.type === 'turn.tool_start' ||
778
+ event.type === 'turn.tool_end' ||
779
+ // P1-2: a late subscriber to the turn stream must see the pending
780
+ // approval (live pause or boot re-announcement), not wait forever.
781
+ event.type === 'turn.approval_required') {
782
+ const { turnId } = event.payload;
783
+ const history = this.progressEvents.get(turnId);
784
+ history?.push(event);
785
+ // ponytail: latest 257 lifecycle events for 200 turns; durable replay needs a store.
786
+ if (history && history.length > 257)
787
+ history.shift();
788
+ }
789
+ try {
790
+ this.deps.publish(event);
791
+ }
792
+ catch {
793
+ // Intentionally swallowed: persistence and turn settlement continue.
794
+ }
795
+ this.rememberTerminal(event);
796
+ }
797
+ /**
798
+ * Retain terminal events for late SSE subscribers. Runs after the publish
799
+ * attempt so a broken bus still leaves the replay available.
800
+ */
801
+ rememberTerminal(event) {
802
+ if (event.type !== 'agent.turn.done' && event.type !== 'agent.turn.error') {
803
+ return;
804
+ }
805
+ const turnId = event.payload.turnId;
806
+ if (typeof turnId !== 'string' || turnId.length === 0)
807
+ return;
808
+ if (this.terminalEvents.size >= GatewayTurnService.MAX_TERMINAL_EVENTS) {
809
+ const oldest = this.terminalEvents.keys().next();
810
+ if (!oldest.done)
811
+ this.terminalEvents.delete(oldest.value);
812
+ }
813
+ this.terminalEvents.set(turnId, event);
814
+ }
815
+ }
816
+ /**
817
+ * Parse persisted approval args for the re-announced SSE payload.
818
+ * Corrupt JSON degrades to `{}` — the approver still sees the tool name
819
+ * and reason, and the row keeps the original bytes.
820
+ */