slackhive 0.1.37 → 0.1.39

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 (542) hide show
  1. package/.dockerignore +14 -0
  2. package/.env.example +44 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +65 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +27 -0
  7. package/.github/dependabot.yml +20 -0
  8. package/.github/workflows/audit.yml +149 -0
  9. package/.github/workflows/ci.yml +135 -0
  10. package/CHANGELOG.md +52 -0
  11. package/CODE_OF_CONDUCT.md +37 -0
  12. package/CONTRIBUTING.md +204 -0
  13. package/LICENSE +21 -0
  14. package/README.md +19 -0
  15. package/SECURITY.md +47 -0
  16. package/apps/runner/Dockerfile +33 -0
  17. package/apps/runner/dist/__tests__/channel-restrictions.test.d.ts +8 -0
  18. package/apps/runner/dist/__tests__/channel-restrictions.test.js +63 -0
  19. package/apps/runner/dist/__tests__/channel-restrictions.test.js.map +1 -0
  20. package/apps/runner/dist/__tests__/claude-handler-resolve.test.d.ts +20 -0
  21. package/apps/runner/dist/__tests__/claude-handler-resolve.test.js +178 -0
  22. package/apps/runner/dist/__tests__/claude-handler-resolve.test.js.map +1 -0
  23. package/apps/runner/dist/__tests__/compile-claude-md.test.d.ts +13 -0
  24. package/apps/runner/dist/__tests__/compile-claude-md.test.js +144 -0
  25. package/apps/runner/dist/__tests__/compile-claude-md.test.js.map +1 -0
  26. package/apps/runner/dist/__tests__/memory-sync.test.d.ts +11 -0
  27. package/apps/runner/dist/__tests__/memory-sync.test.js +56 -0
  28. package/apps/runner/dist/__tests__/memory-sync.test.js.map +1 -0
  29. package/apps/runner/dist/__tests__/slack-file-support.test.d.ts +9 -0
  30. package/apps/runner/dist/__tests__/slack-file-support.test.js +271 -0
  31. package/apps/runner/dist/__tests__/slack-file-support.test.js.map +1 -0
  32. package/apps/runner/dist/__tests__/slack-formatting.test.d.ts +12 -0
  33. package/apps/runner/dist/__tests__/slack-formatting.test.js +400 -0
  34. package/apps/runner/dist/__tests__/slack-formatting.test.js.map +1 -0
  35. package/apps/runner/dist/__tests__/thread-context.test.d.ts +12 -0
  36. package/apps/runner/dist/__tests__/thread-context.test.js +182 -0
  37. package/apps/runner/dist/__tests__/thread-context.test.js.map +1 -0
  38. package/apps/runner/dist/agent-runner.d.ts +118 -0
  39. package/apps/runner/dist/agent-runner.js +352 -0
  40. package/apps/runner/dist/agent-runner.js.map +1 -0
  41. package/apps/runner/dist/claude-handler.d.ts +122 -0
  42. package/apps/runner/dist/claude-handler.js +402 -0
  43. package/apps/runner/dist/claude-handler.js.map +1 -0
  44. package/apps/runner/dist/compile-claude-md.d.ts +59 -0
  45. package/apps/runner/dist/compile-claude-md.js +291 -0
  46. package/apps/runner/dist/compile-claude-md.js.map +1 -0
  47. package/apps/runner/dist/correction-handler.d.ts +46 -0
  48. package/apps/runner/dist/correction-handler.js +162 -0
  49. package/apps/runner/dist/correction-handler.js.map +1 -0
  50. package/apps/runner/dist/correction-manager.d.ts +53 -0
  51. package/apps/runner/dist/correction-manager.js +241 -0
  52. package/apps/runner/dist/correction-manager.js.map +1 -0
  53. package/apps/runner/dist/db.d.ts +193 -0
  54. package/apps/runner/dist/db.js +492 -0
  55. package/apps/runner/dist/db.js.map +1 -0
  56. package/apps/runner/dist/index.d.ts +9 -0
  57. package/apps/runner/dist/index.js +43 -0
  58. package/apps/runner/dist/index.js.map +1 -0
  59. package/apps/runner/dist/job-scheduler.d.ts +57 -0
  60. package/apps/runner/dist/job-scheduler.js +150 -0
  61. package/apps/runner/dist/job-scheduler.js.map +1 -0
  62. package/apps/runner/dist/logger.d.ts +32 -0
  63. package/apps/runner/dist/logger.js +52 -0
  64. package/apps/runner/dist/logger.js.map +1 -0
  65. package/apps/runner/dist/mcp-process-manager.d.ts +38 -0
  66. package/apps/runner/dist/mcp-process-manager.js +189 -0
  67. package/apps/runner/dist/mcp-process-manager.js.map +1 -0
  68. package/apps/runner/dist/memory-mcp.d.ts +14 -0
  69. package/apps/runner/dist/memory-mcp.js +88 -0
  70. package/apps/runner/dist/memory-mcp.js.map +1 -0
  71. package/apps/runner/dist/memory-watcher.d.ts +78 -0
  72. package/apps/runner/dist/memory-watcher.js +220 -0
  73. package/apps/runner/dist/memory-watcher.js.map +1 -0
  74. package/apps/runner/dist/slack-handler.d.ts +120 -0
  75. package/apps/runner/dist/slack-handler.js +843 -0
  76. package/apps/runner/dist/slack-handler.js.map +1 -0
  77. package/apps/runner/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  78. package/apps/runner/package.json +42 -0
  79. package/apps/runner/src/__tests__/channel-restrictions.test.ts +75 -0
  80. package/apps/runner/src/__tests__/claude-handler-resolve.test.ts +160 -0
  81. package/apps/runner/src/__tests__/compile-claude-md.test.ts +139 -0
  82. package/apps/runner/src/__tests__/memory-sync.test.ts +59 -0
  83. package/apps/runner/src/__tests__/slack-file-support.test.ts +376 -0
  84. package/apps/runner/src/__tests__/slack-formatting.test.ts +495 -0
  85. package/apps/runner/src/__tests__/thread-context.test.ts +215 -0
  86. package/apps/runner/src/agent-runner.ts +397 -0
  87. package/apps/runner/src/claude-handler.ts +475 -0
  88. package/apps/runner/src/compile-claude-md.ts +283 -0
  89. package/apps/runner/src/correction-handler.ts +191 -0
  90. package/apps/runner/src/correction-manager.ts +285 -0
  91. package/apps/runner/src/db.ts +604 -0
  92. package/apps/runner/src/index.ts +46 -0
  93. package/apps/runner/src/job-scheduler.ts +165 -0
  94. package/apps/runner/src/logger.ts +49 -0
  95. package/apps/runner/src/mcp-process-manager.ts +195 -0
  96. package/apps/runner/src/memory-mcp.ts +85 -0
  97. package/apps/runner/src/memory-watcher.ts +215 -0
  98. package/apps/runner/src/slack-handler.ts +929 -0
  99. package/apps/runner/tsconfig.json +17 -0
  100. package/apps/runner/vitest.config.mts +17 -0
  101. package/apps/web/.eslintrc.json +3 -0
  102. package/apps/web/.next/app-build-manifest.json +323 -0
  103. package/apps/web/.next/app-path-routes-manifest.json +46 -0
  104. package/apps/web/.next/build-manifest.json +33 -0
  105. package/apps/web/.next/cache/.previewinfo +1 -0
  106. package/apps/web/.next/cache/.rscinfo +1 -0
  107. package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
  108. package/apps/web/.next/cache/webpack/client-production/1.pack +0 -0
  109. package/apps/web/.next/cache/webpack/client-production/2.pack +0 -0
  110. package/apps/web/.next/cache/webpack/client-production/3.pack +0 -0
  111. package/apps/web/.next/cache/webpack/client-production/4.pack +0 -0
  112. package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
  113. package/apps/web/.next/cache/webpack/client-production/index.pack.old +0 -0
  114. package/apps/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
  115. package/apps/web/.next/cache/webpack/edge-server-production/1.pack +0 -0
  116. package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
  117. package/apps/web/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  118. package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
  119. package/apps/web/.next/cache/webpack/server-production/1.pack +0 -0
  120. package/apps/web/.next/cache/webpack/server-production/2.pack +0 -0
  121. package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
  122. package/apps/web/.next/cache/webpack/server-production/index.pack.old +0 -0
  123. package/apps/web/.next/diagnostics/build-diagnostics.json +6 -0
  124. package/apps/web/.next/diagnostics/framework.json +1 -0
  125. package/apps/web/.next/package.json +1 -0
  126. package/apps/web/.next/react-loadable-manifest.json +1 -0
  127. package/apps/web/.next/server/app/_not-found/page.js +2 -0
  128. package/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  129. package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  130. package/apps/web/.next/server/app/agents/[slug]/page.js +4 -0
  131. package/apps/web/.next/server/app/agents/[slug]/page.js.nft.json +1 -0
  132. package/apps/web/.next/server/app/agents/[slug]/page_client-reference-manifest.js +1 -0
  133. package/apps/web/.next/server/app/agents/new/page.js +2 -0
  134. package/apps/web/.next/server/app/agents/new/page.js.nft.json +1 -0
  135. package/apps/web/.next/server/app/agents/new/page_client-reference-manifest.js +1 -0
  136. package/apps/web/.next/server/app/api/agents/[id]/access/route.js +1 -0
  137. package/apps/web/.next/server/app/api/agents/[id]/access/route.js.nft.json +1 -0
  138. package/apps/web/.next/server/app/api/agents/[id]/access/route_client-reference-manifest.js +1 -0
  139. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js +6 -0
  140. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js.nft.json +1 -0
  141. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route_client-reference-manifest.js +1 -0
  142. package/apps/web/.next/server/app/api/agents/[id]/logs/route.js +3 -0
  143. package/apps/web/.next/server/app/api/agents/[id]/logs/route.js.nft.json +1 -0
  144. package/apps/web/.next/server/app/api/agents/[id]/logs/route_client-reference-manifest.js +1 -0
  145. package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js +1 -0
  146. package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js.nft.json +1 -0
  147. package/apps/web/.next/server/app/api/agents/[id]/manifest/route_client-reference-manifest.js +1 -0
  148. package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js +1 -0
  149. package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js.nft.json +1 -0
  150. package/apps/web/.next/server/app/api/agents/[id]/mcps/route_client-reference-manifest.js +1 -0
  151. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js +1 -0
  152. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js.nft.json +1 -0
  153. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route_client-reference-manifest.js +1 -0
  154. package/apps/web/.next/server/app/api/agents/[id]/memories/route.js +1 -0
  155. package/apps/web/.next/server/app/api/agents/[id]/memories/route.js.nft.json +1 -0
  156. package/apps/web/.next/server/app/api/agents/[id]/memories/route_client-reference-manifest.js +1 -0
  157. package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js +1 -0
  158. package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js.nft.json +1 -0
  159. package/apps/web/.next/server/app/api/agents/[id]/permissions/route_client-reference-manifest.js +1 -0
  160. package/apps/web/.next/server/app/api/agents/[id]/reload/route.js +1 -0
  161. package/apps/web/.next/server/app/api/agents/[id]/reload/route.js.nft.json +1 -0
  162. package/apps/web/.next/server/app/api/agents/[id]/reload/route_client-reference-manifest.js +1 -0
  163. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js +1 -0
  164. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js.nft.json +1 -0
  165. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route_client-reference-manifest.js +1 -0
  166. package/apps/web/.next/server/app/api/agents/[id]/route.js +33 -0
  167. package/apps/web/.next/server/app/api/agents/[id]/route.js.nft.json +1 -0
  168. package/apps/web/.next/server/app/api/agents/[id]/route_client-reference-manifest.js +1 -0
  169. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js +1 -0
  170. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js.nft.json +1 -0
  171. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route_client-reference-manifest.js +1 -0
  172. package/apps/web/.next/server/app/api/agents/[id]/skills/route.js +1 -0
  173. package/apps/web/.next/server/app/api/agents/[id]/skills/route.js.nft.json +1 -0
  174. package/apps/web/.next/server/app/api/agents/[id]/skills/route_client-reference-manifest.js +1 -0
  175. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js +1 -0
  176. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js.nft.json +1 -0
  177. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route_client-reference-manifest.js +1 -0
  178. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js +1 -0
  179. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js.nft.json +1 -0
  180. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route_client-reference-manifest.js +1 -0
  181. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js +1 -0
  182. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js.nft.json +1 -0
  183. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route_client-reference-manifest.js +1 -0
  184. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js +1 -0
  185. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js.nft.json +1 -0
  186. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route_client-reference-manifest.js +1 -0
  187. package/apps/web/.next/server/app/api/agents/[id]/start/route.js +1 -0
  188. package/apps/web/.next/server/app/api/agents/[id]/start/route.js.nft.json +1 -0
  189. package/apps/web/.next/server/app/api/agents/[id]/start/route_client-reference-manifest.js +1 -0
  190. package/apps/web/.next/server/app/api/agents/[id]/stop/route.js +1 -0
  191. package/apps/web/.next/server/app/api/agents/[id]/stop/route.js.nft.json +1 -0
  192. package/apps/web/.next/server/app/api/agents/[id]/stop/route_client-reference-manifest.js +1 -0
  193. package/apps/web/.next/server/app/api/agents/route.js +91 -0
  194. package/apps/web/.next/server/app/api/agents/route.js.nft.json +1 -0
  195. package/apps/web/.next/server/app/api/agents/route_client-reference-manifest.js +1 -0
  196. package/apps/web/.next/server/app/api/auth/login/route.js +1 -0
  197. package/apps/web/.next/server/app/api/auth/login/route.js.nft.json +1 -0
  198. package/apps/web/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -0
  199. package/apps/web/.next/server/app/api/auth/logout/route.js +1 -0
  200. package/apps/web/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
  201. package/apps/web/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -0
  202. package/apps/web/.next/server/app/api/auth/me/route.js +1 -0
  203. package/apps/web/.next/server/app/api/auth/me/route.js.nft.json +1 -0
  204. package/apps/web/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -0
  205. package/apps/web/.next/server/app/api/auth/users/[id]/route.js +1 -0
  206. package/apps/web/.next/server/app/api/auth/users/[id]/route.js.nft.json +1 -0
  207. package/apps/web/.next/server/app/api/auth/users/[id]/route_client-reference-manifest.js +1 -0
  208. package/apps/web/.next/server/app/api/auth/users/route.js +1 -0
  209. package/apps/web/.next/server/app/api/auth/users/route.js.nft.json +1 -0
  210. package/apps/web/.next/server/app/api/auth/users/route_client-reference-manifest.js +1 -0
  211. package/apps/web/.next/server/app/api/env-vars/[key]/route.js +1 -0
  212. package/apps/web/.next/server/app/api/env-vars/[key]/route.js.nft.json +1 -0
  213. package/apps/web/.next/server/app/api/env-vars/[key]/route_client-reference-manifest.js +1 -0
  214. package/apps/web/.next/server/app/api/env-vars/route.js +1 -0
  215. package/apps/web/.next/server/app/api/env-vars/route.js.nft.json +1 -0
  216. package/apps/web/.next/server/app/api/env-vars/route_client-reference-manifest.js +1 -0
  217. package/apps/web/.next/server/app/api/jobs/[id]/route.js +1 -0
  218. package/apps/web/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -0
  219. package/apps/web/.next/server/app/api/jobs/[id]/route_client-reference-manifest.js +1 -0
  220. package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js +1 -0
  221. package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js.nft.json +1 -0
  222. package/apps/web/.next/server/app/api/jobs/[id]/runs/route_client-reference-manifest.js +1 -0
  223. package/apps/web/.next/server/app/api/jobs/route.js +1 -0
  224. package/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -0
  225. package/apps/web/.next/server/app/api/jobs/route_client-reference-manifest.js +1 -0
  226. package/apps/web/.next/server/app/api/mcps/[id]/route.js +1 -0
  227. package/apps/web/.next/server/app/api/mcps/[id]/route.js.nft.json +1 -0
  228. package/apps/web/.next/server/app/api/mcps/[id]/route_client-reference-manifest.js +1 -0
  229. package/apps/web/.next/server/app/api/mcps/[id]/test/route.js +1 -0
  230. package/apps/web/.next/server/app/api/mcps/[id]/test/route.js.nft.json +1 -0
  231. package/apps/web/.next/server/app/api/mcps/[id]/test/route_client-reference-manifest.js +1 -0
  232. package/apps/web/.next/server/app/api/mcps/route.js +1 -0
  233. package/apps/web/.next/server/app/api/mcps/route.js.nft.json +1 -0
  234. package/apps/web/.next/server/app/api/mcps/route_client-reference-manifest.js +1 -0
  235. package/apps/web/.next/server/app/api/settings/route.js +1 -0
  236. package/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -0
  237. package/apps/web/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  238. package/apps/web/.next/server/app/icon.svg/route.js +1 -0
  239. package/apps/web/.next/server/app/icon.svg/route.js.nft.json +1 -0
  240. package/apps/web/.next/server/app/jobs/page.js +2 -0
  241. package/apps/web/.next/server/app/jobs/page.js.nft.json +1 -0
  242. package/apps/web/.next/server/app/jobs/page_client-reference-manifest.js +1 -0
  243. package/apps/web/.next/server/app/login/page.js +2 -0
  244. package/apps/web/.next/server/app/login/page.js.nft.json +1 -0
  245. package/apps/web/.next/server/app/login/page_client-reference-manifest.js +1 -0
  246. package/apps/web/.next/server/app/page.js +2 -0
  247. package/apps/web/.next/server/app/page.js.nft.json +1 -0
  248. package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -0
  249. package/apps/web/.next/server/app/settings/env-vars/page.js +2 -0
  250. package/apps/web/.next/server/app/settings/env-vars/page.js.nft.json +1 -0
  251. package/apps/web/.next/server/app/settings/env-vars/page_client-reference-manifest.js +1 -0
  252. package/apps/web/.next/server/app/settings/mcps/page.js +2 -0
  253. package/apps/web/.next/server/app/settings/mcps/page.js.nft.json +1 -0
  254. package/apps/web/.next/server/app/settings/mcps/page_client-reference-manifest.js +1 -0
  255. package/apps/web/.next/server/app/settings/page.js +2 -0
  256. package/apps/web/.next/server/app/settings/page.js.nft.json +1 -0
  257. package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -0
  258. package/apps/web/.next/server/app-paths-manifest.json +46 -0
  259. package/apps/web/.next/server/chunks/1157.js +9 -0
  260. package/apps/web/.next/server/chunks/2287.js +1 -0
  261. package/apps/web/.next/server/chunks/3444.js +1 -0
  262. package/apps/web/.next/server/chunks/383.js +6 -0
  263. package/apps/web/.next/server/chunks/4012.js +58 -0
  264. package/apps/web/.next/server/chunks/6791.js +1 -0
  265. package/apps/web/.next/server/chunks/7171.js +1 -0
  266. package/apps/web/.next/server/chunks/8819.js +22 -0
  267. package/apps/web/.next/server/edge-runtime-webpack.js +2 -0
  268. package/apps/web/.next/server/edge-runtime-webpack.js.map +1 -0
  269. package/apps/web/.next/server/interception-route-rewrite-manifest.js +1 -0
  270. package/apps/web/.next/server/middleware-build-manifest.js +1 -0
  271. package/apps/web/.next/server/middleware-manifest.json +32 -0
  272. package/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  273. package/apps/web/.next/server/next-font-manifest.js +1 -0
  274. package/apps/web/.next/server/next-font-manifest.json +1 -0
  275. package/apps/web/.next/server/pages/_app.js +1 -0
  276. package/apps/web/.next/server/pages/_app.js.nft.json +1 -0
  277. package/apps/web/.next/server/pages/_document.js +1 -0
  278. package/apps/web/.next/server/pages/_document.js.nft.json +1 -0
  279. package/apps/web/.next/server/pages/_error.js +19 -0
  280. package/apps/web/.next/server/pages/_error.js.nft.json +1 -0
  281. package/apps/web/.next/server/pages-manifest.json +5 -0
  282. package/apps/web/.next/server/server-reference-manifest.js +1 -0
  283. package/apps/web/.next/server/server-reference-manifest.json +1 -0
  284. package/apps/web/.next/server/src/middleware.js +14 -0
  285. package/apps/web/.next/server/src/middleware.js.map +1 -0
  286. package/apps/web/.next/server/webpack-runtime.js +1 -0
  287. package/apps/web/.next/static/chunks/18-90b700ea37b686a2.js +1 -0
  288. package/apps/web/.next/static/chunks/87c73c54-24122e7b92478d00.js +1 -0
  289. package/apps/web/.next/static/chunks/9664-af80478aa73ba424.js +1 -0
  290. package/apps/web/.next/static/chunks/app/_not-found/page-b9cee17ed89ca24a.js +1 -0
  291. package/apps/web/.next/static/chunks/app/agents/[slug]/page-18369fc3fe1a9a7b.js +1 -0
  292. package/apps/web/.next/static/chunks/app/agents/new/page-bf11cf8901c7e2cd.js +1 -0
  293. package/apps/web/.next/static/chunks/app/api/agents/[id]/access/route-07f0f73ac9839899.js +1 -0
  294. package/apps/web/.next/static/chunks/app/api/agents/[id]/claude-md/route-07f0f73ac9839899.js +1 -0
  295. package/apps/web/.next/static/chunks/app/api/agents/[id]/logs/route-07f0f73ac9839899.js +1 -0
  296. package/apps/web/.next/static/chunks/app/api/agents/[id]/manifest/route-07f0f73ac9839899.js +1 -0
  297. package/apps/web/.next/static/chunks/app/api/agents/[id]/mcps/route-07f0f73ac9839899.js +1 -0
  298. package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/[memId]/route-07f0f73ac9839899.js +1 -0
  299. package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/route-07f0f73ac9839899.js +1 -0
  300. package/apps/web/.next/static/chunks/app/api/agents/[id]/permissions/route-07f0f73ac9839899.js +1 -0
  301. package/apps/web/.next/static/chunks/app/api/agents/[id]/reload/route-07f0f73ac9839899.js +1 -0
  302. package/apps/web/.next/static/chunks/app/api/agents/[id]/restrictions/route-07f0f73ac9839899.js +1 -0
  303. package/apps/web/.next/static/chunks/app/api/agents/[id]/route-07f0f73ac9839899.js +1 -0
  304. package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/[skillId]/route-07f0f73ac9839899.js +1 -0
  305. package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/route-07f0f73ac9839899.js +1 -0
  306. package/apps/web/.next/static/chunks/app/api/agents/[id]/slack-info/route-07f0f73ac9839899.js +1 -0
  307. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/restore/route-07f0f73ac9839899.js +1 -0
  308. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/route-07f0f73ac9839899.js +1 -0
  309. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/route-07f0f73ac9839899.js +1 -0
  310. package/apps/web/.next/static/chunks/app/api/agents/[id]/start/route-07f0f73ac9839899.js +1 -0
  311. package/apps/web/.next/static/chunks/app/api/agents/[id]/stop/route-07f0f73ac9839899.js +1 -0
  312. package/apps/web/.next/static/chunks/app/api/agents/route-07f0f73ac9839899.js +1 -0
  313. package/apps/web/.next/static/chunks/app/api/auth/login/route-07f0f73ac9839899.js +1 -0
  314. package/apps/web/.next/static/chunks/app/api/auth/logout/route-07f0f73ac9839899.js +1 -0
  315. package/apps/web/.next/static/chunks/app/api/auth/me/route-07f0f73ac9839899.js +1 -0
  316. package/apps/web/.next/static/chunks/app/api/auth/users/[id]/route-07f0f73ac9839899.js +1 -0
  317. package/apps/web/.next/static/chunks/app/api/auth/users/route-07f0f73ac9839899.js +1 -0
  318. package/apps/web/.next/static/chunks/app/api/env-vars/[key]/route-07f0f73ac9839899.js +1 -0
  319. package/apps/web/.next/static/chunks/app/api/env-vars/route-07f0f73ac9839899.js +1 -0
  320. package/apps/web/.next/static/chunks/app/api/jobs/[id]/route-07f0f73ac9839899.js +1 -0
  321. package/apps/web/.next/static/chunks/app/api/jobs/[id]/runs/route-07f0f73ac9839899.js +1 -0
  322. package/apps/web/.next/static/chunks/app/api/jobs/route-07f0f73ac9839899.js +1 -0
  323. package/apps/web/.next/static/chunks/app/api/mcps/[id]/route-07f0f73ac9839899.js +1 -0
  324. package/apps/web/.next/static/chunks/app/api/mcps/[id]/test/route-07f0f73ac9839899.js +1 -0
  325. package/apps/web/.next/static/chunks/app/api/mcps/route-07f0f73ac9839899.js +1 -0
  326. package/apps/web/.next/static/chunks/app/api/settings/route-07f0f73ac9839899.js +1 -0
  327. package/apps/web/.next/static/chunks/app/jobs/page-f5aa89a47c50efd8.js +1 -0
  328. package/apps/web/.next/static/chunks/app/layout-2079f4964aa7314e.js +1 -0
  329. package/apps/web/.next/static/chunks/app/login/layout-07f0f73ac9839899.js +1 -0
  330. package/apps/web/.next/static/chunks/app/login/page-aa259283dc38e8f9.js +1 -0
  331. package/apps/web/.next/static/chunks/app/page-e83437b608104dff.js +1 -0
  332. package/apps/web/.next/static/chunks/app/settings/env-vars/page-06479dbdfb78b76b.js +1 -0
  333. package/apps/web/.next/static/chunks/app/settings/mcps/page-75650686ed6490c7.js +1 -0
  334. package/apps/web/.next/static/chunks/app/settings/page-e1e62fc41ff6cddd.js +1 -0
  335. package/apps/web/.next/static/chunks/framework-811407f832a33072.js +1 -0
  336. package/apps/web/.next/static/chunks/main-3f1cddbdd67b1546.js +1 -0
  337. package/apps/web/.next/static/chunks/main-app-cebd8a6a5ccbf72d.js +1 -0
  338. package/apps/web/.next/static/chunks/pages/_app-50fa07b56b2d29ac.js +1 -0
  339. package/apps/web/.next/static/chunks/pages/_error-fed8688bdd23f211.js +1 -0
  340. package/apps/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  341. package/apps/web/.next/static/chunks/webpack-6c05566dba553c97.js +1 -0
  342. package/apps/web/.next/static/css/15371687405525e2.css +5 -0
  343. package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_buildManifest.js +1 -0
  344. package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_ssgManifest.js +1 -0
  345. package/apps/web/.next/trace +5 -0
  346. package/apps/web/.next/types/app/agents/[slug]/page.ts +84 -0
  347. package/apps/web/.next/types/app/agents/new/page.ts +84 -0
  348. package/apps/web/.next/types/app/api/agents/[id]/access/route.ts +347 -0
  349. package/apps/web/.next/types/app/api/agents/[id]/claude-md/route.ts +347 -0
  350. package/apps/web/.next/types/app/api/agents/[id]/logs/route.ts +347 -0
  351. package/apps/web/.next/types/app/api/agents/[id]/manifest/route.ts +347 -0
  352. package/apps/web/.next/types/app/api/agents/[id]/mcps/route.ts +347 -0
  353. package/apps/web/.next/types/app/api/agents/[id]/memories/[memId]/route.ts +347 -0
  354. package/apps/web/.next/types/app/api/agents/[id]/memories/route.ts +347 -0
  355. package/apps/web/.next/types/app/api/agents/[id]/permissions/route.ts +347 -0
  356. package/apps/web/.next/types/app/api/agents/[id]/reload/route.ts +347 -0
  357. package/apps/web/.next/types/app/api/agents/[id]/restrictions/route.ts +347 -0
  358. package/apps/web/.next/types/app/api/agents/[id]/route.ts +347 -0
  359. package/apps/web/.next/types/app/api/agents/[id]/skills/[skillId]/route.ts +347 -0
  360. package/apps/web/.next/types/app/api/agents/[id]/skills/route.ts +347 -0
  361. package/apps/web/.next/types/app/api/agents/[id]/slack-info/route.ts +347 -0
  362. package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +347 -0
  363. package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/route.ts +347 -0
  364. package/apps/web/.next/types/app/api/agents/[id]/snapshots/route.ts +347 -0
  365. package/apps/web/.next/types/app/api/agents/[id]/start/route.ts +347 -0
  366. package/apps/web/.next/types/app/api/agents/[id]/stop/route.ts +347 -0
  367. package/apps/web/.next/types/app/api/agents/route.ts +347 -0
  368. package/apps/web/.next/types/app/api/auth/login/route.ts +347 -0
  369. package/apps/web/.next/types/app/api/auth/logout/route.ts +347 -0
  370. package/apps/web/.next/types/app/api/auth/me/route.ts +347 -0
  371. package/apps/web/.next/types/app/api/auth/users/[id]/route.ts +347 -0
  372. package/apps/web/.next/types/app/api/auth/users/route.ts +347 -0
  373. package/apps/web/.next/types/app/api/env-vars/[key]/route.ts +347 -0
  374. package/apps/web/.next/types/app/api/env-vars/route.ts +347 -0
  375. package/apps/web/.next/types/app/api/jobs/[id]/route.ts +347 -0
  376. package/apps/web/.next/types/app/api/jobs/[id]/runs/route.ts +347 -0
  377. package/apps/web/.next/types/app/api/jobs/route.ts +347 -0
  378. package/apps/web/.next/types/app/api/mcps/[id]/route.ts +347 -0
  379. package/apps/web/.next/types/app/api/mcps/[id]/test/route.ts +347 -0
  380. package/apps/web/.next/types/app/api/mcps/route.ts +347 -0
  381. package/apps/web/.next/types/app/api/settings/route.ts +347 -0
  382. package/apps/web/.next/types/app/jobs/page.ts +84 -0
  383. package/apps/web/.next/types/app/login/layout.ts +84 -0
  384. package/apps/web/.next/types/app/login/page.ts +84 -0
  385. package/apps/web/.next/types/app/page.ts +84 -0
  386. package/apps/web/.next/types/app/settings/env-vars/page.ts +84 -0
  387. package/apps/web/.next/types/app/settings/mcps/page.ts +84 -0
  388. package/apps/web/.next/types/app/settings/page.ts +84 -0
  389. package/apps/web/.next/types/cache-life.d.ts +141 -0
  390. package/apps/web/.next/types/package.json +1 -0
  391. package/apps/web/.next/types/routes.d.ts +114 -0
  392. package/apps/web/.next/types/validator.ts +448 -0
  393. package/apps/web/Dockerfile +37 -0
  394. package/apps/web/next-env.d.ts +6 -0
  395. package/apps/web/next.config.js +6 -0
  396. package/apps/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  397. package/apps/web/package.json +48 -0
  398. package/apps/web/postcss.config.js +3 -0
  399. package/apps/web/public/logo.svg +17 -0
  400. package/apps/web/src/app/agents/[slug]/page.tsx +2235 -0
  401. package/apps/web/src/app/agents/new/page.tsx +1161 -0
  402. package/apps/web/src/app/api/agents/[id]/access/route.ts +76 -0
  403. package/apps/web/src/app/api/agents/[id]/claude-md/route.ts +111 -0
  404. package/apps/web/src/app/api/agents/[id]/logs/route.ts +84 -0
  405. package/apps/web/src/app/api/agents/[id]/manifest/route.ts +32 -0
  406. package/apps/web/src/app/api/agents/[id]/mcps/route.ts +73 -0
  407. package/apps/web/src/app/api/agents/[id]/memories/[memId]/route.ts +31 -0
  408. package/apps/web/src/app/api/agents/[id]/memories/route.ts +56 -0
  409. package/apps/web/src/app/api/agents/[id]/permissions/route.ts +74 -0
  410. package/apps/web/src/app/api/agents/[id]/reload/route.ts +33 -0
  411. package/apps/web/src/app/api/agents/[id]/restrictions/route.ts +85 -0
  412. package/apps/web/src/app/api/agents/[id]/route.ts +81 -0
  413. package/apps/web/src/app/api/agents/[id]/skills/[skillId]/route.ts +52 -0
  414. package/apps/web/src/app/api/agents/[id]/skills/route.ts +80 -0
  415. package/apps/web/src/app/api/agents/[id]/slack-info/route.ts +38 -0
  416. package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +61 -0
  417. package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/route.ts +53 -0
  418. package/apps/web/src/app/api/agents/[id]/snapshots/route.ts +84 -0
  419. package/apps/web/src/app/api/agents/[id]/start/route.ts +35 -0
  420. package/apps/web/src/app/api/agents/[id]/stop/route.ts +35 -0
  421. package/apps/web/src/app/api/agents/route.ts +99 -0
  422. package/apps/web/src/app/api/auth/login/route.ts +39 -0
  423. package/apps/web/src/app/api/auth/logout/route.ts +21 -0
  424. package/apps/web/src/app/api/auth/me/route.ts +24 -0
  425. package/apps/web/src/app/api/auth/users/[id]/route.ts +48 -0
  426. package/apps/web/src/app/api/auth/users/route.ts +63 -0
  427. package/apps/web/src/app/api/env-vars/[key]/route.ts +66 -0
  428. package/apps/web/src/app/api/env-vars/route.ts +59 -0
  429. package/apps/web/src/app/api/jobs/[id]/route.ts +51 -0
  430. package/apps/web/src/app/api/jobs/[id]/runs/route.ts +24 -0
  431. package/apps/web/src/app/api/jobs/route.ts +42 -0
  432. package/apps/web/src/app/api/mcps/[id]/route.ts +60 -0
  433. package/apps/web/src/app/api/mcps/[id]/test/route.ts +195 -0
  434. package/apps/web/src/app/api/mcps/route.ts +72 -0
  435. package/apps/web/src/app/api/settings/route.ts +42 -0
  436. package/apps/web/src/app/globals.css +124 -0
  437. package/apps/web/src/app/icon.svg +17 -0
  438. package/apps/web/src/app/jobs/page.tsx +543 -0
  439. package/apps/web/src/app/layout-shell.tsx +89 -0
  440. package/apps/web/src/app/layout.tsx +18 -0
  441. package/apps/web/src/app/login/layout.tsx +9 -0
  442. package/apps/web/src/app/login/page.tsx +150 -0
  443. package/apps/web/src/app/page.tsx +573 -0
  444. package/apps/web/src/app/settings/env-vars/page.tsx +216 -0
  445. package/apps/web/src/app/settings/mcps/page.tsx +763 -0
  446. package/apps/web/src/app/settings/page.tsx +528 -0
  447. package/apps/web/src/app/sidebar.tsx +345 -0
  448. package/apps/web/src/lib/__tests__/api-guard.test.ts +189 -0
  449. package/apps/web/src/lib/__tests__/auth.test.ts +262 -0
  450. package/apps/web/src/lib/__tests__/boss-registry.test.ts +323 -0
  451. package/apps/web/src/lib/__tests__/compile.test.ts +161 -0
  452. package/apps/web/src/lib/__tests__/db-agent-hierarchy.test.ts +136 -0
  453. package/apps/web/src/lib/__tests__/db-env-vars.test.ts +216 -0
  454. package/apps/web/src/lib/__tests__/db-restrictions.test.ts +117 -0
  455. package/apps/web/src/lib/__tests__/db.integration.test.ts +271 -0
  456. package/apps/web/src/lib/__tests__/diff.test.ts +102 -0
  457. package/apps/web/src/lib/__tests__/mcp-mask.test.ts +274 -0
  458. package/apps/web/src/lib/__tests__/skill-templates.test.ts +237 -0
  459. package/apps/web/src/lib/__tests__/slack-manifest.test.ts +105 -0
  460. package/apps/web/src/lib/api-guard.ts +68 -0
  461. package/apps/web/src/lib/auth-context.tsx +71 -0
  462. package/apps/web/src/lib/auth.ts +128 -0
  463. package/apps/web/src/lib/boss-registry.ts +90 -0
  464. package/apps/web/src/lib/compile.ts +51 -0
  465. package/apps/web/src/lib/db.ts +1196 -0
  466. package/apps/web/src/lib/diff.ts +43 -0
  467. package/apps/web/src/lib/mcp-mask.ts +91 -0
  468. package/apps/web/src/lib/portal.tsx +23 -0
  469. package/apps/web/src/lib/skill-templates.ts +148 -0
  470. package/apps/web/src/lib/slack-manifest.ts +85 -0
  471. package/apps/web/src/middleware.ts +68 -0
  472. package/apps/web/tailwind.config.js +6 -0
  473. package/apps/web/tsconfig.json +23 -0
  474. package/apps/web/vitest.config.mts +21 -0
  475. package/cli/.claude/settings.local.json +6 -0
  476. package/cli/README.md +281 -0
  477. package/cli/node_modules/.package-lock.json +427 -0
  478. package/cli/node_modules/commander/LICENSE +22 -0
  479. package/cli/node_modules/commander/Readme.md +1157 -0
  480. package/cli/node_modules/commander/esm.mjs +16 -0
  481. package/cli/node_modules/commander/index.js +24 -0
  482. package/cli/node_modules/commander/lib/argument.js +149 -0
  483. package/cli/node_modules/commander/lib/command.js +2509 -0
  484. package/cli/node_modules/commander/lib/error.js +39 -0
  485. package/cli/node_modules/commander/lib/help.js +520 -0
  486. package/cli/node_modules/commander/lib/option.js +330 -0
  487. package/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
  488. package/cli/node_modules/commander/package-support.json +16 -0
  489. package/cli/node_modules/commander/package.json +84 -0
  490. package/cli/node_modules/commander/typings/esm.d.mts +3 -0
  491. package/cli/node_modules/commander/typings/index.d.ts +969 -0
  492. package/cli/package-lock.json +449 -0
  493. package/cli/package.json +44 -0
  494. package/cli/src/commands/init.ts +514 -0
  495. package/cli/src/commands/manage.ts +115 -0
  496. package/cli/src/index.ts +63 -0
  497. package/cli/tsconfig.json +14 -0
  498. package/docker-compose.yml +122 -0
  499. package/docs/agents/boss-agents.mdx +108 -0
  500. package/docs/agents/creating-agents.mdx +132 -0
  501. package/docs/agents/memory.mdx +113 -0
  502. package/docs/agents/tools.mdx +103 -0
  503. package/docs/configuration/env-vars.mdx +166 -0
  504. package/docs/configuration/mcp-servers.mdx +203 -0
  505. package/docs/configuration/slack-app.mdx +175 -0
  506. package/docs/docs.json +79 -0
  507. package/docs/favicon.svg +17 -0
  508. package/docs/features/history.mdx +60 -0
  509. package/docs/features/import-export.mdx +77 -0
  510. package/docs/features/logs.mdx +131 -0
  511. package/docs/features/multi-workspace.mdx +90 -0
  512. package/docs/features/scheduled-jobs.mdx +231 -0
  513. package/docs/features/users.mdx +92 -0
  514. package/docs/introduction.mdx +160 -0
  515. package/docs/logo/dark.svg +17 -0
  516. package/docs/logo/light.svg +17 -0
  517. package/docs/logo/wide-dark.svg +12 -0
  518. package/docs/logo/wide-light.svg +12 -0
  519. package/docs/quickstart.mdx +270 -0
  520. package/docs/self-hosting/docker.mdx +151 -0
  521. package/docs/self-hosting/production.mdx +176 -0
  522. package/package.json +20 -36
  523. package/packages/shared/dist/index.d.ts +8 -0
  524. package/packages/shared/dist/index.d.ts.map +1 -0
  525. package/packages/shared/dist/index.js +24 -0
  526. package/packages/shared/dist/index.js.map +1 -0
  527. package/packages/shared/dist/types.d.ts +584 -0
  528. package/packages/shared/dist/types.d.ts.map +1 -0
  529. package/packages/shared/dist/types.js +39 -0
  530. package/packages/shared/dist/types.js.map +1 -0
  531. package/packages/shared/package.json +15 -0
  532. package/packages/shared/src/db/schema.sql +354 -0
  533. package/packages/shared/src/index.ts +8 -0
  534. package/packages/shared/src/types.ts +683 -0
  535. package/packages/shared/tsconfig.json +17 -0
  536. package/scripts/dev.sh +45 -0
  537. /package/{dist → cli/dist}/commands/init.d.ts +0 -0
  538. /package/{dist → cli/dist}/commands/init.js +0 -0
  539. /package/{dist → cli/dist}/commands/manage.d.ts +0 -0
  540. /package/{dist → cli/dist}/commands/manage.js +0 -0
  541. /package/{dist → cli/dist}/index.d.ts +0 -0
  542. /package/{dist → cli/dist}/index.js +0 -0
