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,262 @@
1
+ /**
2
+ * @fileoverview Unit tests for auth.ts — signSession, verifySession,
3
+ * getSessionFromRequest, requireRole, and authenticateUser.
4
+ *
5
+ * No database connection required for session/cookie tests.
6
+ * authenticateUser DB path is tested via vi.mock.
7
+ *
8
+ * @module web/lib/__tests__/auth.test
9
+ */
10
+
11
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
12
+ import * as crypto from 'crypto';
13
+
14
+ // Mock DB dependency before importing auth
15
+ vi.mock('@/lib/db', () => ({
16
+ getUserByUsername: vi.fn(),
17
+ }));
18
+
19
+ import {
20
+ signSession,
21
+ verifySession,
22
+ getSessionFromRequest,
23
+ requireRole,
24
+ authenticateUser,
25
+ COOKIE_NAME,
26
+ } from '@/lib/auth';
27
+ import { getUserByUsername } from '@/lib/db';
28
+ import type { SessionPayload } from '@/lib/auth';
29
+
30
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
31
+
32
+ function makeRequest(cookie?: string): Request {
33
+ return new Request('http://localhost/api/test', {
34
+ headers: cookie ? { cookie } : {},
35
+ });
36
+ }
37
+
38
+ function makeSignedCookie(payload: SessionPayload, secret = 'change-this-secret-in-production'): string {
39
+ const data = Buffer.from(JSON.stringify(payload)).toString('base64url');
40
+ const sig = crypto.createHmac('sha256', secret).update(data).digest('base64url');
41
+ return `${data}.${sig}`;
42
+ }
43
+
44
+ // ─── signSession / verifySession ──────────────────────────────────────────────
45
+
46
+ describe('signSession + verifySession', () => {
47
+ it('round-trips a valid session payload', () => {
48
+ const payload: SessionPayload = { username: 'alice', role: 'editor' };
49
+ const cookie = signSession(payload);
50
+ const result = verifySession(cookie);
51
+ expect(result).toEqual(payload);
52
+ });
53
+
54
+ it('round-trips all role types', () => {
55
+ const roles: SessionPayload['role'][] = ['superadmin', 'admin', 'editor', 'viewer'];
56
+ for (const role of roles) {
57
+ const payload: SessionPayload = { username: 'user', role };
58
+ expect(verifySession(signSession(payload))).toEqual(payload);
59
+ }
60
+ });
61
+
62
+ it('returns null for a tampered signature', () => {
63
+ const payload: SessionPayload = { username: 'alice', role: 'admin' };
64
+ const cookie = signSession(payload);
65
+ const [data] = cookie.split('.');
66
+ const tampered = `${data}.invalidsignature`;
67
+ expect(verifySession(tampered)).toBeNull();
68
+ });
69
+
70
+ it('returns null for a tampered payload (data changed, sig unchanged)', () => {
71
+ const payload: SessionPayload = { username: 'alice', role: 'editor' };
72
+ const cookie = signSession(payload);
73
+ const [, sig] = cookie.split('.');
74
+ const evilPayload = Buffer.from(JSON.stringify({ username: 'alice', role: 'superadmin' })).toString('base64url');
75
+ const tampered = `${evilPayload}.${sig}`;
76
+ expect(verifySession(tampered)).toBeNull();
77
+ });
78
+
79
+ it('returns null when cookie has no dot separator', () => {
80
+ expect(verifySession('nodothere')).toBeNull();
81
+ });
82
+
83
+ it('returns null when cookie has more than one dot', () => {
84
+ expect(verifySession('part1.part2.part3')).toBeNull();
85
+ });
86
+
87
+ it('returns null for an empty string', () => {
88
+ expect(verifySession('')).toBeNull();
89
+ });
90
+
91
+ it('returns null when payload is valid base64 but not JSON', () => {
92
+ const notJson = Buffer.from('not-json').toString('base64url');
93
+ const sig = crypto
94
+ .createHmac('sha256', 'change-this-secret-in-production')
95
+ .update(notJson)
96
+ .digest('base64url');
97
+ expect(verifySession(`${notJson}.${sig}`)).toBeNull();
98
+ });
99
+
100
+ it('returns null when signed with a different secret', () => {
101
+ const cookie = makeSignedCookie({ username: 'alice', role: 'admin' }, 'other-secret');
102
+ expect(verifySession(cookie)).toBeNull();
103
+ });
104
+
105
+ it('produces different cookies for different payloads', () => {
106
+ const a = signSession({ username: 'alice', role: 'admin' });
107
+ const b = signSession({ username: 'alice', role: 'editor' });
108
+ expect(a).not.toBe(b);
109
+ });
110
+ });
111
+
112
+ // ─── getSessionFromRequest ────────────────────────────────────────────────────
113
+
114
+ describe('getSessionFromRequest', () => {
115
+ it('extracts and verifies a valid session from the cookie header', () => {
116
+ const payload: SessionPayload = { username: 'bob', role: 'viewer' };
117
+ const value = signSession(payload);
118
+ const req = makeRequest(`${COOKIE_NAME}=${value}`);
119
+ expect(getSessionFromRequest(req)).toEqual(payload);
120
+ });
121
+
122
+ it('returns null when no cookie header is present', () => {
123
+ const req = makeRequest();
124
+ expect(getSessionFromRequest(req)).toBeNull();
125
+ });
126
+
127
+ it('returns null when cookie header has no auth_session key', () => {
128
+ const req = makeRequest('other_cookie=abc123');
129
+ expect(getSessionFromRequest(req)).toBeNull();
130
+ });
131
+
132
+ it('returns null when the session cookie is tampered', () => {
133
+ const req = makeRequest(`${COOKIE_NAME}=invalid.garbage`);
134
+ expect(getSessionFromRequest(req)).toBeNull();
135
+ });
136
+
137
+ it('extracts session when auth_session is among multiple cookies', () => {
138
+ const payload: SessionPayload = { username: 'charlie', role: 'admin' };
139
+ const value = signSession(payload);
140
+ const req = makeRequest(`other=val; ${COOKIE_NAME}=${value}; another=foo`);
141
+ expect(getSessionFromRequest(req)).toEqual(payload);
142
+ });
143
+
144
+ it('handles URL-encoded cookie values', () => {
145
+ const payload: SessionPayload = { username: 'dave', role: 'editor' };
146
+ const value = encodeURIComponent(signSession(payload));
147
+ const req = makeRequest(`${COOKIE_NAME}=${value}`);
148
+ expect(getSessionFromRequest(req)).toEqual(payload);
149
+ });
150
+ });
151
+
152
+ // ─── requireRole ─────────────────────────────────────────────────────────────
153
+
154
+ describe('requireRole', () => {
155
+ it('returns session when role meets minimum (viewer requires viewer)', () => {
156
+ const payload: SessionPayload = { username: 'alice', role: 'viewer' };
157
+ const req = makeRequest(`${COOKIE_NAME}=${signSession(payload)}`);
158
+ expect(requireRole(req, 'viewer')).toEqual(payload);
159
+ });
160
+
161
+ it('returns session when role exceeds minimum (admin requires editor)', () => {
162
+ const payload: SessionPayload = { username: 'alice', role: 'admin' };
163
+ const req = makeRequest(`${COOKIE_NAME}=${signSession(payload)}`);
164
+ expect(requireRole(req, 'editor')).toEqual(payload);
165
+ });
166
+
167
+ it('superadmin passes any role requirement', () => {
168
+ const payload: SessionPayload = { username: 'root', role: 'superadmin' };
169
+ const req = makeRequest(`${COOKIE_NAME}=${signSession(payload)}`);
170
+ expect(requireRole(req, 'admin')).toEqual(payload);
171
+ });
172
+
173
+ it('throws when role is below minimum (viewer requires editor)', () => {
174
+ const payload: SessionPayload = { username: 'alice', role: 'viewer' };
175
+ const req = makeRequest(`${COOKIE_NAME}=${signSession(payload)}`);
176
+ expect(() => requireRole(req, 'editor')).toThrow('Insufficient permissions');
177
+ });
178
+
179
+ it('throws when not authenticated (no cookie)', () => {
180
+ const req = makeRequest();
181
+ expect(() => requireRole(req, 'viewer')).toThrow('Not authenticated');
182
+ });
183
+
184
+ it('editor cannot meet admin requirement', () => {
185
+ const payload: SessionPayload = { username: 'alice', role: 'editor' };
186
+ const req = makeRequest(`${COOKIE_NAME}=${signSession(payload)}`);
187
+ expect(() => requireRole(req, 'admin')).toThrow('Insufficient permissions');
188
+ });
189
+ });
190
+
191
+ // ─── authenticateUser ─────────────────────────────────────────────────────────
192
+
193
+ describe('authenticateUser', () => {
194
+ beforeEach(() => vi.clearAllMocks());
195
+
196
+ it('returns superadmin session for env-var credentials', async () => {
197
+ const result = await authenticateUser('admin', 'changeme');
198
+ expect(result).toEqual({ username: 'admin', role: 'superadmin' });
199
+ });
200
+
201
+ it('returns null for wrong superadmin password', async () => {
202
+ const result = await authenticateUser('admin', 'wrongpass');
203
+ expect(result).toBeNull();
204
+ });
205
+
206
+ it('returns null when DB user does not exist', async () => {
207
+ vi.mocked(getUserByUsername).mockResolvedValue(null);
208
+ const result = await authenticateUser('unknown', 'pass');
209
+ expect(result).toBeNull();
210
+ });
211
+
212
+ it('returns null when DB user password does not match', async () => {
213
+ vi.mocked(getUserByUsername).mockResolvedValue({
214
+ id: 'u1', username: 'bob', role: 'editor',
215
+ passwordHash: '$2b$10$invalidhashthatisnevervalid123456789012',
216
+ createdAt: new Date().toISOString(),
217
+ });
218
+ const result = await authenticateUser('bob', 'wrongpass');
219
+ expect(result).toBeNull();
220
+ });
221
+
222
+ it('does not hit DB for superadmin credentials', async () => {
223
+ await authenticateUser('admin', 'changeme');
224
+ expect(getUserByUsername).not.toHaveBeenCalled();
225
+ });
226
+ });
227
+
228
+ // ─── hashPassword ─────────────────────────────────────────────────────────────
229
+
230
+ describe('hashPassword', () => {
231
+ it('returns a bcrypt hash string', async () => {
232
+ const { hashPassword } = await import('@/lib/auth');
233
+ const hash = await hashPassword('mysecret');
234
+ expect(hash).toMatch(/^\$2[ab]\$10\$/);
235
+ });
236
+
237
+ it('produces a different hash each call (salt)', async () => {
238
+ const { hashPassword } = await import('@/lib/auth');
239
+ const h1 = await hashPassword('same');
240
+ const h2 = await hashPassword('same');
241
+ expect(h1).not.toBe(h2);
242
+ });
243
+
244
+ it('produces a hash that bcrypt can verify', async () => {
245
+ const { hashPassword } = await import('@/lib/auth');
246
+ const bcrypt = (await import('bcryptjs')).default;
247
+ const hash = await hashPassword('testpass');
248
+ expect(await bcrypt.compare('testpass', hash)).toBe(true);
249
+ });
250
+ });
251
+
252
+ // ─── Production secret guard ────────────────────────────────────────────────
253
+
254
+ describe('production secret guard', () => {
255
+ it('does not throw in test/dev environment', () => {
256
+ // The module already loaded successfully via the top-level import.
257
+ // If the guard fired incorrectly, this entire test file would have
258
+ // failed to import. Verify the module exported the expected function.
259
+ expect(signSession).toBeDefined();
260
+ expect(typeof signSession).toBe('function');
261
+ });
262
+ });
@@ -0,0 +1,323 @@
1
+ /**
2
+ * @fileoverview Unit tests for boss-registry.ts — regenerateBossRegistry.
3
+ *
4
+ * Tests cover the registry content generation logic: team listing, delegation
5
+ * instructions, Slack mention format, multi-boss routing, and edge cases like
6
+ * agents with no Slack user ID or no team members.
7
+ *
8
+ * All DB calls are mocked via vi.mock — no database connection required.
9
+ *
10
+ * @module web/lib/__tests__/boss-registry.test
11
+ */
12
+
13
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
14
+ import type { Agent } from '@slackhive/shared';
15
+
16
+ // Mock DB dependencies before importing the module under test
17
+ vi.mock('@/lib/db', () => ({
18
+ getAllAgents: vi.fn(),
19
+ updateAgentClaudeMd: vi.fn().mockResolvedValue(undefined),
20
+ publishAgentEvent: vi.fn().mockResolvedValue(undefined),
21
+ }));
22
+
23
+ import { regenerateBossRegistry } from '@/lib/boss-registry';
24
+ import { getAllAgents, updateAgentClaudeMd, publishAgentEvent } from '@/lib/db';
25
+
26
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
27
+
28
+ function makeAgent(overrides: Partial<Agent>): Agent {
29
+ return {
30
+ id: 'agent-1',
31
+ slug: 'agent',
32
+ name: 'Agent',
33
+ persona: undefined,
34
+ description: undefined,
35
+ slackBotToken: 'xoxb-fake',
36
+ slackAppToken: 'xapp-fake',
37
+ slackSigningSecret: 'secret',
38
+ slackBotUserId: undefined,
39
+ model: 'claude-opus-4-5',
40
+ status: 'stopped',
41
+ enabled: true,
42
+ isBoss: false,
43
+ reportsTo: [],
44
+ claudeMd: '',
45
+ createdBy: 'system',
46
+ createdAt: new Date(),
47
+ updatedAt: new Date(),
48
+ ...overrides,
49
+ };
50
+ }
51
+
52
+ // ─── Tests ────────────────────────────────────────────────────────────────────
53
+
54
+ describe('regenerateBossRegistry', () => {
55
+ beforeEach(() => {
56
+ vi.clearAllMocks();
57
+ });
58
+
59
+ it('does nothing when there are no boss agents', async () => {
60
+ vi.mocked(getAllAgents).mockResolvedValue([
61
+ makeAgent({ id: 'a1', isBoss: false }),
62
+ ]);
63
+
64
+ await regenerateBossRegistry();
65
+
66
+ expect(updateAgentClaudeMd).not.toHaveBeenCalled();
67
+ expect(publishAgentEvent).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it('does nothing when boss has no team members with slackBotUserId', async () => {
71
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
72
+ const specialist = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: undefined });
73
+
74
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
75
+
76
+ await regenerateBossRegistry();
77
+
78
+ expect(updateAgentClaudeMd).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('generates registry with correct agent mention and description', async () => {
82
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
83
+ const specialist = makeAgent({
84
+ id: 'spec-1', name: 'DataBot', isBoss: false,
85
+ reportsTo: ['boss-1'], slackBotUserId: 'U123ABC',
86
+ description: 'Runs Redshift queries',
87
+ });
88
+
89
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
90
+
91
+ await regenerateBossRegistry();
92
+
93
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
94
+ expect(content).toContain('**DataBot** (<@U123ABC>) — Runs Redshift queries');
95
+ });
96
+
97
+ it('uses slug-based fallback mention when slackBotUserId is missing', async () => {
98
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
99
+ // specialist has no slackBotUserId — registry should fall back to @slug
100
+ const specialist = makeAgent({
101
+ id: 'spec-1', name: 'DataBot', slug: 'data-bot', isBoss: false,
102
+ reportsTo: ['boss-1'], slackBotUserId: undefined,
103
+ description: 'Runs Redshift queries',
104
+ });
105
+
106
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
107
+
108
+ // regenerateSingleBossRegistry skips agents with no slackBotUserId
109
+ // so updateAgentClaudeMd should NOT be called
110
+ await regenerateBossRegistry();
111
+ expect(updateAgentClaudeMd).not.toHaveBeenCalled();
112
+ });
113
+
114
+ it('uses default description when agent description is undefined', async () => {
115
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
116
+ const specialist = makeAgent({
117
+ id: 'spec-1', name: 'Writer', isBoss: false,
118
+ reportsTo: ['boss-1'], slackBotUserId: 'U999',
119
+ description: undefined,
120
+ });
121
+
122
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
123
+
124
+ await regenerateBossRegistry();
125
+
126
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
127
+ expect(content).toContain('No description provided.');
128
+ });
129
+
130
+ it('includes boss name in the registry heading', async () => {
131
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'HQ Boss' });
132
+ const specialist = makeAgent({
133
+ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'U1',
134
+ });
135
+
136
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
137
+
138
+ await regenerateBossRegistry();
139
+
140
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
141
+ expect(content).toContain('# HQ Boss — Team Orchestrator');
142
+ });
143
+
144
+ it('includes delegation instructions in the registry', async () => {
145
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
146
+ const specialist = makeAgent({
147
+ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'U1',
148
+ });
149
+
150
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
151
+
152
+ await regenerateBossRegistry();
153
+
154
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
155
+ expect(content).toContain('ALWAYS delegate');
156
+ expect(content).toContain('## Your Team');
157
+ expect(content).toContain('## How to delegate');
158
+ });
159
+
160
+ it('publishes a reload event after updating the registry', async () => {
161
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
162
+ const specialist = makeAgent({
163
+ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'U1',
164
+ });
165
+
166
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
167
+
168
+ await regenerateBossRegistry();
169
+
170
+ expect(publishAgentEvent).toHaveBeenCalledWith({ type: 'reload', agentId: 'boss-1' });
171
+ });
172
+
173
+ it('handles multiple bosses independently', async () => {
174
+ const boss1 = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss One' });
175
+ const boss2 = makeAgent({ id: 'boss-2', isBoss: true, name: 'Boss Two' });
176
+ const spec1 = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'U1', name: 'Alpha' });
177
+ const spec2 = makeAgent({ id: 'spec-2', isBoss: false, reportsTo: ['boss-2'], slackBotUserId: 'U2', name: 'Beta' });
178
+
179
+ vi.mocked(getAllAgents).mockResolvedValue([boss1, boss2, spec1, spec2]);
180
+
181
+ await regenerateBossRegistry();
182
+
183
+ expect(updateAgentClaudeMd).toHaveBeenCalledTimes(2);
184
+
185
+ const [call1, call2] = vi.mocked(updateAgentClaudeMd).mock.calls;
186
+ const contents = [call1[1], call2[1]];
187
+
188
+ expect(contents.some(c => c.includes('Boss One') && c.includes('Alpha'))).toBe(true);
189
+ expect(contents.some(c => c.includes('Boss Two') && c.includes('Beta'))).toBe(true);
190
+ });
191
+
192
+ it('does not include agents from other boss teams in a boss registry', async () => {
193
+ const boss1 = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss One' });
194
+ const boss2 = makeAgent({ id: 'boss-2', isBoss: true, name: 'Boss Two' });
195
+ const spec1 = makeAgent({ id: 'spec-1', name: 'Alpha', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'U1' });
196
+ const spec2 = makeAgent({ id: 'spec-2', name: 'Beta', isBoss: false, reportsTo: ['boss-2'], slackBotUserId: 'U2' });
197
+
198
+ vi.mocked(getAllAgents).mockResolvedValue([boss1, boss2, spec1, spec2]);
199
+
200
+ await regenerateBossRegistry();
201
+
202
+ const [call1, call2] = vi.mocked(updateAgentClaudeMd).mock.calls;
203
+ const contents = [call1[1], call2[1]];
204
+ const boss1Content = contents.find(c => c.includes('Boss One'))!;
205
+ const boss2Content = contents.find(c => c.includes('Boss Two'))!;
206
+
207
+ expect(boss1Content).not.toContain('Beta');
208
+ expect(boss2Content).not.toContain('Alpha');
209
+ });
210
+
211
+ it('uses fallback description when agent description is empty string', async () => {
212
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
213
+ const specialist = makeAgent({
214
+ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'],
215
+ slackBotUserId: 'U1', description: '',
216
+ });
217
+
218
+ vi.mocked(getAllAgents).mockResolvedValue([boss, specialist]);
219
+
220
+ await regenerateBossRegistry();
221
+
222
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
223
+ expect(content).toContain('No description provided.');
224
+ });
225
+
226
+ it('skips a boss agent that appears in another boss team (boss is also a specialist)', async () => {
227
+ const boss1 = makeAgent({ id: 'boss-1', isBoss: true, name: 'Top Boss' });
228
+ // boss2 reports to boss1 but is also a boss itself
229
+ const boss2 = makeAgent({ id: 'boss-2', isBoss: true, name: 'Mid Boss', reportsTo: ['boss-1'], slackBotUserId: 'U2' });
230
+ const spec = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-2'], slackBotUserId: 'U3', name: 'Worker' });
231
+
232
+ vi.mocked(getAllAgents).mockResolvedValue([boss1, boss2, spec]);
233
+
234
+ await regenerateBossRegistry();
235
+
236
+ // boss1's registry should include boss2 (it reports to boss1)
237
+ const boss1Call = vi.mocked(updateAgentClaudeMd).mock.calls.find(c => c[0] === 'boss-1');
238
+ expect(boss1Call).toBeDefined();
239
+ expect(boss1Call![1]).toContain('Mid Boss');
240
+ });
241
+
242
+ it('does not call updateAgentClaudeMd when getAllAgents throws', async () => {
243
+ vi.mocked(getAllAgents).mockRejectedValue(new Error('DB connection failed'));
244
+
245
+ await expect(regenerateBossRegistry()).rejects.toThrow('DB connection failed');
246
+ expect(updateAgentClaudeMd).not.toHaveBeenCalled();
247
+ });
248
+
249
+ it('handles an agent reporting to multiple bosses', async () => {
250
+ const boss1 = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss One' });
251
+ const boss2 = makeAgent({ id: 'boss-2', isBoss: true, name: 'Boss Two' });
252
+ const shared = makeAgent({
253
+ id: 'spec-1', name: 'SharedBot', isBoss: false,
254
+ reportsTo: ['boss-1', 'boss-2'], slackBotUserId: 'U1',
255
+ });
256
+
257
+ vi.mocked(getAllAgents).mockResolvedValue([boss1, boss2, shared]);
258
+
259
+ await regenerateBossRegistry();
260
+
261
+ expect(updateAgentClaudeMd).toHaveBeenCalledTimes(2);
262
+ const contents = vi.mocked(updateAgentClaudeMd).mock.calls.map(c => c[1]);
263
+ expect(contents[0]).toContain('SharedBot');
264
+ expect(contents[1]).toContain('SharedBot');
265
+ });
266
+
267
+ it('includes boss self-mention in delegation instructions when boss has slackBotUserId', async () => {
268
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss', slackBotUserId: 'UBOSS' });
269
+ const spec = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'USPEC' });
270
+
271
+ vi.mocked(getAllAgents).mockResolvedValue([boss, spec]);
272
+ await regenerateBossRegistry();
273
+
274
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
275
+ expect(content).toContain('<@UBOSS>');
276
+ });
277
+
278
+ it('falls back to @slug in delegation instructions when boss has no slackBotUserId', async () => {
279
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss', slug: 'my-boss', slackBotUserId: undefined });
280
+ const spec = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'USPEC' });
281
+
282
+ vi.mocked(getAllAgents).mockResolvedValue([boss, spec]);
283
+ await regenerateBossRegistry();
284
+
285
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
286
+ expect(content).toContain('@my-boss');
287
+ });
288
+
289
+ it('includes post-delegation confirmation instructions', async () => {
290
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss', slackBotUserId: 'UBOSS' });
291
+ const spec = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'USPEC' });
292
+
293
+ vi.mocked(getAllAgents).mockResolvedValue([boss, spec]);
294
+ await regenerateBossRegistry();
295
+
296
+ const content = vi.mocked(updateAgentClaudeMd).mock.calls[0][1];
297
+ expect(content).toContain('After a specialist responds');
298
+ expect(content).toContain("When you're done, please tag");
299
+ });
300
+ });
301
+
302
+ // ─── slug fallback branch (line 48) ──────────────────────────────────────────
303
+ // The branch `a.slackBotUserId ? <@id> : @slug` is only reachable if the
304
+ // filter on line 44 is bypassed. Since the filter removes agents without
305
+ // slackBotUserId, the ternary's false branch (slug fallback) is dead code.
306
+ // We document this explicitly rather than testing unreachable code.
307
+ describe('mention format branch coverage note', () => {
308
+ beforeEach(() => vi.clearAllMocks());
309
+
310
+ it('always uses <@userId> format because filter removes agents without slackBotUserId', async () => {
311
+ const boss = makeAgent({ id: 'boss-1', isBoss: true, name: 'Boss' });
312
+ const spec = makeAgent({ id: 'spec-1', isBoss: false, reportsTo: ['boss-1'], slackBotUserId: 'UABC', slug: 'my-bot' });
313
+
314
+ vi.mocked(getAllAgents).mockResolvedValue([boss, spec]);
315
+ await regenerateBossRegistry();
316
+
317
+ const calls = vi.mocked(updateAgentClaudeMd).mock.calls;
318
+ expect(calls.length).toBe(1);
319
+ const registryContent = calls[0][1];
320
+ expect(registryContent).toContain('<@UABC>');
321
+ expect(registryContent).not.toContain('@my-bot');
322
+ });
323
+ });