slackhive 0.1.41 → 0.1.43

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