@@ -0,0 +1,843 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Slack event handler for a single agent's Bolt App.
4
+ *
5
+ * Key behaviours:
6
+ * - Abort/cancel: new message in same thread cancels the in-flight request
7
+ * - Tool status: shows friendly "Querying Redshift…" live in the status message
8
+ * - Fallback text: uses lastAssistantText if no messages were sent during stream
9
+ * - result message: extracts final result from SDK result.subtype === 'success'
10
+ * - Reaction cycling: thinking_face → gear → white_check_mark / x
11
+ * - Markdown→Slack formatting: **bold** → *bold*, headings, tables, code blocks
12
+ * - Block Kit: tables rendered as native Slack table blocks with section chunks
13
+ *
14
+ * @module runner/slack-handler
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.registerSlackHandlers = registerSlackHandlers;
18
+ exports.isChannelRestricted = isChannelRestricted;
19
+ exports.buildMessagePayloads = buildMessagePayloads;
20
+ exports.formatMessage = formatMessage;
21
+ exports.splitTextForBlocks = splitTextForBlocks;
22
+ exports.isSeparatorLine = isSeparatorLine;
23
+ exports.extractFirstMarkdownTable = extractFirstMarkdownTable;
24
+ exports.parseMarkdownTable = parseMarkdownTable;
25
+ exports.buildSlackTableBlock = buildSlackTableBlock;
26
+ exports.formatToolStatus = formatToolStatus;
27
+ exports.getFileKind = getFileKind;
28
+ exports.downloadFile = downloadFile;
29
+ exports.buildPrompt = buildPrompt;
30
+ exports.stripBotMention = stripBotMention;
31
+ const correction_handler_1 = require("./correction-handler");
32
+ const logger_1 = require("./logger");
33
+ const MAX_THREAD_CONTEXT_MESSAGES = 20;
34
+ const MAX_THREAD_CONTEXT_CHARS = 8_000;
35
+ /** Friendly labels shown in the status message while a tool is running. */
36
+ const MCP_TOOL_LABELS = {
37
+ 'mcp__redshift-mcp__query': 'Querying Redshift',
38
+ 'mcp__redshift-mcp__describe_table': 'Inspecting table structure',
39
+ 'mcp__redshift-mcp__find_column': 'Searching for columns',
40
+ 'mcp__mcp-server-openmetadata-PRD__search_entities': 'Searching metadata catalog',
41
+ 'mcp__mcp-server-openmetadata-PRD__suggest_entities': 'Looking up suggestions',
42
+ 'mcp__mcp-server-openmetadata-PRD__get_table_by_name': 'Getting table details',
43
+ 'mcp__mcp-server-openmetadata-PRD__get_table': 'Getting table details',
44
+ 'mcp__mcp-server-openmetadata-PRD__list_tables': 'Listing tables',
45
+ 'mcp__mcp-server-openmetadata-PRD__get_metric': 'Getting metric definition',
46
+ 'mcp__mcp-server-openmetadata-PRD__get_metric_by_name': 'Getting metric definition',
47
+ 'mcp__mcp-server-openmetadata-PRD__list_metrics': 'Listing metrics',
48
+ 'mcp__mcp-server-openmetadata-PRD__get_glossary': 'Getting glossary',
49
+ 'mcp__mcp-server-openmetadata-PRD__get_glossary_by_name': 'Getting glossary',
50
+ 'mcp__mcp-server-openmetadata-PRD__list_glossaries': 'Listing glossaries',
51
+ 'mcp__mcp-server-openmetadata-PRD__get_glossary_term': 'Getting glossary term',
52
+ 'mcp__mcp-server-openmetadata-PRD__list_glossary_terms': 'Listing glossary terms',
53
+ 'mcp__mcp-server-openmetadata-PRD__get_schema': 'Getting schema info',
54
+ 'mcp__mcp-server-openmetadata-PRD__get_schema_by_name': 'Getting schema info',
55
+ 'mcp__mcp-server-openmetadata-PRD__list_schemas': 'Listing schemas',
56
+ 'mcp__mcp-server-openmetadata-PRD__get_database': 'Getting database info',
57
+ 'mcp__mcp-server-openmetadata-PRD__get_database_by_name': 'Getting database info',
58
+ 'mcp__mcp-server-openmetadata-PRD__list_databases': 'Listing databases',
59
+ 'mcp__mcp-server-openmetadata-PRD__get_lineage': 'Getting data lineage',
60
+ 'mcp__mcp-server-openmetadata-PRD__get_lineage_by_name': 'Getting data lineage',
61
+ 'mcp__mcp-server-openmetadata-PRD__search_field_query': 'Searching fields',
62
+ 'mcp__mcp-server-openmetadata-PRD__search_aggregate': 'Searching aggregations',
63
+ 'mcp__mcp-server-openmetadata-PRD__get_tag': 'Getting tag info',
64
+ 'mcp__mcp-server-openmetadata-PRD__get_tag_by_name': 'Getting tag info',
65
+ 'mcp__mcp-server-openmetadata-PRD__list_tags': 'Listing tags',
66
+ 'mcp__mcp-server-openmetadata-PRD__get_classification': 'Getting classification',
67
+ 'mcp__mcp-server-openmetadata-PRD__list_classifications': 'Listing classifications',
68
+ 'mcp__mcp-server-openmetadata-PRD__get_usage_by_entity': 'Getting usage stats',
69
+ 'mcp__mcp-server-openmetadata-PRD__get_entity_usage_summary': 'Getting usage summary',
70
+ 'mcp__mcp-server-openmetadata-PRD__get_data_quality_report': 'Getting data quality report',
71
+ };
72
+ /**
73
+ * Registers all Slack event handlers for a single agent's Bolt App.
74
+ *
75
+ * Handles:
76
+ * - `app_mention` — responds when mentioned in a channel
77
+ * - `message` — responds to direct messages
78
+ * - `member_joined_channel` — posts a welcome message when added to a channel
79
+ *
80
+ * @param {App} app - The Slack Bolt App instance for this agent.
81
+ * @param {Agent} agent - The agent configuration record.
82
+ * @param {ClaudeHandler} claudeHandler - The Claude SDK session manager.
83
+ * @returns {void}
84
+ */
85
+ function registerSlackHandlers(app, agent, claudeHandler, restrictions = null) {
86
+ const log = (0, logger_1.agentLogger)(agent.slug);
87
+ const correctionHandler = new correction_handler_1.CorrectionHandler(agent);
88
+ /** Track in-flight abort controllers per session so new messages cancel old ones. */
89
+ const activeControllers = new Map();
90
+ /** Track current emoji reaction per session to avoid duplicate add calls. */
91
+ const currentReactions = new Map();
92
+ /**
93
+ * Swaps the emoji reaction on a message without leaving duplicate reactions.
94
+ * Removes the current reaction (if any) before adding the new one.
95
+ * Failures are silently ignored as reactions are non-critical UI feedback.
96
+ *
97
+ * @param {WebClient} client - Slack Web API client.
98
+ * @param {string} channelId - Slack channel ID.
99
+ * @param {string} messageTs - Timestamp of the message to react to.
100
+ * @param {string} sessionKey - Session key used to track current reaction.
101
+ * @param {string} emoji - Emoji name to set (without colons).
102
+ * @returns {Promise<void>}
103
+ */
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ async function updateReaction(client, channelId, messageTs, sessionKey, emoji) {
106
+ const current = currentReactions.get(sessionKey);
107
+ if (current === emoji)
108
+ return;
109
+ try {
110
+ if (current) {
111
+ await client.reactions.remove({ channel: channelId, timestamp: messageTs, name: current }).catch(() => { });
112
+ }
113
+ await client.reactions.add({ channel: channelId, timestamp: messageTs, name: emoji });
114
+ currentReactions.set(sessionKey, emoji);
115
+ }
116
+ catch { /* non-fatal */ }
117
+ }
118
+ app.event('app_mention', async ({ event, client }) => {
119
+ await handleMessage({
120
+ app, agent, claudeHandler, correctionHandler, client, log,
121
+ activeControllers, currentReactions, updateReaction,
122
+ userId: event.user ?? 'unknown',
123
+ channelId: event.channel,
124
+ threadTs: event.thread_ts ?? event.ts,
125
+ messageTs: event.ts,
126
+ rawText: event.text ?? '',
127
+ files: event.files ?? [],
128
+ restrictions,
129
+ });
130
+ });
131
+ app.message(async ({ message, client }) => {
132
+ const msg = message;
133
+ if (!('channel' in msg) || !msg.channel?.startsWith('D'))
134
+ return;
135
+ if (!('user' in msg))
136
+ return;
137
+ await handleMessage({
138
+ app, agent, claudeHandler, correctionHandler, client, log,
139
+ activeControllers, currentReactions, updateReaction,
140
+ userId: msg.user,
141
+ channelId: msg.channel,
142
+ threadTs: msg.thread_ts,
143
+ messageTs: msg.ts,
144
+ rawText: msg.text ?? '',
145
+ files: msg.files ?? [],
146
+ restrictions,
147
+ });
148
+ });
149
+ app.event('member_joined_channel', async ({ event, client }) => {
150
+ if (!agent.slackBotUserId || event.user !== agent.slackBotUserId)
151
+ return;
152
+ // If the bot joined a restricted channel, post a notice and leave
153
+ if (isChannelRestricted(event.channel, restrictions)) {
154
+ try {
155
+ await client.chat.postMessage({
156
+ channel: event.channel,
157
+ text: `Sorry, I'm only configured to operate in specific channels. Please contact an admin if you'd like to add me here.`,
158
+ });
159
+ await client.conversations.leave({ channel: event.channel });
160
+ }
161
+ catch { /* non-fatal */ }
162
+ return;
163
+ }
164
+ try {
165
+ await client.chat.postMessage({
166
+ channel: event.channel,
167
+ text: `👋 Hi! I'm *${agent.name}*. ${agent.description ?? ''}\n\nMention me to get started.`,
168
+ });
169
+ }
170
+ catch { /* non-fatal */ }
171
+ });
172
+ }
173
+ /**
174
+ * Returns true if the channel is blocked by the agent's restrictions.
175
+ * If restrictions is null or allowedChannels is empty, the channel is allowed.
176
+ *
177
+ * @param {string} channelId - Slack channel ID of the incoming message.
178
+ * @param {Restriction | null} restrictions - Agent's restriction config.
179
+ * @returns {boolean} True if the message should be silently ignored.
180
+ */
181
+ function isChannelRestricted(channelId, restrictions) {
182
+ if (!restrictions || restrictions.allowedChannels.length === 0)
183
+ return false;
184
+ return !restrictions.allowedChannels.includes(channelId);
185
+ }
186
+ async function handleMessage(opts) {
187
+ const { app, agent, claudeHandler, correctionHandler, client, log, activeControllers, currentReactions, updateReaction, userId, channelId, threadTs, messageTs, rawText, files, restrictions } = opts;
188
+ const userText = stripBotMention(rawText, agent.slackBotUserId).trim();
189
+ if (!userText && (!files || files.length === 0))
190
+ return;
191
+ // Silently ignore messages from channels not in the allowed list
192
+ if (isChannelRestricted(channelId, restrictions))
193
+ return;
194
+ // Route correction/help commands before normal processing
195
+ // Commands use agent slug prefix: {slug}:correct, {slug}:corrections, {slug}:help
196
+ if (correctionHandler.isCommand(userText)) {
197
+ await correctionHandler.handle({ userId, channelId, threadTs, messageTs }, userText, client);
198
+ return;
199
+ }
200
+ const sessionKey = claudeHandler.getSessionKey(userId, channelId, threadTs);
201
+ log.info('Processing message', { userId, channelId, threadTs, sessionKey, textLength: userText.length });
202
+ // Abort any in-flight request for this session (user sent a new message)
203
+ activeControllers.get(sessionKey)?.abort();
204
+ const abortController = new AbortController();
205
+ activeControllers.set(sessionKey, abortController);
206
+ // Thinking reaction + initial status message
207
+ await updateReaction(client, channelId, messageTs, sessionKey, 'thinking_face');
208
+ let statusTs;
209
+ try {
210
+ const posted = await client.chat.postMessage({ channel: channelId, thread_ts: threadTs, text: '*Thinking...*' });
211
+ statusTs = posted.ts;
212
+ }
213
+ catch (err) {
214
+ log.error('Failed to post status message', { error: err });
215
+ return;
216
+ }
217
+ const prompt = await buildPrompt(client, channelId, threadTs, userText, agent, log, files);
218
+ let sentMessages = [];
219
+ let lastAssistantText = null;
220
+ let lastToolResultText = null;
221
+ try {
222
+ for await (const message of claudeHandler.streamQuery(prompt, sessionKey, abortController)) {
223
+ if (abortController.signal.aborted)
224
+ break;
225
+ if (message.type === 'assistant') {
226
+ const content = message.message?.content ?? [];
227
+ const hasToolUse = content.some((b) => b.type === 'tool_use');
228
+ // Extract text blocks
229
+ const textContent = content.filter((b) => b.type === 'text').map((b) => b.text).join('');
230
+ if (textContent)
231
+ lastAssistantText = textContent;
232
+ if (hasToolUse) {
233
+ // Show live tool status in the status message
234
+ await updateReaction(client, channelId, messageTs, sessionKey, 'gear');
235
+ const toolStatus = formatToolStatus(content);
236
+ if (statusTs && toolStatus) {
237
+ await client.chat.update({ channel: channelId, ts: statusTs, text: toolStatus }).catch(() => { });
238
+ }
239
+ }
240
+ else if (textContent) {
241
+ // Text-only assistant message — send immediately
242
+ sentMessages.push(textContent);
243
+ // Extract code blocks for file upload, send text without them
244
+ const { textWithoutCode, codeBlocks } = extractCodeBlocks(textContent);
245
+ const displayText = codeBlocks.length > 0 ? textWithoutCode.trim() : textContent;
246
+ if (displayText) {
247
+ for (const payload of buildMessagePayloads(displayText, false)) {
248
+ await postMessageWithFallback(client, {
249
+ channel: channelId,
250
+ thread_ts: threadTs,
251
+ text: payload.text,
252
+ ...(payload.blocks && { blocks: payload.blocks }),
253
+ }, log);
254
+ }
255
+ }
256
+ // Upload code blocks as downloadable file snippets
257
+ if (codeBlocks.length > 0) {
258
+ await uploadCodeSnippets(client, codeBlocks, channelId, threadTs);
259
+ }
260
+ }
261
+ }
262
+ else if (message.type === 'user') {
263
+ // Capture tool result text for fallback
264
+ const userContent = message.message?.content;
265
+ if (Array.isArray(userContent)) {
266
+ for (const part of userContent) {
267
+ if (part.type === 'tool_result' && typeof part.content === 'string' && part.content.length > 0) {
268
+ lastToolResultText = part.content;
269
+ }
270
+ else if (part.type === 'tool_result' && Array.isArray(part.content)) {
271
+ const textParts = part.content.filter((p) => p.type === 'text').map((p) => p.text);
272
+ if (textParts.length > 0)
273
+ lastToolResultText = textParts.join('');
274
+ }
275
+ }
276
+ }
277
+ }
278
+ else if (message.type === 'result') {
279
+ log.info('Query completed', {
280
+ cost: message.total_cost_usd,
281
+ duration_ms: message.duration_ms,
282
+ status: message.subtype,
283
+ num_turns: message.num_turns,
284
+ });
285
+ if (message.subtype === 'success') {
286
+ const finalResult = message.result;
287
+ if (finalResult && !sentMessages.includes(finalResult)) {
288
+ sentMessages.push(finalResult);
289
+ // Extract code blocks for file upload
290
+ const { textWithoutCode, codeBlocks } = extractCodeBlocks(finalResult);
291
+ const displayText = codeBlocks.length > 0 ? textWithoutCode.trim() : finalResult;
292
+ if (displayText) {
293
+ for (const payload of buildMessagePayloads(displayText, true)) {
294
+ await postMessageWithFallback(client, {
295
+ channel: channelId,
296
+ thread_ts: threadTs,
297
+ text: payload.text,
298
+ ...(payload.blocks && { blocks: payload.blocks }),
299
+ }, log);
300
+ }
301
+ }
302
+ // Upload code blocks as downloadable file snippets
303
+ if (codeBlocks.length > 0) {
304
+ await uploadCodeSnippets(client, codeBlocks, channelId, threadTs);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ // Fallback: if Claude produced no messages, use lastAssistantText or tool result
311
+ if (sentMessages.length === 0) {
312
+ const fallback = lastAssistantText ?? lastToolResultText ?? '_No response generated._';
313
+ log.info('No messages sent, using fallback', {
314
+ source: lastAssistantText ? 'lastAssistantText' : lastToolResultText ? 'lastToolResultText' : 'default',
315
+ });
316
+ for (const payload of buildMessagePayloads(fallback, true)) {
317
+ await postMessageWithFallback(client, {
318
+ channel: channelId,
319
+ thread_ts: threadTs,
320
+ text: payload.text,
321
+ ...(payload.blocks && { blocks: payload.blocks }),
322
+ }, log);
323
+ }
324
+ }
325
+ // Update status message to Done and set ✅ reaction
326
+ if (statusTs) {
327
+ await client.chat.update({ channel: channelId, ts: statusTs, text: '*Done*' }).catch(() => { });
328
+ }
329
+ await updateReaction(client, channelId, messageTs, sessionKey, 'white_check_mark');
330
+ }
331
+ catch (error) {
332
+ if (error?.name === 'AbortError') {
333
+ log.debug('Request aborted', { sessionKey });
334
+ if (statusTs)
335
+ await client.chat.update({ channel: channelId, ts: statusTs, text: '*Cancelled*' }).catch(() => { });
336
+ await updateReaction(client, channelId, messageTs, sessionKey, 'stop_button');
337
+ }
338
+ else {
339
+ log.error('Error streaming Claude response', { sessionKey, error: error?.message });
340
+ const errText = `❌ Something went wrong. Please try again.\n\`${error?.message ?? 'Unknown error'}\``;
341
+ if (statusTs)
342
+ await client.chat.update({ channel: channelId, ts: statusTs, text: errText }).catch(() => { });
343
+ await updateReaction(client, channelId, messageTs, sessionKey, 'x');
344
+ }
345
+ }
346
+ finally {
347
+ activeControllers.delete(sessionKey);
348
+ setTimeout(() => currentReactions.delete(sessionKey), 5 * 60 * 1000);
349
+ }
350
+ }
351
+ // =============================================================================
352
+ // Slack posting helpers
353
+ // =============================================================================
354
+ /**
355
+ * Posts a message to Slack; if blocks are rejected with `invalid_blocks`,
356
+ * retries as plain text so the user still sees the response.
357
+ */
358
+ async function postMessageWithFallback(client, opts, log) {
359
+ try {
360
+ await client.chat.postMessage(opts);
361
+ }
362
+ catch (err) {
363
+ if (err?.data?.error === 'invalid_blocks' && opts.blocks) {
364
+ log.warn('Slack rejected blocks, falling back to plain text', {
365
+ error: err?.data?.error,
366
+ blockTypes: opts.blocks.map((b) => b.type),
367
+ textPreview: opts.text.slice(0, 200),
368
+ });
369
+ await client.chat.postMessage({ channel: opts.channel, thread_ts: opts.thread_ts, text: opts.text });
370
+ }
371
+ else {
372
+ throw err;
373
+ }
374
+ }
375
+ }
376
+ // =============================================================================
377
+ // Message formatting
378
+ // =============================================================================
379
+ /**
380
+ * Builds one or more Slack message payloads from Claude's response text.
381
+ * Each markdown table gets its own payload because Slack only supports
382
+ * one native table block per message.
383
+ *
384
+ * @param {string} text - Raw text from Claude.
385
+ * @param {boolean} isFinal - Whether this is the final message.
386
+ * @returns {{ text: string; blocks?: any[] }[]} Array of Slack-ready payloads.
387
+ */
388
+ function buildMessagePayloads(text, isFinal) {
389
+ const payloads = [];
390
+ let remaining = text;
391
+ while (remaining.trim()) {
392
+ const extracted = extractFirstMarkdownTable(remaining);
393
+ if (!extracted) {
394
+ payloads.push({ text: formatMessage(remaining, isFinal) });
395
+ break;
396
+ }
397
+ const parsed = parseMarkdownTable(extracted.tableLines);
398
+ if (parsed.headers.length === 0) {
399
+ payloads.push({ text: formatMessage(remaining, isFinal) });
400
+ break;
401
+ }
402
+ const blocks = [];
403
+ const beforeText = formatMessage(extracted.before.trim(), false);
404
+ if (beforeText) {
405
+ for (const chunk of splitTextForBlocks(beforeText)) {
406
+ blocks.push({ type: 'section', text: { type: 'mrkdwn', text: chunk } });
407
+ }
408
+ }
409
+ blocks.push(buildSlackTableBlock(parsed));
410
+ const fallback = formatMessage(extracted.before.trim() + '\n' + extracted.tableLines.join('\n'), false);
411
+ payloads.push({ text: fallback, blocks });
412
+ remaining = extracted.after;
413
+ }
414
+ return payloads.length > 0 ? payloads : [{ text: formatMessage(text, isFinal) }];
415
+ }
416
+ /**
417
+ * Formats markdown text for Slack mrkdwn:
418
+ * - Preserves code blocks as-is (strips language hints)
419
+ * - Converts headings → *bold*
420
+ * - Removes HR lines
421
+ * - **bold** → *bold*
422
+ * - __italic__ → _italic_
423
+ * - Auto-wraps bare markdown tables in code blocks
424
+ */
425
+ function formatMessage(text, _isFinal) {
426
+ const codeBlocks = [];
427
+ // Use a placeholder that cannot be matched by the __italic__ regex.
428
+ // \x00 is not present in normal text and breaks the /__([^_]+)__/ pattern.
429
+ let formatted = text.replace(/```[\s\S]*?```/g, (match) => {
430
+ codeBlocks.push(match);
431
+ return `\x00CB${codeBlocks.length - 1}\x00`;
432
+ });
433
+ // Auto-wrap bare markdown tables in code blocks
434
+ formatted = formatted.replace(/(?:^|\n)((?:[ \t]*\S.+\|.+[ \t]*\n?){2,})/g, (_match, tableBlock) => `\n\`\`\`\n${tableBlock.trim()}\n\`\`\`\n`);
435
+ formatted = formatted.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
436
+ formatted = formatted.replace(/^(?:---+|\*\*\*+|___+)\s*$/gm, '');
437
+ formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '*$1*');
438
+ formatted = formatted.replace(/__([^_]+)__/g, '_$1_');
439
+ // Restore code blocks (strip language hints)
440
+ formatted = formatted.replace(/\x00CB(\d+)\x00/g, (_, index) => {
441
+ const block = codeBlocks[parseInt(index)];
442
+ return block.replace(/^```\w+\n/, '```\n');
443
+ });
444
+ return formatted;
445
+ }
446
+ /** Splits text into ≤3000-char chunks for Slack section blocks. */
447
+ function splitTextForBlocks(text) {
448
+ const MAX = 3000;
449
+ if (text.length <= MAX)
450
+ return [text];
451
+ const chunks = [];
452
+ let remaining = text;
453
+ while (remaining.length > 0) {
454
+ if (remaining.length <= MAX) {
455
+ chunks.push(remaining);
456
+ break;
457
+ }
458
+ let splitAt = remaining.lastIndexOf('\n', MAX);
459
+ if (splitAt <= 0)
460
+ splitAt = MAX;
461
+ chunks.push(remaining.slice(0, splitAt));
462
+ remaining = remaining.slice(splitAt).replace(/^\n/, '');
463
+ }
464
+ return chunks;
465
+ }
466
+ function isSeparatorLine(line) {
467
+ return /^\s*\|?[-:\s|]+\|?\s*$/.test(line) && line.includes('-');
468
+ }
469
+ function extractFirstMarkdownTable(text) {
470
+ const codeBlockTableRe = /```(?:\w*)\n((?:[ \t]*.+\|.+[ \t]*\n?){2,})```/;
471
+ const bareTableRe = /(?:^|\n)((?:[ \t]*\|.+\|[ \t]*(?:\n|$)){2,})/;
472
+ const loosePipeRe = /(?:^|\n)((?:[ \t]*\S.+\|.+(?:\n|$)){2,})/;
473
+ const candidates = [];
474
+ const cbMatch = codeBlockTableRe.exec(text);
475
+ const bareMatch = bareTableRe.exec(text);
476
+ const looseMatch = loosePipeRe.exec(text);
477
+ if (cbMatch)
478
+ candidates.push({ match: cbMatch, content: cbMatch[1] });
479
+ if (bareMatch)
480
+ candidates.push({ match: bareMatch, content: bareMatch[1] });
481
+ if (looseMatch)
482
+ candidates.push({ match: looseMatch, content: looseMatch[1] });
483
+ candidates.sort((a, b) => a.match.index - b.match.index);
484
+ for (const { match, content } of candidates) {
485
+ const lines = content.trim().split('\n').map(l => l.trim());
486
+ if (lines.length < 2)
487
+ continue;
488
+ if (!lines.some(l => isSeparatorLine(l)))
489
+ continue;
490
+ const fullMatch = match[0];
491
+ const startIdx = match.index + (fullMatch.startsWith('\n') ? 1 : 0);
492
+ const endIdx = match.index + fullMatch.length;
493
+ return { before: text.slice(0, startIdx), tableLines: lines, after: text.slice(endIdx) };
494
+ }
495
+ return null;
496
+ }
497
+ function parseMarkdownTable(lines) {
498
+ const splitRow = (line) => line.replace(/^\|/, '').replace(/\|$/, '').split('|').map(c => c.trim());
499
+ const headers = splitRow(lines[0]);
500
+ const sepIdx = lines.findIndex(l => isSeparatorLine(l));
501
+ const sepCells = sepIdx >= 0 ? splitRow(lines[sepIdx]) : [];
502
+ const alignments = sepCells.map(cell => {
503
+ const t = cell.trim();
504
+ if (t.startsWith(':') && t.endsWith(':'))
505
+ return 'center';
506
+ if (t.endsWith(':'))
507
+ return 'right';
508
+ return 'left';
509
+ });
510
+ while (alignments.length < headers.length)
511
+ alignments.push('left');
512
+ const rows = [];
513
+ for (let i = 0; i < lines.length; i++) {
514
+ if (i === 0 || i === sepIdx)
515
+ continue;
516
+ rows.push(splitRow(lines[i]));
517
+ }
518
+ return { headers, rows, alignments };
519
+ }
520
+ function buildSlackTableBlock(parsed) {
521
+ const maxCols = Math.min(parsed.headers.length, 20);
522
+ const buildRow = (cells) => Array.from({ length: maxCols }, (_, i) => ({ type: 'raw_text', text: (cells[i] || '').toString() }));
523
+ return {
524
+ type: 'table',
525
+ rows: [buildRow(parsed.headers), ...parsed.rows.slice(0, 99).map(r => buildRow(r))],
526
+ column_settings: parsed.alignments.slice(0, maxCols).map(a => ({ align: a })),
527
+ };
528
+ }
529
+ // =============================================================================
530
+ // Code snippet uploads
531
+ // =============================================================================
532
+ /**
533
+ * Strips all non-ASCII characters from code content.
534
+ * Prevents invisible Unicode characters from breaking copy-pasted queries.
535
+ *
536
+ * Ported from nlq-claude-slack-bot/src/slack-handler.ts:890
537
+ */
538
+ function sanitizeCodeContent(code) {
539
+ return code.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '');
540
+ }
541
+ /**
542
+ * Extracts fenced code blocks from text, returning text without code blocks
543
+ * and the extracted blocks separately.
544
+ *
545
+ * Ported from nlq-claude-slack-bot/src/slack-handler.ts:898
546
+ */
547
+ function extractCodeBlocks(text) {
548
+ const codeBlocks = [];
549
+ const textWithoutCode = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
550
+ codeBlocks.push({ lang: lang || 'sql', code: code.trim() });
551
+ return '';
552
+ });
553
+ return { textWithoutCode, codeBlocks };
554
+ }
555
+ /**
556
+ * Uploads code blocks as Slack file snippets for clean copy-paste.
557
+ * Sanitizes content to remove invisible Unicode characters.
558
+ *
559
+ * Ported from nlq-claude-slack-bot/src/slack-handler.ts:910
560
+ *
561
+ * @param client - Slack Web API client
562
+ * @param codeBlocks - Extracted code blocks with language and content
563
+ * @param channelId - Channel to upload to
564
+ * @param threadTs - Thread timestamp for threading the upload
565
+ */
566
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
567
+ async function uploadCodeSnippets(client, codeBlocks, channelId, threadTs) {
568
+ for (let i = 0; i < codeBlocks.length; i++) {
569
+ const { lang, code } = codeBlocks[i];
570
+ const sanitized = sanitizeCodeContent(code);
571
+ const extension = lang === 'sql' ? 'sql' : (lang || 'txt');
572
+ const name = codeBlocks.length === 1
573
+ ? `query.${extension}`
574
+ : `query_${i + 1}.${extension}`;
575
+ try {
576
+ await client.filesUploadV2({
577
+ channel_id: channelId,
578
+ thread_ts: threadTs,
579
+ content: sanitized,
580
+ filename: name,
581
+ title: name,
582
+ });
583
+ }
584
+ catch {
585
+ // Non-fatal — code is still visible inline in the message
586
+ }
587
+ }
588
+ }
589
+ // =============================================================================
590
+ // Other helpers
591
+ // =============================================================================
592
+ /**
593
+ * Builds a human-readable status string for the first tool_use block in a
594
+ * Claude assistant message. Used to keep the user informed while a tool runs.
595
+ *
596
+ * Returns a Slack mrkdwn string like `*Querying Redshift*\n\`\`\`sql\n...\n\`\`\``
597
+ * for known tools, `*Working...*` for unknown tools, or null if no tool block.
598
+ *
599
+ * @param {unknown[]} content - The `content` array from a Claude assistant message.
600
+ * @returns {string | null} Slack-formatted status text, or null if no tool was used.
601
+ */
602
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
603
+ function formatToolStatus(content) {
604
+ for (const block of content) {
605
+ if (block.type !== 'tool_use')
606
+ continue;
607
+ const label = MCP_TOOL_LABELS[block.name];
608
+ if (label) {
609
+ if (block.name === 'mcp__redshift-mcp__query' && block.input?.sql) {
610
+ const sql = String(block.input.sql).slice(0, 500);
611
+ return `*${label}*\n\`\`\`sql\n${sql}\n\`\`\``;
612
+ }
613
+ if (block.input?.query)
614
+ return `*${label}:* \`${String(block.input.query).slice(0, 100)}\``;
615
+ if (block.input?.fqn || block.input?.name)
616
+ return `*${label}:* \`${block.input.fqn ?? block.input.name}\``;
617
+ return `*${label}...*`;
618
+ }
619
+ return `*Working...*`;
620
+ }
621
+ return null;
622
+ }
623
+ /**
624
+ * Builds the full prompt to send to Claude by prepending thread context.
625
+ *
626
+ * Fetches the preceding messages in the thread (up to MAX_THREAD_CONTEXT_MESSAGES)
627
+ * and prefixes them as `[Thread context]` so Claude can follow the conversation.
628
+ * Silently falls back to the bare user text if the Slack API call fails.
629
+ *
630
+ * @param {unknown} client - Slack Web API client.
631
+ * @param {string} channelId - Channel containing the thread.
632
+ * @param {string | undefined} threadTs - Thread timestamp, or undefined for non-thread DMs.
633
+ * @param {string} userText - The user's message with bot mentions stripped.
634
+ * @param {Agent} agent - The agent (used for speaker labelling in context).
635
+ * @param {Logger} log - Logger instance.
636
+ * @param {SlackFile[]} [files] - Files attached to the message.
637
+ * @returns {Promise<string | ContentBlockParam[]>} Prompt for `claudeHandler.streamQuery`.
638
+ */
639
+ /** Max bytes to read from a single text file (512 KB). */
640
+ const MAX_TEXT_FILE_BYTES = 512 * 1024;
641
+ /** Max bytes to download for image/PDF files (20 MB). */
642
+ const MAX_BINARY_FILE_BYTES = 20 * 1024 * 1024;
643
+ const TEXT_MIMETYPES = new Set([
644
+ 'text/plain', 'text/csv', 'text/html', 'text/xml', 'text/markdown',
645
+ 'text/x-python', 'text/x-script.python', 'text/javascript',
646
+ 'application/json', 'application/xml', 'application/x-yaml',
647
+ 'application/x-ndjson', 'application/sql',
648
+ ]);
649
+ const TEXT_FILETYPES = new Set([
650
+ 'text', 'csv', 'json', 'yaml', 'xml', 'html', 'markdown', 'md',
651
+ 'python', 'py', 'javascript', 'js', 'typescript', 'ts', 'go',
652
+ 'ruby', 'rb', 'java', 'kotlin', 'swift', 'cpp', 'c', 'rust',
653
+ 'sh', 'bash', 'zsh', 'sql', 'r', 'scala', 'php', 'toml', 'ini',
654
+ 'conf', 'cfg', 'env', 'diff', 'patch', 'log',
655
+ ]);
656
+ const IMAGE_MIMETYPES = {
657
+ 'image/jpeg': 'image/jpeg',
658
+ 'image/jpg': 'image/jpeg',
659
+ 'image/png': 'image/png',
660
+ 'image/webp': 'image/webp',
661
+ };
662
+ const IMAGE_FILETYPES = {
663
+ jpg: 'image/jpeg',
664
+ jpeg: 'image/jpeg',
665
+ png: 'image/png',
666
+ webp: 'image/webp',
667
+ };
668
+ function getFileKind(file) {
669
+ const mt = file.mimetype ?? '';
670
+ const ft = (file.filetype ?? '').toLowerCase();
671
+ if (mt === 'application/pdf' || ft === 'pdf')
672
+ return 'pdf';
673
+ if (mt in IMAGE_MIMETYPES || ft in IMAGE_FILETYPES)
674
+ return 'image';
675
+ if (TEXT_MIMETYPES.has(mt) || mt.startsWith('text/') || TEXT_FILETYPES.has(ft))
676
+ return 'text';
677
+ return 'unsupported';
678
+ }
679
+ async function fetchSlackFile(client, url) {
680
+ const token = client.token ?? client._token ?? '';
681
+ const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
682
+ if (!response.ok)
683
+ throw new Error(`HTTP ${response.status}`);
684
+ return response.arrayBuffer();
685
+ }
686
+ async function downloadFile(client, file, log) {
687
+ const kind = getFileKind(file);
688
+ if (kind === 'unsupported') {
689
+ log.debug('Skipping unsupported file type', { name: file.name, mimetype: file.mimetype, filetype: file.filetype });
690
+ return null;
691
+ }
692
+ if (!file.url_private_download)
693
+ return null;
694
+ const label = file.name ?? file.title ?? file.id;
695
+ try {
696
+ if (kind === 'text') {
697
+ if (file.size && file.size > MAX_TEXT_FILE_BYTES) {
698
+ log.warn('Text file too large, truncating', { name: file.name, size: file.size });
699
+ }
700
+ const buffer = await fetchSlackFile(client, file.url_private_download);
701
+ let text = new TextDecoder().decode(buffer.slice(0, MAX_TEXT_FILE_BYTES));
702
+ if (buffer.byteLength > MAX_TEXT_FILE_BYTES)
703
+ text += '\n[... truncated at 512 KB ...]';
704
+ return { kind: 'text', content: `[File: ${label}]\n${text}` };
705
+ }
706
+ if (file.size && file.size > MAX_BINARY_FILE_BYTES) {
707
+ log.warn('Binary file too large to send to Claude', { name: file.name, size: file.size });
708
+ return { kind: 'text', content: `[File "${label}" is too large to process (${Math.round((file.size ?? 0) / 1024 / 1024)} MB, limit 20 MB)]` };
709
+ }
710
+ const buffer = await fetchSlackFile(client, file.url_private_download);
711
+ const base64 = Buffer.from(buffer).toString('base64');
712
+ if (kind === 'image') {
713
+ const mt = file.mimetype ?? '';
714
+ const ft = (file.filetype ?? '').toLowerCase();
715
+ const mediaType = IMAGE_MIMETYPES[mt] ?? IMAGE_FILETYPES[ft] ?? 'image/jpeg';
716
+ return {
717
+ kind: 'block',
718
+ block: {
719
+ type: 'image',
720
+ source: { type: 'base64', media_type: mediaType, data: base64 },
721
+ },
722
+ };
723
+ }
724
+ return {
725
+ kind: 'block',
726
+ block: {
727
+ type: 'document',
728
+ source: { type: 'base64', media_type: 'application/pdf', data: base64 },
729
+ title: label,
730
+ },
731
+ };
732
+ }
733
+ catch (err) {
734
+ log.warn('Error downloading file', { name: file.name, error: err });
735
+ return null;
736
+ }
737
+ }
738
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
739
+ async function buildPrompt(client, channelId, threadTs, userText, agent, log, files) {
740
+ // Fetch thread context
741
+ let threadContext = '';
742
+ if (threadTs) {
743
+ try {
744
+ const replies = await client.conversations.replies({ channel: channelId, ts: threadTs, limit: MAX_THREAD_CONTEXT_MESSAGES });
745
+ const messages = replies.messages ?? [];
746
+ const contextMessages = messages.slice(0, -1);
747
+ if (contextMessages.length > 0) {
748
+ const userCache = {};
749
+ const getUserLabel = async (userId) => {
750
+ if (userCache[userId])
751
+ return userCache[userId];
752
+ try {
753
+ const info = await client.users.info({ user: userId });
754
+ const name = info.user?.display_name || info.user?.real_name || userId;
755
+ userCache[userId] = `${name} (${userId})`;
756
+ }
757
+ catch {
758
+ userCache[userId] = userId;
759
+ }
760
+ return userCache[userId];
761
+ };
762
+ const contextLines = await Promise.all(contextMessages.map(async (m) => {
763
+ const speaker = m.bot_id ? `${agent.name}` : await getUserLabel(m.user);
764
+ const parts = [`${speaker}: ${stripBotMention(m.text ?? '', agent.slackBotUserId)}`];
765
+ // Include forwarded/shared message attachments (text + images)
766
+ if (m.attachments?.length) {
767
+ for (const att of m.attachments) {
768
+ const attParts = [];
769
+ if (att.author_name || att.from_url)
770
+ attParts.push(`[Forwarded from ${att.author_name ?? att.from_url}]`);
771
+ if (att.pretext)
772
+ attParts.push(att.pretext);
773
+ if (att.text)
774
+ attParts.push(att.text);
775
+ if (att.fallback && !att.text)
776
+ attParts.push(att.fallback);
777
+ if (att.image_url)
778
+ attParts.push(`[Attached image: ${att.image_url}]`);
779
+ if (attParts.length)
780
+ parts.push(attParts.join('\n'));
781
+ }
782
+ }
783
+ // Include files shared in thread history (images shown as note)
784
+ if (m.files?.length) {
785
+ for (const f of m.files) {
786
+ const label = f.name ?? f.title ?? f.id;
787
+ if (f.mimetype?.startsWith('image/')) {
788
+ parts.push(`[Shared image: ${label}]`);
789
+ }
790
+ else if (f.name) {
791
+ parts.push(`[Shared file: ${label}]`);
792
+ }
793
+ }
794
+ }
795
+ return parts.join('\n');
796
+ }));
797
+ let context = contextLines.join('\n');
798
+ if (context.length > MAX_THREAD_CONTEXT_CHARS)
799
+ context = '...' + context.slice(-MAX_THREAD_CONTEXT_CHARS);
800
+ threadContext = `[Thread context]\n${context}\n\n`;
801
+ }
802
+ }
803
+ catch (err) {
804
+ log.warn('Failed to fetch thread context', { error: err });
805
+ }
806
+ }
807
+ // Download files — split into text chunks and binary blocks
808
+ const textChunks = [];
809
+ const binaryBlocks = [];
810
+ if (files && files.length > 0) {
811
+ const results = await Promise.all(files.map(f => downloadFile(client, f, log)));
812
+ for (const result of results) {
813
+ if (!result)
814
+ continue;
815
+ if (result.kind === 'text')
816
+ textChunks.push(result.content);
817
+ else
818
+ binaryBlocks.push(result.block);
819
+ }
820
+ }
821
+ const textPrompt = `${threadContext}${textChunks.length > 0 ? textChunks.join('\n\n') + '\n\n' : ''}${userText}`.trim();
822
+ if (binaryBlocks.length > 0) {
823
+ const blocks = [];
824
+ if (textPrompt)
825
+ blocks.push({ type: 'text', text: textPrompt });
826
+ blocks.push(...binaryBlocks);
827
+ return blocks;
828
+ }
829
+ return textPrompt;
830
+ }
831
+ /**
832
+ * Removes `<@BOT_USER_ID>` mention tokens from a message string.
833
+ *
834
+ * @param {string} text - Raw Slack message text.
835
+ * @param {string} [botUserId] - The bot's Slack user ID. No-op if undefined.
836
+ * @returns {string} Text with all bot mention tokens stripped and trimmed.
837
+ */
838
+ function stripBotMention(text, botUserId) {
839
+ if (!botUserId)
840
+ return text;
841
+ return text.replace(new RegExp(`<@${botUserId}>\\s*`, 'g'), '').trim();
842
+ }
843
+ //# sourceMappingURL=slack-handler.js.map