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,271 @@
1
+ /**
2
+ * @fileoverview Integration tests for db.ts — requires a real Postgres connection.
3
+ *
4
+ * Set TEST_DATABASE_URL to run these tests. Without it, the entire suite is
5
+ * skipped so CI (which has no DB) is not affected.
6
+ *
7
+ * @module web/lib/__tests__/db.integration.test
8
+ */
9
+
10
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
11
+ import type { Agent } from '@slackhive/shared';
12
+
13
+ // Swap DATABASE_URL for the test database before importing db functions.
14
+ // This must happen before the module is loaded so the singleton pool picks it up.
15
+ if (process.env.TEST_DATABASE_URL) {
16
+ process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
17
+ }
18
+
19
+ import {
20
+ createAgent,
21
+ getAgentById,
22
+ updateAgentClaudeMd,
23
+ deleteAgent,
24
+ upsertSkill,
25
+ getAgentSkills,
26
+ deleteSkill,
27
+ createSnapshot,
28
+ listSnapshots,
29
+ getSnapshotById,
30
+ deleteSnapshot,
31
+ userCanWriteAgent,
32
+ grantAgentWrite,
33
+ revokeAgentWrite,
34
+ createUser,
35
+ deleteUser,
36
+ getUserByUsername,
37
+ } from '@/lib/db';
38
+
39
+ const DB_AVAILABLE = !!process.env.TEST_DATABASE_URL;
40
+
41
+ describe.skipIf(!DB_AVAILABLE)('db integration tests', () => {
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // createAgent + getAgentById
45
+ // ---------------------------------------------------------------------------
46
+
47
+ describe('createAgent + getAgentById', () => {
48
+ let agentId: string;
49
+
50
+ afterAll(async () => {
51
+ if (agentId) await deleteAgent(agentId);
52
+ });
53
+
54
+ it('creates an agent and fetches it by id', async () => {
55
+ const agent = await createAgent({
56
+ slug: `test-agent-${Date.now()}`,
57
+ name: 'Test Agent',
58
+ persona: 'A test persona',
59
+ description: 'Integration test agent',
60
+ slackBotToken: 'xoxb-test',
61
+ slackAppToken: 'xapp-test',
62
+ slackSigningSecret: 'secret',
63
+ model: 'claude-opus-4-6',
64
+ }, 'test-user');
65
+
66
+ agentId = agent.id;
67
+
68
+ expect(agent.id).toBeTruthy();
69
+ expect(agent.slug).toMatch(/^test-agent-/);
70
+ expect(agent.name).toBe('Test Agent');
71
+ expect(agent.persona).toBe('A test persona');
72
+ expect(agent.createdBy).toBe('test-user');
73
+
74
+ const fetched = await getAgentById(agent.id);
75
+ expect(fetched).not.toBeNull();
76
+ expect(fetched!.id).toBe(agent.id);
77
+ expect(fetched!.name).toBe('Test Agent');
78
+ });
79
+
80
+ it('returns null for a non-existent agent id', async () => {
81
+ const result = await getAgentById('00000000-0000-0000-0000-000000000000');
82
+ expect(result).toBeNull();
83
+ });
84
+ });
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // updateAgentClaudeMd
88
+ // ---------------------------------------------------------------------------
89
+
90
+ describe('updateAgentClaudeMd', () => {
91
+ let agentId: string;
92
+
93
+ beforeAll(async () => {
94
+ const agent = await createAgent({
95
+ slug: `test-claude-md-${Date.now()}`,
96
+ name: 'Claude MD Test',
97
+ slackBotToken: 'xoxb-test',
98
+ slackAppToken: 'xapp-test',
99
+ slackSigningSecret: 'secret',
100
+ }, 'test-user');
101
+ agentId = agent.id;
102
+ });
103
+
104
+ afterAll(async () => {
105
+ if (agentId) await deleteAgent(agentId);
106
+ });
107
+
108
+ it('updates claudeMd and reflects the change on re-fetch', async () => {
109
+ const newContent = '# Updated CLAUDE.md\n\nNew content here.';
110
+ await updateAgentClaudeMd(agentId, newContent);
111
+
112
+ const fetched = await getAgentById(agentId);
113
+ expect(fetched!.claudeMd).toBe(newContent);
114
+ });
115
+ });
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // upsertSkill + getAgentSkills + deleteSkill
119
+ // ---------------------------------------------------------------------------
120
+
121
+ describe('upsertSkill + getAgentSkills + deleteSkill', () => {
122
+ let agentId: string;
123
+
124
+ beforeAll(async () => {
125
+ const agent = await createAgent({
126
+ slug: `test-skills-${Date.now()}`,
127
+ name: 'Skills Test',
128
+ slackBotToken: 'xoxb-test',
129
+ slackAppToken: 'xapp-test',
130
+ slackSigningSecret: 'secret',
131
+ }, 'test-user');
132
+ agentId = agent.id;
133
+ });
134
+
135
+ afterAll(async () => {
136
+ if (agentId) await deleteAgent(agentId);
137
+ });
138
+
139
+ it('adds two skills and lists them in sort order', async () => {
140
+ await upsertSkill(agentId, '00-core', 'main.md', '# Main', 0);
141
+ await upsertSkill(agentId, '00-core', 'extra.md', '# Extra', 1);
142
+
143
+ const skills = await getAgentSkills(agentId);
144
+ expect(skills).toHaveLength(2);
145
+ // Sort order: main.md (0) before extra.md (1)
146
+ expect(skills[0].filename).toBe('main.md');
147
+ expect(skills[1].filename).toBe('extra.md');
148
+ });
149
+
150
+ it('deletes one skill and leaves only one remaining', async () => {
151
+ const skills = await getAgentSkills(agentId);
152
+ await deleteSkill(skills[0].id);
153
+
154
+ const remaining = await getAgentSkills(agentId);
155
+ expect(remaining).toHaveLength(1);
156
+ expect(remaining[0].filename).toBe('extra.md');
157
+ });
158
+ });
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // createSnapshot + listSnapshots + getSnapshotById + deleteSnapshot
162
+ // ---------------------------------------------------------------------------
163
+
164
+ describe('snapshots', () => {
165
+ let agentId: string;
166
+ let snapshotId: string;
167
+
168
+ beforeAll(async () => {
169
+ const agent = await createAgent({
170
+ slug: `test-snapshot-${Date.now()}`,
171
+ name: 'Snapshot Test',
172
+ slackBotToken: 'xoxb-test',
173
+ slackAppToken: 'xapp-test',
174
+ slackSigningSecret: 'secret',
175
+ }, 'test-user');
176
+ agentId = agent.id;
177
+ });
178
+
179
+ afterAll(async () => {
180
+ if (agentId) await deleteAgent(agentId);
181
+ });
182
+
183
+ it('creates a snapshot and it appears in listSnapshots', async () => {
184
+ const snap = await createSnapshot(
185
+ agentId,
186
+ 'manual',
187
+ 'test-user',
188
+ 'initial snapshot',
189
+ [{ category: '00-core', filename: 'main.md', content: '# Main', sort_order: 0 }],
190
+ ['Read'],
191
+ [],
192
+ [],
193
+ '# Main',
194
+ );
195
+ snapshotId = snap.id;
196
+
197
+ expect(snap.id).toBeTruthy();
198
+ expect(snap.label).toBe('initial snapshot');
199
+ expect(snap.trigger).toBe('manual');
200
+ expect(snap.compiledMd).toBe('# Main');
201
+
202
+ const list = await listSnapshots(agentId);
203
+ expect(list.some(s => s.id === snap.id)).toBe(true);
204
+ });
205
+
206
+ it('fetches a snapshot by id', async () => {
207
+ const fetched = await getSnapshotById(snapshotId);
208
+ expect(fetched).not.toBeNull();
209
+ expect(fetched!.id).toBe(snapshotId);
210
+ expect(fetched!.skillsJson).toHaveLength(1);
211
+ });
212
+
213
+ it('deletes a snapshot and it no longer appears in listSnapshots', async () => {
214
+ await deleteSnapshot(snapshotId);
215
+ const list = await listSnapshots(agentId);
216
+ expect(list.some(s => s.id === snapshotId)).toBe(false);
217
+ });
218
+ });
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // userCanWriteAgent + grantAgentWrite + revokeAgentWrite
222
+ // ---------------------------------------------------------------------------
223
+
224
+ describe('userCanWriteAgent', () => {
225
+ let agentId: string;
226
+ let editorUserId: string;
227
+ const editorUsername = `test-editor-${Date.now()}`;
228
+
229
+ beforeAll(async () => {
230
+ const agent = await createAgent({
231
+ slug: `test-access-${Date.now()}`,
232
+ name: 'Access Test',
233
+ slackBotToken: 'xoxb-test',
234
+ slackAppToken: 'xapp-test',
235
+ slackSigningSecret: 'secret',
236
+ }, 'system');
237
+ agentId = agent.id;
238
+
239
+ // Create a test editor user
240
+ const user = await createUser(editorUsername, 'hashed-password', 'editor');
241
+ editorUserId = user.id;
242
+ });
243
+
244
+ afterAll(async () => {
245
+ if (agentId) await deleteAgent(agentId);
246
+ if (editorUserId) await deleteUser(editorUserId);
247
+ });
248
+
249
+ it('returns true for admin role without a DB check', async () => {
250
+ const result = await userCanWriteAgent(agentId, 'any-admin', 'admin');
251
+ expect(result).toBe(true);
252
+ });
253
+
254
+ it('returns false for editor with no grants', async () => {
255
+ const result = await userCanWriteAgent(agentId, editorUsername, 'editor');
256
+ expect(result).toBe(false);
257
+ });
258
+
259
+ it('returns true after grantAgentWrite', async () => {
260
+ await grantAgentWrite(agentId, editorUserId);
261
+ const result = await userCanWriteAgent(agentId, editorUsername, 'editor');
262
+ expect(result).toBe(true);
263
+ });
264
+
265
+ it('returns false after revokeAgentWrite', async () => {
266
+ await revokeAgentWrite(agentId, editorUserId);
267
+ const result = await userCanWriteAgent(agentId, editorUsername, 'editor');
268
+ expect(result).toBe(false);
269
+ });
270
+ });
271
+ });
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview Unit tests for the lineDiff utility in diff.ts.
3
+ *
4
+ * @module web/lib/__tests__/diff.test
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { lineDiff } from '@/lib/diff';
9
+
10
+ describe('lineDiff', () => {
11
+ it('returns a single same entry for two empty strings', () => {
12
+ // ''.split('\n') yields [''], so one same entry with an empty line
13
+ expect(lineDiff('', '')).toEqual([{ type: 'same', line: '' }]);
14
+ });
15
+
16
+ it('returns all same entries for identical strings', () => {
17
+ const result = lineDiff('hello\nworld', 'hello\nworld');
18
+ expect(result).toEqual([
19
+ { type: 'same', line: 'hello' },
20
+ { type: 'same', line: 'world' },
21
+ ]);
22
+ });
23
+
24
+ it('returns add entries for new lines when old is empty', () => {
25
+ // old = '' → [''], new = 'foo\nbar' → ['foo','bar']
26
+ // '' does not match 'foo' or 'bar', so it becomes a remove; foo and bar become adds
27
+ const result = lineDiff('', 'foo\nbar');
28
+ const adds = result.filter(l => l.type === 'add').map(l => l.line);
29
+ expect(adds).toEqual(['foo', 'bar']);
30
+ });
31
+
32
+ it('returns remove entries for old lines when new is empty', () => {
33
+ // old = 'foo\nbar' → ['foo','bar'], new = '' → ['']
34
+ // foo and bar become removes; '' becomes an add
35
+ const result = lineDiff('foo\nbar', '');
36
+ const removes = result.filter(l => l.type === 'remove').map(l => l.line);
37
+ expect(removes).toEqual(['foo', 'bar']);
38
+ });
39
+
40
+ it('detects one line added in the middle', () => {
41
+ const result = lineDiff('a\nb', 'a\nc\nb');
42
+ expect(result).toContainEqual({ type: 'add', line: 'c' });
43
+ expect(result.filter(l => l.type === 'same').map(l => l.line)).toEqual(['a', 'b']);
44
+ });
45
+
46
+ it('detects one line removed from the middle', () => {
47
+ const result = lineDiff('a\nb\nc', 'a\nc');
48
+ expect(result).toContainEqual({ type: 'remove', line: 'b' });
49
+ expect(result.filter(l => l.type === 'same').map(l => l.line)).toEqual(['a', 'c']);
50
+ });
51
+
52
+ it('produces all removes then all adds for a complete replacement', () => {
53
+ const result = lineDiff('old1\nold2', 'new1\nnew2');
54
+ const types = result.map(l => l.type);
55
+ // All removes should come before all adds
56
+ const firstAdd = types.indexOf('add');
57
+ const lastRemove = types.lastIndexOf('remove');
58
+ expect(firstAdd).toBeGreaterThan(-1);
59
+ expect(lastRemove).toBeGreaterThan(-1);
60
+ expect(lastRemove).toBeLessThan(firstAdd);
61
+ expect(result.filter(l => l.type === 'remove').map(l => l.line)).toEqual(['old1', 'old2']);
62
+ expect(result.filter(l => l.type === 'add').map(l => l.line)).toEqual(['new1', 'new2']);
63
+ });
64
+
65
+ it('treats lines with only whitespace differences as different', () => {
66
+ const result = lineDiff('hello', 'hello ');
67
+ expect(result).toContainEqual({ type: 'remove', line: 'hello' });
68
+ expect(result).toContainEqual({ type: 'add', line: 'hello ' });
69
+ });
70
+
71
+ it('treats lines differing only by indentation as different', () => {
72
+ const result = lineDiff(' indented', 'indented');
73
+ expect(result).toContainEqual({ type: 'remove', line: ' indented' });
74
+ expect(result).toContainEqual({ type: 'add', line: 'indented' });
75
+ });
76
+
77
+ it('handles Windows CRLF by treating \\r as part of the line content', () => {
78
+ // lineDiff splits on \n only; \r stays attached to the line
79
+ const result = lineDiff('line1\r\nline2', 'line1\nline2');
80
+ const removeLines = result.filter(l => l.type === 'remove').map(l => l.line);
81
+ const addLines = result.filter(l => l.type === 'add').map(l => l.line);
82
+ expect(removeLines).toContain('line1\r');
83
+ expect(addLines).toContain('line1');
84
+ });
85
+
86
+ it('handles a single line with no newlines', () => {
87
+ const result = lineDiff('old', 'new');
88
+ expect(result).toContainEqual({ type: 'remove', line: 'old' });
89
+ expect(result).toContainEqual({ type: 'add', line: 'new' });
90
+ });
91
+
92
+ it('handles multiline: first same, middle change, last same', () => {
93
+ const oldText = 'line1\noriginal\nline3';
94
+ const newText = 'line1\nchanged\nline3';
95
+ const result = lineDiff(oldText, newText);
96
+ expect(result[0]).toEqual({ type: 'same', line: 'line1' });
97
+ const middleEntries = result.slice(1, result.length - 1);
98
+ expect(middleEntries).toContainEqual({ type: 'remove', line: 'original' });
99
+ expect(middleEntries).toContainEqual({ type: 'add', line: 'changed' });
100
+ expect(result[result.length - 1]).toEqual({ type: 'same', line: 'line3' });
101
+ });
102
+ });
@@ -0,0 +1,274 @@
1
+ /**
2
+ * @fileoverview Unit tests for mcp-mask.ts — maskMcpConfig, maskMcpServer, mergeMcpConfig.
3
+ *
4
+ * These tests cover the core security behaviour: secrets are never returned to
5
+ * the client in plaintext, and sending back "********" placeholders on PATCH
6
+ * never overwrites the real stored value.
7
+ *
8
+ * No database connection required — all tests use inline data.
9
+ *
10
+ * @module web/lib/__tests__/mcp-mask.test
11
+ */
12
+
13
+ import { describe, it, expect } from 'vitest';
14
+ import { maskMcpConfig, maskMcpServer, mergeMcpConfig } from '@/lib/mcp-mask';
15
+ import type { McpServer } from '@slackhive/shared';
16
+
17
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
18
+
19
+ function makeServer(overrides: Partial<McpServer> = {}): McpServer {
20
+ return {
21
+ id: 'mcp-1',
22
+ name: 'test-mcp',
23
+ type: 'stdio',
24
+ config: { command: 'node', args: ['server.js'], env: { SECRET: 'real-secret', API_KEY: 'real-key' } },
25
+ description: 'Test server',
26
+ enabled: true,
27
+ createdAt: new Date(),
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ // ─── maskMcpConfig ────────────────────────────────────────────────────────────
33
+
34
+ describe('maskMcpConfig', () => {
35
+ it('masks all env values for stdio config', () => {
36
+ const config = { command: 'node', args: ['s.js'], env: { SECRET: 'real-secret', KEY: 'real-key' } };
37
+ const result = maskMcpConfig(config) as typeof config;
38
+ expect(result.env.SECRET).toBe('********');
39
+ expect(result.env.KEY).toBe('********');
40
+ });
41
+
42
+ it('preserves env keys after masking', () => {
43
+ const config = { command: 'node', args: [], env: { DB_URL: 'postgres://...', TOKEN: 'abc' } };
44
+ const result = maskMcpConfig(config) as typeof config;
45
+ expect(Object.keys(result.env)).toEqual(['DB_URL', 'TOKEN']);
46
+ });
47
+
48
+ it('preserves non-secret fields (command, args) for stdio', () => {
49
+ const config = { command: 'node', args: ['server.js'], env: { KEY: 'val' } };
50
+ const result = maskMcpConfig(config) as typeof config;
51
+ expect(result.command).toBe('node');
52
+ expect(result.args).toEqual(['server.js']);
53
+ });
54
+
55
+ it('masks all header values for sse config', () => {
56
+ const config = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer tok', 'X-Key': 'secret' } };
57
+ const result = maskMcpConfig(config) as typeof config;
58
+ expect(result.headers.Authorization).toBe('********');
59
+ expect(result.headers['X-Key']).toBe('********');
60
+ });
61
+
62
+ it('preserves url for sse config after masking headers', () => {
63
+ const config = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer tok' } };
64
+ const result = maskMcpConfig(config) as typeof config;
65
+ expect(result.url).toBe('https://api.example.com/sse');
66
+ });
67
+
68
+ it('masks header values for http config', () => {
69
+ const config = { url: 'https://api.example.com/mcp', headers: { 'X-Api-Key': 'supersecret' } };
70
+ const result = maskMcpConfig(config) as typeof config;
71
+ expect((result as typeof config).headers['X-Api-Key']).toBe('********');
72
+ });
73
+
74
+ it('returns config unchanged when no env or headers present (stdio without env)', () => {
75
+ const config = { command: 'node', args: ['s.js'] };
76
+ const result = maskMcpConfig(config);
77
+ expect(result).toEqual(config);
78
+ });
79
+
80
+ it('returns config unchanged when env is empty object', () => {
81
+ const config = { command: 'node', args: [], env: {} };
82
+ const result = maskMcpConfig(config) as typeof config;
83
+ expect(result.env).toEqual({});
84
+ });
85
+
86
+ it('does not mutate the original config object', () => {
87
+ const config = { command: 'node', args: [], env: { SECRET: 'real' } };
88
+ maskMcpConfig(config);
89
+ expect((config as typeof config).env.SECRET).toBe('real');
90
+ });
91
+ });
92
+
93
+ // ─── maskMcpServer ────────────────────────────────────────────────────────────
94
+
95
+ describe('maskMcpServer', () => {
96
+ it('returns a server with masked env values', () => {
97
+ const server = makeServer();
98
+ const result = maskMcpServer(server);
99
+ const cfg = result.config as { env: Record<string, string> };
100
+ expect(cfg.env.SECRET).toBe('********');
101
+ expect(cfg.env.API_KEY).toBe('********');
102
+ });
103
+
104
+ it('preserves all non-config fields on the server', () => {
105
+ const server = makeServer({ name: 'my-mcp', type: 'stdio', enabled: false });
106
+ const result = maskMcpServer(server);
107
+ expect(result.id).toBe('mcp-1');
108
+ expect(result.name).toBe('my-mcp');
109
+ expect(result.type).toBe('stdio');
110
+ expect(result.enabled).toBe(false);
111
+ });
112
+
113
+ it('does not mutate the original server object', () => {
114
+ const server = makeServer();
115
+ maskMcpServer(server);
116
+ const cfg = server.config as { env: Record<string, string> };
117
+ expect(cfg.env.SECRET).toBe('real-secret');
118
+ });
119
+ });
120
+
121
+ // ─── mergeMcpConfig ───────────────────────────────────────────────────────────
122
+
123
+ describe('mergeMcpConfig', () => {
124
+ it('preserves existing secret when incoming value is "********"', () => {
125
+ const existing = { command: 'node', args: [], env: { SECRET: 'real-secret' } };
126
+ const incoming = { command: 'node', args: [], env: { SECRET: '********' } };
127
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
128
+ expect(result.env.SECRET).toBe('real-secret');
129
+ });
130
+
131
+ it('uses new value when incoming is not "********"', () => {
132
+ const existing = { command: 'node', args: [], env: { SECRET: 'old-secret' } };
133
+ const incoming = { command: 'node', args: [], env: { SECRET: 'new-secret' } };
134
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
135
+ expect(result.env.SECRET).toBe('new-secret');
136
+ });
137
+
138
+ it('handles mixed masked and real values in same PATCH', () => {
139
+ const existing = { command: 'node', args: [], env: { SECRET: 'real-secret', NEW_KEY: 'old-val' } };
140
+ const incoming = { command: 'node', args: [], env: { SECRET: '********', NEW_KEY: 'updated' } };
141
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
142
+ expect(result.env.SECRET).toBe('real-secret');
143
+ expect(result.env.NEW_KEY).toBe('updated');
144
+ });
145
+
146
+ it('removes a key deleted by the user (key absent from incoming)', () => {
147
+ const existing = { command: 'node', args: [], env: { SECRET: 'real', OLD_KEY: 'val' } };
148
+ const incoming = { command: 'node', args: [], env: { SECRET: '********' } };
149
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
150
+ expect('OLD_KEY' in result.env).toBe(false);
151
+ });
152
+
153
+ it('adds a new key introduced in the PATCH', () => {
154
+ const existing = { command: 'node', args: [], env: { SECRET: 'real' } };
155
+ const incoming = { command: 'node', args: [], env: { SECRET: '********', NEW_KEY: 'new-val' } };
156
+ const result = mergeMcpConfig(existing, incoming) as { command: string; args: string[]; env: Record<string, string> };
157
+ expect(result.env.SECRET).toBe('real');
158
+ expect(result.env.NEW_KEY).toBe('new-val');
159
+ });
160
+
161
+ it('preserves existing header secret for sse config', () => {
162
+ const existing = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer real-token' } };
163
+ const incoming = { url: 'https://api.example.com/sse', headers: { Authorization: '********' } };
164
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
165
+ expect(result.headers.Authorization).toBe('Bearer real-token');
166
+ });
167
+
168
+ it('updates url even when headers are masked', () => {
169
+ const existing = { url: 'https://old.example.com/sse', headers: { Authorization: 'Bearer tok' } };
170
+ const incoming = { url: 'https://new.example.com/sse', headers: { Authorization: '********' } };
171
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
172
+ expect(result.url).toBe('https://new.example.com/sse');
173
+ expect(result.headers.Authorization).toBe('Bearer tok');
174
+ });
175
+
176
+ it('returns incoming unchanged when neither env nor headers exist', () => {
177
+ const existing = { command: 'node', args: ['s.js'] };
178
+ const incoming = { command: 'python', args: ['s.py'] };
179
+ const result = mergeMcpConfig(existing, incoming);
180
+ expect(result).toEqual(incoming);
181
+ });
182
+
183
+ it('does not mutate the existing config', () => {
184
+ const existing = { command: 'node', args: [], env: { SECRET: 'real' } };
185
+ const incoming = { command: 'node', args: [], env: { SECRET: 'new' } };
186
+ mergeMcpConfig(existing, incoming);
187
+ expect(existing.env.SECRET).toBe('real');
188
+ });
189
+ });
190
+
191
+ // ─── maskMcpConfig — envRefs and tsSource pass-through ───────────────────────
192
+
193
+ describe('maskMcpConfig — envRefs and tsSource are not secrets', () => {
194
+ it('passes envRefs through unchanged when masking env values', () => {
195
+ const config = {
196
+ command: 'tsx',
197
+ args: ['server.ts'],
198
+ env: { EXTRA: 'val' },
199
+ envRefs: { DATABASE_URL: 'REDSHIFT_DATABASE_URL' },
200
+ };
201
+ const result = maskMcpConfig(config) as typeof config;
202
+ expect(result.envRefs).toEqual({ DATABASE_URL: 'REDSHIFT_DATABASE_URL' });
203
+ expect(result.env.EXTRA).toBe('********');
204
+ });
205
+
206
+ it('passes tsSource through unchanged when masking env values', () => {
207
+ const config = {
208
+ command: 'tsx',
209
+ args: ['server.ts'],
210
+ env: { SECRET: 'val' },
211
+ tsSource: 'import { Server } from "@modelcontextprotocol/sdk/server/index.js";',
212
+ };
213
+ const result = maskMcpConfig(config) as typeof config;
214
+ expect(result.tsSource).toBe(config.tsSource);
215
+ expect(result.env.SECRET).toBe('********');
216
+ });
217
+
218
+ it('passes envRefs and tsSource through even with no inline env', () => {
219
+ const config = {
220
+ command: 'tsx',
221
+ args: [],
222
+ envRefs: { DB: 'MY_DB_URL' },
223
+ tsSource: '// inline source',
224
+ };
225
+ const result = maskMcpConfig(config) as typeof config;
226
+ expect(result.envRefs).toEqual({ DB: 'MY_DB_URL' });
227
+ expect(result.tsSource).toBe('// inline source');
228
+ });
229
+ });
230
+
231
+ // ─── mergeMcpConfig — envRefs handling ────────────────────────────────────────
232
+
233
+ describe('mergeMcpConfig — envRefs are config fields, not secrets', () => {
234
+ it('replaces envRefs with incoming value', () => {
235
+ const existing = { command: 'tsx', args: [], envRefs: { DB: 'OLD_KEY' } };
236
+ const incoming = { command: 'tsx', args: [], envRefs: { DB: 'NEW_KEY' } };
237
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
238
+ expect(result.envRefs).toEqual({ DB: 'NEW_KEY' });
239
+ });
240
+
241
+ it('removes envRefs when incoming omits them', () => {
242
+ const existing = { command: 'tsx', args: [], envRefs: { DB: 'SOME_KEY' } };
243
+ const incoming = { command: 'tsx', args: [] };
244
+ const result = mergeMcpConfig(existing, incoming) as unknown as Record<string, unknown>;
245
+ expect(result.envRefs).toBeUndefined();
246
+ });
247
+ });
248
+
249
+ // ─── mergeMcpConfig — header branch gaps (lines 81-84) ───────────────────────
250
+
251
+ describe('mergeMcpConfig — header key deletion and new key for sse/http', () => {
252
+ it('removes a header key deleted by the user', () => {
253
+ const existing = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer tok', 'X-Old': 'val' } };
254
+ const incoming = { url: 'https://api.example.com/sse', headers: { Authorization: '********' } };
255
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
256
+ expect('X-Old' in result.headers).toBe(false);
257
+ expect(result.headers.Authorization).toBe('Bearer tok');
258
+ });
259
+
260
+ it('adds a new header key introduced in the PATCH', () => {
261
+ const existing = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer tok' } };
262
+ const incoming = { url: 'https://api.example.com/sse', headers: { Authorization: '********', 'X-New': 'newval' } };
263
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
264
+ expect(result.headers.Authorization).toBe('Bearer tok');
265
+ expect((result.headers as Record<string, string>)['X-New']).toBe('newval');
266
+ });
267
+
268
+ it('uses new real header value when not masked', () => {
269
+ const existing = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer old' } };
270
+ const incoming = { url: 'https://api.example.com/sse', headers: { Authorization: 'Bearer new' } };
271
+ const result = mergeMcpConfig(existing, incoming) as typeof existing;
272
+ expect(result.headers.Authorization).toBe('Bearer new');
273
+ });
274
+ });